fix(app): show download icon for shell updates (#3471)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-14 17:48:41 +02:00
1 parent 5f5e26a8d4
commit 7c16d424ce
5 files changed
+38 -15

No files matched your search

@@ -25,8 +25,8 @@ function deferred() {
describe('DesktopUpdateGate', () => {
test('nothing is available and nothing starts before the bootstrap arms the gate', async () => {
assert.deepEqual(getDesktopUpdateState(), {available: false, updating: false});
assert.deepEqual(await checkDesktopUpdateNow(), {available: false, updating: false});
assert.deepEqual(getDesktopUpdateState(), {available: false, updating: false, replacesShell: false});
assert.deepEqual(await checkDesktopUpdateNow(), {available: false, updating: false, replacesShell: false});
assert.equal(startDesktopUpdate(), false);
});
@@ -44,17 +44,17 @@ describe('DesktopUpdateGate', () => {
},
});
assert.deepEqual(await checkDesktopUpdateNow(), {available: true, updating: false});
assert.deepEqual(await checkDesktopUpdateNow(), {available: true, updating: false, replacesShell: false});
assert.equal(startDesktopUpdate(), true);
assert.equal(startDesktopUpdate(), false, 'a second click while the update runs is a no-op');
assert.deepEqual(
getDesktopUpdateState(),
{available: false, updating: true},
{available: false, updating: true, replacesShell: false},
'the affordance hides the moment it is clicked',
);
assert.deepEqual(
await checkDesktopUpdateNow(),
{available: false, updating: true},
{available: false, updating: true, replacesShell: false},
'a poll during the update changes nothing',
);
@@ -62,11 +62,11 @@ describe('DesktopUpdateGate', () => {
await new Promise((resolve) => setImmediate(resolve));
assert.equal(starts, 1);
assert.deepEqual(getDesktopUpdateState(), {available: false, updating: false});
assert.deepEqual(getDesktopUpdateState(), {available: false, updating: false, replacesShell: false});
assert.deepEqual(states, [
{available: true, updating: false},
{available: false, updating: true},
{available: false, updating: false},
{available: true, updating: false, replacesShell: false},
{available: false, updating: true, replacesShell: false},
{available: false, updating: false, replacesShell: false},
]);
stop();
});
@@ -111,7 +111,7 @@ describe('DesktopUpdateGate', () => {
pending.resolve();
await background;
assert.deepEqual(await manual, {available: true, updating: false});
assert.deepEqual(await manual, {available: true, updating: false, replacesShell: true});
assert.deepEqual(calls, [false, true]);
});
@@ -127,7 +127,7 @@ describe('DesktopUpdateGate', () => {
assert.equal(startDesktopUpdate(), true);
await new Promise((resolve) => setImmediate(resolve));
assert.deepEqual(getDesktopUpdateState(), {available: true, updating: false});
assert.deepEqual(getDesktopUpdateState(), {available: true, updating: false, replacesShell: true});
assert.equal(startDesktopUpdate(), true);
});
});
+12 -2
View File
@@ -24,6 +24,7 @@ interface DesktopUpdateGateState {
checking: {readonly manual: boolean; readonly settled: Promise<void>} | null;
notifiedAvailable: boolean;
notifiedUpdating: boolean;
notifiedReplacesShell: boolean;
readonly listeners: Set<DesktopUpdateStateListener>;
}
@@ -34,6 +35,7 @@ const gateState: DesktopUpdateGateState = {
checking: null,
notifiedAvailable: false,
notifiedUpdating: false,
notifiedReplacesShell: false,
listeners: new Set(),
};
@@ -42,9 +44,11 @@ function isAvailable(check: DesktopUpdateCheck | null): boolean {
}
export function getDesktopUpdateState(): DesktopUpdateState {
const available = gateState.running == null && isAvailable(gateState.lastCheck);
return {
available: gateState.running == null && isAvailable(gateState.lastCheck),
available,
updating: gateState.running != null,
replacesShell: available && gateState.lastCheck?.shellNewer === true,
};
}
@@ -55,11 +59,17 @@ export function desktopUpdateReplacesShell(): boolean {
function notify(): void {
const state = getDesktopUpdateState();
const updating = state.updating === true;
if (state.available === gateState.notifiedAvailable && updating === gateState.notifiedUpdating) {
const replacesShell = state.replacesShell === true;
if (
state.available === gateState.notifiedAvailable &&
updating === gateState.notifiedUpdating &&
replacesShell === gateState.notifiedReplacesShell
) {
return;
}
gateState.notifiedAvailable = state.available;
gateState.notifiedUpdating = updating;
gateState.notifiedReplacesShell = replacesShell;
for (const listener of Array.from(gateState.listeners)) {
try {
listener(state);