Files
iofficeai--aionui/tests/unit/settings/updateNotificationState.test.ts
2026-07-13 13:20:22 +08:00

421 lines
13 KiB
TypeScript

/**
* @license
* Copyright 2026 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import {
initialUpdateNotificationState,
type UpdateNotificationState,
updateNotificationCapabilities,
updateNotificationReducer,
} from '@/renderer/components/settings/updateNotificationState';
describe('updateNotificationReducer', () => {
it('opens the notification when auto-update reports an available version', () => {
const result = updateNotificationReducer(initialUpdateNotificationState, {
type: 'autoStatusAvailable',
version: '2.2.0',
currentVersion: '2.1.9',
releaseNotes: 'release notes',
});
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('available');
expect(result.state.autoUpdateAvailable).toBe(true);
expect(result.state.currentVersion).toBe('2.1.9');
expect(result.state.autoUpdateInfo).toEqual({
version: '2.2.0',
releaseNotes: 'release notes',
});
expect(result.effects).toEqual([{ type: 'loadManualReleaseInfoForDisplay' }]);
});
it('preserves the active download when an entry opens during downloading', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: false,
status: 'downloading',
activeTask: {
kind: 'manual',
id: 'manual-1',
},
progress: {
percent: 42,
transferred: 42,
total: 100,
speed: '1.0 MB/s',
},
};
const result = updateNotificationReducer(downloadingState, {
type: 'openRequested',
source: 'about',
userInitiated: true,
});
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('downloading');
expect(result.state.activeTask).toEqual({
kind: 'manual',
id: 'manual-1',
});
expect(result.state.progress).toEqual({
percent: 42,
transferred: 42,
total: 100,
speed: '1.0 MB/s',
});
expect(result.effects).toEqual([]);
});
it('hides the notification on later without clearing the active download', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
activeTask: {
kind: 'auto',
id: 'auto',
},
progress: {
percent: 64,
transferred: 64,
total: 100,
speed: '2.0 MB/s',
},
};
const result = updateNotificationReducer(downloadingState, {
type: 'dismissRequested',
reason: 'later',
});
expect(result.state.visible).toBe(false);
expect(result.state.status).toBe('downloading');
expect(result.state.activeTask).toEqual({
kind: 'auto',
id: 'auto',
});
expect(result.state.progress.percent).toBe(64);
expect(result.effects).toEqual([]);
});
it('cancels an active download and restores the available update state', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
currentVersion: '2.1.13',
autoUpdateAvailable: true,
autoUpdateInfo: {
version: '2.1.14',
releaseNotes: 'notes',
},
activeTask: {
kind: 'auto',
id: 'auto',
},
progress: {
percent: 58,
transferred: 58,
total: 100,
speed: '1.0 MB/s',
},
};
const result = updateNotificationReducer(downloadingState, {
type: 'cancelDownloadRequested',
} as never);
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('available');
expect(result.state.activeTask).toBeNull();
expect(result.state.progress.percent).toBe(0);
expect(result.state.autoUpdateInfo?.version).toBe('2.1.14');
expect(result.effects).toEqual([{ type: 'cancelDownload', task: { kind: 'auto', id: 'auto' } }]);
});
it('minimizes and restores the active download without changing progress owner', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
activeTask: {
kind: 'manual',
id: 'manual-1',
},
progress: {
percent: 33,
transferred: 33,
total: 100,
speed: '1.0 MB/s',
},
};
const minimized = updateNotificationReducer(downloadingState, {
type: 'minimizeRequested',
} as never);
expect(minimized.state.visible).toBe(true);
expect((minimized.state as UpdateNotificationState & { presentation: string }).presentation).toBe('mini');
expect(minimized.state.activeTask).toEqual({ kind: 'manual', id: 'manual-1' });
expect(minimized.state.progress.percent).toBe(33);
const restored = updateNotificationReducer(minimized.state, {
type: 'restoreRequested',
} as never);
expect((restored.state as UpdateNotificationState & { presentation: string }).presentation).toBe('card');
expect(restored.state.activeTask).toEqual({ kind: 'manual', id: 'manual-1' });
expect(restored.state.progress.percent).toBe(33);
});
it('keeps a 100 percent progress snapshot when auto download completes', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
activeTask: {
kind: 'auto',
id: 'auto',
},
progress: {
percent: 74,
transferred: 74,
total: 100,
speed: '2.0 MB/s',
},
};
const result = updateNotificationReducer(downloadingState, {
type: 'autoDownloaded',
});
expect(result.state.status).toBe('downloaded');
expect(result.state.activeTask).toBeNull();
expect(result.state.progress.percent).toBe(100);
});
it('restores a completed auto-update without requiring an active download owner', () => {
const result = updateNotificationReducer(initialUpdateNotificationState, {
type: 'autoDownloadedRestored',
version: '2.1.14',
currentVersion: '2.1.13',
} as never);
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('downloaded');
expect(result.state.autoUpdateAvailable).toBe(true);
expect(result.state.currentVersion).toBe('2.1.13');
expect(result.state.autoUpdateInfo).toEqual({ version: '2.1.14' });
expect(result.state.activeTask).toBeNull();
expect(result.state.presentation).toBe('card');
expect(result.state.progress.percent).toBe(100);
expect(result.effects).toEqual([]);
});
it('enters preparing-install after an auto-update install request', () => {
const downloadedState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloaded',
autoUpdateAvailable: true,
autoUpdateInfo: { version: '2.2.0' },
progress: {
percent: 100,
transferred: 100,
total: 100,
speed: '',
},
};
const result = updateNotificationReducer(downloadedState, {
type: 'autoPreparingInstall',
version: '2.2.0',
} as never);
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('preparing-install');
expect(result.state.autoUpdateInfo?.version).toBe('2.2.0');
expect(result.state.progress.percent).toBe(100);
expect(result.effects).toEqual([]);
});
it('shows an error when preparing-install fails before app exit', () => {
const preparingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'preparing-install',
autoUpdateAvailable: true,
autoUpdateInfo: { version: '2.2.0' },
};
const result = updateNotificationReducer(preparingState, {
type: 'autoError',
message: 'Preparing installation timed out. Please try again later.',
});
expect(result.state.status).toBe('error');
expect(result.state.activeTask).toBeNull();
expect(result.state.errorMsg).toBe('Preparing installation timed out. Please try again later.');
expect(result.effects).toEqual([]);
});
it('shows an error when native readiness fails after auto download completes', () => {
const downloadedState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloaded',
autoUpdateAvailable: true,
autoUpdateInfo: { version: '2.2.0' },
};
const result = updateNotificationReducer(downloadedState, {
type: 'autoError',
message: 'Preparing installation failed. Please try again later.',
});
expect(result.state.status).toBe('error');
expect(result.state.activeTask).toBeNull();
expect(result.state.errorMsg).toBe('Preparing installation failed. Please try again later.');
expect(result.effects).toEqual([]);
});
it('ignores stale manual progress from a different download id', () => {
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
activeTask: {
kind: 'manual',
id: 'current-download',
},
progress: {
percent: 58,
transferred: 58,
total: 100,
speed: '3.0 MB/s',
},
};
const result = updateNotificationReducer(downloadingState, {
type: 'manualProgress',
downloadId: 'stale-download',
status: 'downloading',
progress: {
percent: 99,
transferred: 99,
total: 100,
speed: '9.0 MB/s',
},
});
expect(result.state.progress).toEqual({
percent: 58,
transferred: 58,
total: 100,
speed: '3.0 MB/s',
});
expect(result.state.status).toBe('downloading');
expect(result.effects).toEqual([]);
});
it('keeps notification singleton capabilities platform independent', () => {
expect(updateNotificationCapabilities).toEqual({
notificationCard: true,
singletonState: true,
autoDownloadSingleFlight: true,
manualDownloadDedupe: true,
});
});
it('shows a one-shot installer failure marker without clobbering update download state', () => {
const marker = {
schemaVersion: 1,
kind: 'app-cannot-be-closed' as const,
phase: 'customCheckAppRunning' as const,
silent: true,
updated: true,
retryCount: 3,
instDir: 'D:\\AionUi',
logPath: 'C:\\Users\\me\\AppData\\Local\\Temp\\aionui-installer-2.1.27-20260702-151830-ab12cd34ef56.log',
at: '2026-07-01T00:00:00.000Z',
};
const result = updateNotificationReducer(initialUpdateNotificationState, {
type: 'installerLastFailureConsumed',
marker,
} as never);
expect(result.state.visible).toBe(true);
expect(result.state.status).toBe('installer-last-failure');
expect(result.state.installerLastFailure).toEqual(marker);
expect(result.state.activeTask).toBeNull();
expect(result.effects).toEqual([]);
const downloadingState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloading',
activeTask: { kind: 'auto', id: 'auto' },
};
const ignored = updateNotificationReducer(downloadingState, {
type: 'installerLastFailureConsumed',
marker,
} as never);
expect(ignored.state.status).toBe('downloading');
expect(ignored.state.installerLastFailure).toBeUndefined();
});
it('queues an installer failure marker during downloaded and preparing states until it can be displayed', () => {
const marker = {
schemaVersion: 1,
kind: 'app-cannot-be-closed' as const,
phase: 'customCheckAppRunning' as const,
silent: true,
updated: true,
retryCount: 3,
instDir: 'D:\\AionUi',
logPath: 'C:\\Users\\me\\AppData\\Local\\Temp\\aionui-installer-2.1.27-20260702-151830-ab12cd34ef56.log',
at: '2026-07-01T00:00:00.000Z',
};
const downloadedState: UpdateNotificationState = {
...initialUpdateNotificationState,
visible: true,
status: 'downloaded',
autoUpdateAvailable: true,
autoUpdateInfo: { version: '2.2.0' },
};
const queued = updateNotificationReducer(downloadedState, {
type: 'installerLastFailureConsumed',
marker,
} as never);
expect(queued.state.status).toBe('downloaded');
expect(queued.state.installerLastFailure).toBeUndefined();
expect(queued.state.pendingInstallerLastFailure).toEqual(marker);
const preparing = updateNotificationReducer(queued.state, {
type: 'autoPreparingInstall',
version: '2.2.0',
} as never);
expect(preparing.state.status).toBe('preparing-install');
expect(preparing.state.pendingInstallerLastFailure).toEqual(marker);
const displayed = updateNotificationReducer(preparing.state, {
type: 'autoError',
message: 'Preparing installation failed. Please try again later.',
});
expect(displayed.state.visible).toBe(true);
expect(displayed.state.status).toBe('installer-last-failure');
expect(displayed.state.installerLastFailure).toEqual(marker);
expect(displayed.state.pendingInstallerLastFailure).toBeUndefined();
});
});