Files
glchat/web/tests/serviceWorker.test.ts
T
grendervill 5afcfee744 feat(ops): чистка данных, reindex и PWA с офлайн-режимом
- internal/retention: истёкшие сессии, аудит старше RETENTION_AUDIT_DAYS и
  файлы без ссылок старше RETENTION_ORPHAN_HOURS; сиротой файл считается
  только если на него не ссылается ничего (вложения, аватары, оформление
  сервера, комнат, ролей, эмодзи, звуки, вебхуки, приглашения)
- обслуживание запускается в приложении по RETENTION_INTERVAL_SECONDS;
  команды glchat cleanup и glchat reindex делают то же вручную
- PWA: манифест с иконками и shortcuts, офлайн-страница, сервис-воркер
  (кэш только /assets/*, приватные запросы не кэшируются)
- тесты: Go на чистку (сирота удаляется, используемые файлы и свежие сироты
  остаются, истёкшая сессия уходит), Vitest на манифест и регистрацию воркера
2026-09-21 19:42:12 +03:00

43 lines
1.6 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { registerServiceWorker } from '@/lib/serviceWorker';
/**
* PWA (Фаза 5): сервис-воркер регистрируется только в production-сборке и
* только там, где браузер его поддерживает.
*/
afterEach(() => {
vi.unstubAllGlobals();
});
describe('регистрация сервис-воркера', () => {
it('в тестовом окружении (не production) ничего не делает', () => {
const register = vi.fn().mockResolvedValue(undefined);
vi.stubGlobal('navigator', { serviceWorker: { register } });
expect(registerServiceWorker()).toBe(false);
expect(register).not.toHaveBeenCalled();
});
});
describe('манифест PWA', () => {
it('содержит обязательные поля и иконки нужных размеров', async () => {
const manifest = await import('../public/manifest.webmanifest?raw');
const parsed = JSON.parse(manifest.default) as {
name: string;
start_url: string;
display: string;
icons: { sizes: string; type: string; purpose?: string }[];
};
expect(parsed.name).toBe('glchat');
expect(parsed.start_url).toBe('/app');
expect(parsed.display).toBe('standalone');
const sizes = parsed.icons.map((icon) => icon.sizes);
expect(sizes).toContain('192x192');
expect(sizes).toContain('512x512');
expect(parsed.icons.every((icon) => icon.type === 'image/png')).toBe(true);
expect(parsed.icons.some((icon) => icon.purpose === 'maskable')).toBe(true);
});
});