Files
Amit Haridas 47315add80 feat(updater): electron-updater wiring + IPC bridge
electron-updater integration so users get fixes automatically:
  - new dependency electron-updater@^6.6.2 in package.json
  - electron-builder.config.js publishes to GitHub releases
    (owner: concreteinfo, repo: markdownconverter)
  - new src/main/auto-updater.js wraps autoUpdater: wires the six
    lifecycle events to a 'updates:status' IPC channel, exposes
    check() and install() helpers, dev-gates via isDev(), and
    swallows the 'missing app-update.yml' error that dev runs hit
  - main.js: ensureAutoUpdater() forwards status to the renderer
    via mainWindow.webContents.send; ipcMain handlers for
    'updates:check' (invoke) and 'updates:install' (invoke)
  - preload.js: 'updates:check' / 'updates:install' added to
    ALLOWED_SEND_CHANNELS; 'updates:status' added to receive
    channels; electronAPI.updates.{check, install, onStatus}
    exposed

7 new tests cover event registration, dev-gate behaviour,
'not-available' fallback for missing app-update.yml, typed event
payloads, error forwarding, and install() quitAndInstall.

Amit Haridas
2026-09-30 20:30:19 +05:30

115 lines
3.4 KiB
JavaScript

/**
* @jest-environment node
*
* Auto-updater — unit test the controller without bringing up electron.
* The electron-updater module is mocked because the real one requires the
* Electron app to be initialised.
*/
const mockAutoUpdater = {
on: jest.fn(),
autoDownload: false,
autoInstallOnAppQuit: false,
checkForUpdates: jest.fn().mockResolvedValue(null),
quitAndInstall: jest.fn(),
};
jest.mock('electron-updater', () => ({ autoUpdater: mockAutoUpdater }), { virtual: true });
const { setupAutoUpdater } = require('../src/main/auto-updater');
describe('setupAutoUpdater', () => {
let send;
let updater;
beforeEach(() => {
jest.clearAllMocks();
mockAutoUpdater.on.mockReset();
send = jest.fn();
updater = setupAutoUpdater({ send });
});
test('registers all six event handlers', () => {
const events = mockAutoUpdater.on.mock.calls.map((c) => c[0]);
expect(events).toEqual(
expect.arrayContaining([
'checking-for-update',
'update-available',
'update-not-available',
'download-progress',
'update-downloaded',
'error',
])
);
});
test('enables autoDownload and autoInstallOnAppQuit', () => {
expect(mockAutoUpdater.autoDownload).toBe(true);
expect(mockAutoUpdater.autoInstallOnAppQuit).toBe(true);
});
test('check() forwards "not-available" when app-update.yml is missing (dev)', async () => {
mockAutoUpdater.checkForUpdates.mockRejectedValueOnce(
new Error('Cannot find any update manifest (app-update.yml) in resources')
);
await updater.check();
expect(send).toHaveBeenCalledWith('updates:status', { state: 'not-available' });
});
test('check() forwards "error" for non-benign failures', async () => {
mockAutoUpdater.checkForUpdates.mockRejectedValueOnce(new Error('network down'));
await updater.check();
expect(send).toHaveBeenCalledWith('updates:status', {
state: 'error',
message: 'network down',
});
});
test('check() respects isDev() and does not invoke autoUpdater', async () => {
const isDev = jest.fn(() => true);
const u2 = setupAutoUpdater({ send, isDev });
await u2.check();
expect(isDev).toHaveBeenCalled();
expect(mockAutoUpdater.checkForUpdates).not.toHaveBeenCalled();
});
test('install() calls quitAndInstall', () => {
updater.install();
expect(mockAutoUpdater.quitAndInstall).toHaveBeenCalledTimes(1);
});
test('forwarded events carry typed payloads', () => {
// Find each handler and invoke it; assert what got sent.
const map = new Map();
for (const [event, cb] of mockAutoUpdater.on.mock.calls) map.set(event, cb);
send.mockClear();
map.get('checking-for-update')();
expect(send).toHaveBeenLastCalledWith('updates:status', { state: 'checking' });
map.get('update-available')({ version: '4.13.0' });
expect(send).toHaveBeenLastCalledWith('updates:status', {
state: 'available',
version: '4.13.0',
});
map.get('download-progress')({ percent: 42.7 });
expect(send).toHaveBeenLastCalledWith('updates:status', {
state: 'downloading',
percent: 43,
});
map.get('update-downloaded')({ version: '4.13.0' });
expect(send).toHaveBeenLastCalledWith('updates:status', {
state: 'downloaded',
version: '4.13.0',
});
map.get('error')(new Error('boom'));
expect(send).toHaveBeenLastCalledWith('updates:status', {
state: 'error',
message: 'boom',
});
});
});