electron/spec/api-notification-dbus-spec.ts
trop[bot] ad8f2f8d5d
test: add tests dbus notification images (#43946)
* test: add tests dbus notification images

Provide a NativeImage icon in the notification tests and then inspect
the DBus message payload's `image_data` hint to see if it's correct.
This adds test coverage for LibnotifyNotification::Show() and for
GdkPixbufFromSkBitmap().

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* chore: use the same notification_icon.png as in main

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2024-09-25 16:48:24 -05:00

133 lines
4.3 KiB
TypeScript

// For these tests we use a fake DBus daemon to verify libnotify interaction
// with the session bus. This requires python-dbusmock to be installed and
// running at $DBUS_SESSION_BUS_ADDRESS.
//
// script/spec-runner.js spawns dbusmock, which sets DBUS_SESSION_BUS_ADDRESS.
//
// See https://pypi.python.org/pypi/python-dbusmock to read about dbusmock.
import { expect } from 'chai';
import * as dbus from 'dbus-native';
import { app } from 'electron/main';
import { nativeImage } from 'electron/common';
import { ifdescribe } from './lib/spec-helpers';
import { promisify } from 'node:util';
import * as path from 'node:path';
const fixturesPath = path.join(__dirname, 'fixtures');
const skip = process.platform !== 'linux' ||
process.arch === 'ia32' ||
process.arch.indexOf('arm') === 0 ||
!process.env.DBUS_SESSION_BUS_ADDRESS;
ifdescribe(!skip)('Notification module (dbus)', () => {
let mock: any, Notification, getCalls: any, reset: any;
const realAppName = app.name;
const realAppVersion = app.getVersion();
const appName = 'api-notification-dbus-spec';
const serviceName = 'org.freedesktop.Notifications';
before(async () => {
// init app
app.name = appName;
app.setDesktopName(`${appName}.desktop`);
// init DBus
const path = '/org/freedesktop/Notifications';
const iface = 'org.freedesktop.DBus.Mock';
console.log(`session bus: ${process.env.DBUS_SESSION_BUS_ADDRESS}`);
const bus = dbus.sessionBus();
const service = bus.getService(serviceName);
const getInterface = promisify(service.getInterface.bind(service));
mock = await getInterface(path, iface);
getCalls = promisify(mock.GetCalls.bind(mock));
reset = promisify(mock.Reset.bind(mock));
});
after(async () => {
// cleanup dbus
if (reset) await reset();
// cleanup app
app.setName(realAppName);
app.setVersion(realAppVersion);
});
describe(`Notification module using ${serviceName}`, () => {
function onMethodCalled (done: () => void) {
function cb (name: string) {
console.log(`onMethodCalled: ${name}`);
if (name === 'Notify') {
mock.removeListener('MethodCalled', cb);
console.log('done');
done();
}
}
return cb;
}
function unmarshalDBusNotifyHints (dbusHints: any) {
const o: Record<string, any> = {};
for (const hint of dbusHints) {
const key = hint[0];
const value = hint[1][1][0];
o[key] = value;
}
return o;
}
function unmarshalDBusNotifyArgs (dbusArgs: any) {
return {
app_name: dbusArgs[0][1][0],
replaces_id: dbusArgs[1][1][0],
app_icon: dbusArgs[2][1][0],
title: dbusArgs[3][1][0],
body: dbusArgs[4][1][0],
actions: dbusArgs[5][1][0],
hints: unmarshalDBusNotifyHints(dbusArgs[6][1][0])
};
}
before(done => {
mock.on('MethodCalled', onMethodCalled(done));
// lazy load Notification after we listen to MethodCalled mock signal
Notification = require('electron').Notification;
const n = new Notification({
title: 'title',
subtitle: 'subtitle',
body: 'body',
icon: nativeImage.createFromPath(path.join(fixturesPath, 'assets', 'notification_icon.png')),
replyPlaceholder: 'replyPlaceholder',
sound: 'sound',
closeButtonText: 'closeButtonText'
});
n.show();
});
it(`should call ${serviceName} to show notifications`, async () => {
const calls = await getCalls();
expect(calls).to.be.an('array').of.lengthOf.at.least(1);
const lastCall = calls[calls.length - 1];
const methodName = lastCall[1];
expect(methodName).to.equal('Notify');
const args = unmarshalDBusNotifyArgs(lastCall[2]);
expect(args).to.deep.equal({
app_name: appName,
replaces_id: 0,
app_icon: '',
title: 'title',
body: 'body',
actions: [],
hints: {
append: 'true',
image_data: [3, 3, 12, true, 8, 4, Buffer.from([255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 76, 255, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 38, 255, 255, 0, 0, 0, 255, 0, 0, 0, 0])],
'desktop-entry': appName,
'sender-pid': process.pid,
urgency: 1
}
});
});
});
});