mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
123 lines
6.4 KiB
TypeScript
123 lines
6.4 KiB
TypeScript
/**
|
|
* SystemNoticesService — the DI wrapper over the plain systemNotices service.
|
|
* What's under test is the threading added when conditions.ts lost its
|
|
* addons.bridge import: getActiveFor must hand the evaluator a live
|
|
* addonEnabled callback over the INJECTED AddonsService. The upstream plain
|
|
* service is mocked; its own behavior is pinned in tests/unit/systemNotices/
|
|
* and the integration suite.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const { mockGetActive, mockDismiss, mockAppVersion } = vi.hoisted(() => ({ mockGetActive: vi.fn(), mockDismiss: vi.fn(), mockAppVersion: vi.fn(() => '4.3.0') }));
|
|
vi.mock('../../../src/systemNotices/service', () => ({
|
|
getActiveNoticesFor: mockGetActive,
|
|
dismissNotice: mockDismiss,
|
|
getCurrentAppVersion: mockAppVersion,
|
|
}));
|
|
|
|
import { SystemNoticesService } from '../../../src/nest/system-notices/system-notices.service';
|
|
import type { AddonsService } from '../../../src/nest/addons/addons.service';
|
|
import type { RuntimeEnvService } from '../../../src/nest/app-config/runtime-env.service';
|
|
|
|
const isAddonEnabled = vi.fn((id: string) => id === 'journey');
|
|
const svc = new SystemNoticesService({ isAddonEnabled } as unknown as AddonsService, { isManaged: () => false } as unknown as RuntimeEnvService);
|
|
|
|
beforeEach(() => {
|
|
mockGetActive.mockReset();
|
|
mockDismiss.mockReset();
|
|
isAddonEnabled.mockClear();
|
|
});
|
|
|
|
describe('SystemNoticesService', () => {
|
|
it('getActiveFor threads a live addonEnabled check over the injected AddonsService', async () => {
|
|
mockGetActive.mockReturnValue([]);
|
|
await svc.getActiveFor(7);
|
|
expect(mockGetActive).toHaveBeenCalledWith(7, expect.any(Function), false);
|
|
const addonEnabled = mockGetActive.mock.calls[0][1] as (id: string) => boolean;
|
|
expect(addonEnabled('journey')).toBe(true);
|
|
expect(addonEnabled('vacay')).toBe(false);
|
|
expect(isAddonEnabled).toHaveBeenLastCalledWith('vacay');
|
|
});
|
|
|
|
it('dismiss passes through to the upstream service', async () => {
|
|
mockDismiss.mockReturnValue(true);
|
|
expect(await svc.dismiss(7, 'welcome')).toBe(true);
|
|
expect(mockDismiss).toHaveBeenCalledWith(7, 'welcome');
|
|
});
|
|
|
|
// After an update the service worker keeps serving the previous bundle for a while.
|
|
// That bundle would draw a release notice as bare keys and let the reader dismiss it
|
|
// for good, so the release layout is only delivered to a client that says it can
|
|
// draw it. Everything without a release block goes out as before.
|
|
describe('getActiveFor holds the release layout back from a client that does not announce it', () => {
|
|
const generic = {
|
|
id: 'outage', display: 'banner', severity: 'warn',
|
|
titleKey: 'system_notice.outage.title', bodyKey: 'system_notice.outage.body', dismissible: true,
|
|
};
|
|
const release = {
|
|
id: 'release-notes', display: 'modal', severity: 'info',
|
|
titleKey: 'system_notice.release_notes.headline', bodyKey: 'system_notice.release_notes.intro',
|
|
dismissible: true,
|
|
release: { version: '4.3.0', headlineKey: 'system_notice.release_notes.headline' },
|
|
};
|
|
|
|
it('drops a notice with a release block when nothing is announced', async () => {
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7)).map(n => n.id)).toEqual(['outage']);
|
|
});
|
|
|
|
it('drops it when other layouts are announced but not release', async () => {
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7, new Set(['banner']))).map(n => n.id)).toEqual(['outage']);
|
|
});
|
|
|
|
it('delivers it, in place, once the client announces the release layout for the running version', async () => {
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), '4.3.0')).map(n => n.id)).toEqual(['release-notes', 'outage']);
|
|
});
|
|
|
|
it('drops it for a bundle that announces the layout but was built for another version', async () => {
|
|
// The shell the service worker serves right after an update: it can draw the
|
|
// layout, but with the texts of the version it was built for, and its X would
|
|
// use the notice up for the version now running.
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), '4.2.1')).map(n => n.id)).toEqual(['outage']);
|
|
mockAppVersion.mockReturnValueOnce('4.3.1');
|
|
expect((await svc.getActiveFor(7, new Set(['release']), '4.3.0')).map(n => n.id)).toEqual(['outage']);
|
|
});
|
|
|
|
it('drops it for a bundle that names no version at all', async () => {
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7, new Set(['release']))).map(n => n.id)).toEqual(['outage']);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), '')).map(n => n.id)).toEqual(['outage']);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), 'dev')).map(n => n.id)).toEqual(['outage']);
|
|
});
|
|
|
|
it('reads the version loosely, as the server reads its own', async () => {
|
|
mockGetActive.mockReturnValue([release, generic]);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), 'v4.3.0')).map(n => n.id)).toEqual(['release-notes', 'outage']);
|
|
});
|
|
|
|
it('always delivers a notice without a release block', async () => {
|
|
mockGetActive.mockReturnValue([generic]);
|
|
expect((await svc.getActiveFor(7)).map(n => n.id)).toEqual(['outage']);
|
|
expect((await svc.getActiveFor(7, new Set(['release']), '4.3.0')).map(n => n.id)).toEqual(['outage']);
|
|
});
|
|
});
|
|
|
|
it('still loads and works when the AddonsService binding is unresolved (import-cycle fallback)', async () => {
|
|
// The emitted design:paramtypes metadata guards an unresolved class binding
|
|
// with `typeof AddonsService === 'undefined' ? Object : AddonsService`.
|
|
// Simulate that mid-cycle state: the module must still evaluate and the
|
|
// instance must still thread the callback.
|
|
vi.resetModules();
|
|
vi.doMock('../../../src/nest/addons/addons.service', () => ({ AddonsService: undefined }));
|
|
const { SystemNoticesService: Reloaded } = await import('../../../src/nest/system-notices/system-notices.service');
|
|
const inst = new Reloaded({ isAddonEnabled } as unknown as AddonsService, { isManaged: () => false } as unknown as RuntimeEnvService);
|
|
mockGetActive.mockReturnValue([]);
|
|
await inst.getActiveFor(1);
|
|
expect(mockGetActive).toHaveBeenCalledWith(1, expect.any(Function), false);
|
|
vi.doUnmock('../../../src/nest/addons/addons.service');
|
|
});
|
|
});
|