mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(desktop): update from the splash only after the user clicks (#3280)
This commit is contained in:
1 parent
f1373a63c3
commit
7937d5f802
90 files changed
+6888
-11255
No files matched your search
@@ -0,0 +1,197 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import {describe, test} from 'node:test';
|
||||
import {installElectronStub} from './LocalAppTestSupport.test.mjs';
|
||||
|
||||
installElectronStub();
|
||||
|
||||
const {DesktopUpdateRun} = await import('@electron/main/DesktopUpdateRun');
|
||||
|
||||
const LAUNCH_ATTEMPT = Object.freeze({committed: {fluxer_renderer: 'b'.repeat(64)}});
|
||||
|
||||
function harness({
|
||||
probes = [{shellLatestVersion: '2026.1008.2', shellNewer: false, modulesChanged: true}],
|
||||
canSelfUpdateShell = true,
|
||||
shellResults = [],
|
||||
installFails = false,
|
||||
settled = true,
|
||||
} = {}) {
|
||||
const steps = [];
|
||||
const published = [];
|
||||
let probeIndex = 0;
|
||||
let shellIndex = 0;
|
||||
const quiet = () => undefined;
|
||||
const run = new DesktopUpdateRun({
|
||||
probe: async () => {
|
||||
const next = probes[Math.min(probeIndex, probes.length - 1)];
|
||||
probeIndex += 1;
|
||||
steps.push('probe');
|
||||
if (next instanceof Error) throw next;
|
||||
return next;
|
||||
},
|
||||
canSelfUpdateShell,
|
||||
runShellSelfUpdate: async () => {
|
||||
steps.push('shell-update');
|
||||
const result = shellResults[Math.min(shellIndex, shellResults.length - 1)];
|
||||
shellIndex += 1;
|
||||
return result;
|
||||
},
|
||||
installModules: async () => {
|
||||
steps.push('install');
|
||||
if (installFails) throw new Error('disk full');
|
||||
return LAUNCH_ATTEMPT;
|
||||
},
|
||||
takeover: {
|
||||
begin: async () => {
|
||||
steps.push('takeover');
|
||||
},
|
||||
restore: () => {
|
||||
steps.push('restore');
|
||||
},
|
||||
closeApp: async () => {
|
||||
steps.push('close');
|
||||
},
|
||||
reopen: (launchAttempt) => {
|
||||
steps.push(launchAttempt == null ? 'reopen' : 'reopen-updated');
|
||||
},
|
||||
},
|
||||
publish: (check) => {
|
||||
published.push(check);
|
||||
},
|
||||
openDownloadsPage: async () => {
|
||||
steps.push('downloads-page');
|
||||
},
|
||||
logger: {info: quiet, warn: quiet, error: quiet},
|
||||
});
|
||||
if (settled) run.markLaunchSettled();
|
||||
return {run, steps, published};
|
||||
}
|
||||
|
||||
describe('DesktopUpdateRun', () => {
|
||||
test('the background check only looks, it never downloads, closes or installs anything', async () => {
|
||||
const {run, steps, published} = harness();
|
||||
|
||||
assert.deepEqual(await run.check(), {shellNewer: false, modulesChanged: true});
|
||||
|
||||
assert.deepEqual(steps, ['probe']);
|
||||
assert.deepEqual(published, []);
|
||||
});
|
||||
|
||||
test('a click takes the window over, checks, and only then closes the app and installs', async () => {
|
||||
const {run, steps, published} = harness();
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'close', 'install', 'reopen-updated']);
|
||||
assert.deepEqual(published, [{shellNewer: false, modulesChanged: false}]);
|
||||
});
|
||||
|
||||
test('a click while offline gives the same windows back instead of tearing the app down', async () => {
|
||||
const {run, steps, published} = harness({probes: [new Error('offline')]});
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'restore']);
|
||||
assert.deepEqual(published, [], 'the icon stays so the next click can try again');
|
||||
});
|
||||
|
||||
test('a click after the update was withdrawn gives the windows back and hides the icon', async () => {
|
||||
const {run, steps, published} = harness({
|
||||
probes: [{shellLatestVersion: '2026.1008.1', shellNewer: false, modulesChanged: false}],
|
||||
});
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'restore']);
|
||||
assert.deepEqual(published, [{shellNewer: false, modulesChanged: false}]);
|
||||
});
|
||||
|
||||
test('a shell update runs before any module download, and modules install only when the shell did not restart', async () => {
|
||||
const {run, steps} = harness({
|
||||
probes: [{shellLatestVersion: '2026.1008.2', shellNewer: true, modulesChanged: true}],
|
||||
shellResults: [{reason: 'install-failed', detail: 'nope'}],
|
||||
});
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'close', 'shell-update', 'install', 'reopen-updated']);
|
||||
});
|
||||
|
||||
test('a shell feed that lags the manifest stops offering the shell until the manifest moves on', async () => {
|
||||
const lagging = {shellLatestVersion: '2026.1008.2', shellNewer: true, modulesChanged: false};
|
||||
const {run, steps, published} = harness({
|
||||
probes: [lagging, lagging, {...lagging, shellLatestVersion: '2026.1008.3'}],
|
||||
shellResults: [{reason: 'no-update', detail: null}],
|
||||
});
|
||||
|
||||
await run.start();
|
||||
assert.deepEqual(published, [{shellNewer: false, modulesChanged: false}]);
|
||||
|
||||
assert.deepEqual(
|
||||
await run.check(),
|
||||
{shellNewer: false, modulesChanged: false},
|
||||
'the poll no longer shows the icon',
|
||||
);
|
||||
assert.deepEqual(await run.check(), {shellNewer: true, modulesChanged: false}, 'a newer manifest shell does');
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'close', 'shell-update', 'reopen', 'probe', 'probe']);
|
||||
});
|
||||
|
||||
test('a shell update that failed on the network is retried by the next click', async () => {
|
||||
const shell = {shellLatestVersion: '2026.1008.2', shellNewer: true, modulesChanged: false};
|
||||
const {run, steps, published} = harness({
|
||||
probes: [shell],
|
||||
shellResults: [
|
||||
{reason: 'timed-out', detail: 'slow link'},
|
||||
{reason: 'check-failed', detail: 'offline'},
|
||||
],
|
||||
});
|
||||
|
||||
await run.start();
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(published, [
|
||||
{shellNewer: true, modulesChanged: false},
|
||||
{shellNewer: true, modulesChanged: false},
|
||||
]);
|
||||
assert.equal(steps.filter((step) => step === 'shell-update').length, 2);
|
||||
assert.equal(steps.includes('downloads-page'), false);
|
||||
});
|
||||
|
||||
test('only a shell update that failed to install falls back to the downloads page', async () => {
|
||||
const shell = {shellLatestVersion: '2026.1008.2', shellNewer: true, modulesChanged: false};
|
||||
const {run, steps} = harness({probes: [shell], shellResults: [{reason: 'install-failed', detail: 'apply'}]});
|
||||
|
||||
await run.start();
|
||||
steps.length = 0;
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['downloads-page']);
|
||||
});
|
||||
|
||||
test('a shell that cannot update itself never offers the shell part', async () => {
|
||||
const {run} = harness({
|
||||
canSelfUpdateShell: false,
|
||||
probes: [{shellLatestVersion: '2026.1008.2', shellNewer: true, modulesChanged: false}],
|
||||
});
|
||||
|
||||
assert.deepEqual(await run.check(), {shellNewer: false, modulesChanged: false});
|
||||
});
|
||||
|
||||
test('a failed module install reopens the app on the installed set and keeps the icon', async () => {
|
||||
const {run, steps, published} = harness({installFails: true});
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, ['probe', 'takeover', 'close', 'install', 'reopen']);
|
||||
assert.deepEqual(published, [{shellNewer: false, modulesChanged: true}]);
|
||||
});
|
||||
|
||||
test('a click before the renderer confirmed its launch does nothing', async () => {
|
||||
const {run, steps} = harness({settled: false});
|
||||
|
||||
await run.start();
|
||||
|
||||
assert.deepEqual(steps, []);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user