feat(social): add listening activity, shared rooms and modern chat
ci / api / api (push) Canceled after 0s
ci / module boundaries / boundaries (push) Canceled after 0s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Canceled after 11m29s
ci / db / db (push) Canceled after 11m22s
ci / mobile / mobile (Metro-бандл Android) (push) Canceled after 6m43s
ci / recommender / recommender (push) Canceled after 6m47s
ci / ts / ts (packages + web) (push) Canceled after 1s

This commit is contained in:
mrleo1nid committed 2026-10-23 10:45:40 +03:00
1 parent 0a05bfe2cd
commit 11a16cd023
59 files changed
+5022 -542

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { ActivityPublisher } from './activity';
const track = { id: '00000000-0000-4000-8000-000000000001' };
const device = '00000000-0000-4000-8000-000000000002';
describe('listening activity leases', () => {
it('publishes actual playback, throttles progress, and clears on pause', async () => {
let now = 1000; const sent: unknown[] = [];
const publisher = new ActivityPublisher(async value => { sent.push(value); }, device, () => now);
publisher.update({ track, playing: false, context: null }); await publisher.idle(); expect(sent).toEqual([]);
publisher.update({ track, playing: true, context: { type: 'wave' }, positionMs: 123 }); await publisher.idle();
expect(sent[0]).toMatchObject({ context: 'wave', track_id: track.id, sequence: 1, position_ms: 123 });
publisher.update({ track, playing: true, context: { type: 'wave' }, positionMs: 900 }); await publisher.idle(); expect(sent).toHaveLength(1);
now += 20_001; publisher.update({ track, playing: true, context: { type: 'wave' }, positionMs: 20123 }); await publisher.idle(); expect(sent).toHaveLength(2);
publisher.update({ track, playing: false, context: null }); await publisher.idle(); expect(sent.at(-1)).toMatchObject({ playing: false, sequence: 3 });
publisher.dispose(); await publisher.idle();
});
it('serializes an in-flight play and a final stop without replaying intermediate tracks', async () => {
let release!: () => void; const waiting = new Promise<void>(r => { release = r; }); const sent: { playing: boolean; sequence: number }[] = [];
const publisher = new ActivityPublisher(async value => { sent.push(value); if (sent.length === 1) await waiting; }, device);
publisher.update({ track, playing: true, context: null });
publisher.update({ track: { id: device }, playing: true, context: null });
publisher.dispose(); release(); await publisher.idle();
expect(sent.map(v => [v.playing, v.sequence])).toEqual([[true, 1], [false, 2]]);
});
it('only treats UUID collection contexts as playlists', async () => {
const sent: { context: string }[] = []; const publisher = new ActivityPublisher(async v => { sent.push(v); }, device);
publisher.update({ track, playing: true, context: { type: 'collection', id: 'likes' } }); await publisher.idle(); expect(sent[0]?.context).toBe('other');
publisher.update({ track, playing: true, context: { type: 'collection', id: device } }); await publisher.idle(); expect(sent[1]?.context).toBe('playlist');
publisher.dispose(); await publisher.idle();
});
});