feat(ops): чистка данных, reindex и PWA с офлайн-режимом
- internal/retention: истёкшие сессии, аудит старше RETENTION_AUDIT_DAYS и файлы без ссылок старше RETENTION_ORPHAN_HOURS; сиротой файл считается только если на него не ссылается ничего (вложения, аватары, оформление сервера, комнат, ролей, эмодзи, звуки, вебхуки, приглашения) - обслуживание запускается в приложении по RETENTION_INTERVAL_SECONDS; команды glchat cleanup и glchat reindex делают то же вручную - PWA: манифест с иконками и shortcuts, офлайн-страница, сервис-воркер (кэш только /assets/*, приватные запросы не кэшируются) - тесты: Go на чистку (сирота удаляется, используемые файлы и свежие сироты остаются, истёкшая сессия уходит), Vitest на манифест и регистрацию воркера
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user