Files
glchat/web/tests/desktopNotifications.test.ts
T
grendervill cffcec72b8 feat(desktop): обёртка Tauri 2 вокруг веб-клиента
Первый инкремент Фазы 6 (docs/client-tauri.md): окно с тем же веб-клиентом
(по умолчанию https://gl.mhspx.su, адрес настраивается), трей с меню и
индикатором непрочитанных, нативные уведомления, глобальный push-to-talk и
переключение микрофона/deafen, автозапуск, single-instance, сохранение
геометрии окна, deep links glchat://…, автообновление по подписанному
манифесту.

Связь страницы с обёрткой — через платформенный адаптер
(web/src/lib/platform.ts) и минимальный типизированный набор команд;
в веб-клиенте появились системные уведомления об упоминаниях и личных
сообщениях и переключатель в настройках.

В web/src/stores/gateway.ts вместе с вызовом уведомления лежат правки по
дефектам realtime (READY и сессии) из параллельной волны: файл коммитится
целиком по договорённости с координатором.
2026-09-22 21:46:01 +03:00

127 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from 'vitest';
import {
notificationFor,
notificationsEnabled,
previewText,
setNotificationsEnabled,
} from '@/lib/desktopNotifications';
import type { NotificationInput } from '@/lib/desktopNotifications';
/** Сообщение по умолчанию: обычное сообщение в канале сервера. */
function input(overrides: Partial<NotificationInput> = {}): NotificationInput {
return {
message: {
type: 'default',
content: 'привет',
mentions: [],
channel_id: 'c1',
author_id: 'u2',
},
meId: 'u1',
isDirect: false,
channelName: 'общий',
guildId: 'g1',
isChannelOpen: false,
windowFocused: false,
enabled: true,
...overrides,
};
}
describe('notificationFor', () => {
it('уведомляет об упоминании в неактивной комнате', () => {
const result = notificationFor(input({ message: { ...input().message, mentions: ['u1'] } }));
expect(result).toMatchObject({ title: 'Упоминание в #общий', body: 'привет', channelId: 'c1' });
});
it('уведомляет о личном сообщении', () => {
const result = notificationFor(input({ isDirect: true, channelName: 'Аня', guildId: null }));
expect(result).toMatchObject({ title: 'Личное сообщение — Аня', guildId: null });
});
it('молчит об обычном сообщении в канале', () => {
expect(notificationFor(input())).toBeNull();
});
it('молчит о собственных сообщениях', () => {
const result = notificationFor(
input({
isDirect: true,
message: { ...input().message, author_id: 'u1' },
}),
);
expect(result).toBeNull();
});
it('молчит, когда открытая комната в фокусе', () => {
const result = notificationFor(
input({ isDirect: true, isChannelOpen: true, windowFocused: true }),
);
expect(result).toBeNull();
});
it('уведомляет об открытой комнате, если окно не в фокусе', () => {
const result = notificationFor(
input({ isDirect: true, isChannelOpen: true, windowFocused: false }),
);
expect(result).not.toBeNull();
});
it('молчит при выключенных уведомлениях', () => {
expect(notificationFor(input({ isDirect: true, enabled: false }))).toBeNull();
});
it('пропускает системные сообщения', () => {
const result = notificationFor(
input({
isDirect: true,
message: { ...input().message, type: 'system' },
}),
);
expect(result).toBeNull();
});
it('подставляет «Вложение» для сообщения без текста', () => {
const result = notificationFor(
input({
isDirect: true,
message: { ...input().message, content: '', mentions: ['u1'] },
}),
);
expect(result?.body).toBe('Вложение');
});
});
describe('previewText', () => {
it('убирает разметку и лишние переводы строк', () => {
expect(previewText('**жирный**\n\n[ссылка](https://example.com) `код`')).toBe(
'жирный ссылка код',
);
});
it('обрезает длинный текст', () => {
const result = previewText('а'.repeat(400));
expect(result.length).toBeLessThanOrEqual(140);
expect(result.endsWith('…')).toBe(true);
});
});
describe('предпочтение уведомлений', () => {
it('включено по умолчанию и выключается переключателем', () => {
expect(notificationsEnabled()).toBe(true);
setNotificationsEnabled(false);
expect(notificationsEnabled()).toBe(false);
setNotificationsEnabled(true);
expect(notificationsEnabled()).toBe(true);
});
it('не падает, если localStorage недоступен', () => {
const stub = vi.spyOn(window, 'localStorage', 'get').mockImplementation(() => {
throw new Error('нет доступа');
});
expect(notificationsEnabled()).toBe(true);
stub.mockRestore();
});
});