mirror of
https://github.com/hectorqin/reader.git
synced 2026-11-11 05:20:54 +08:00
63 lines
2.7 KiB
TypeScript
63 lines
2.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { ReaderApi } from '../src/api/client.ts';
|
|
import type { Session } from '../src/api/types.ts';
|
|
import { FakeTransport, makePlatform } from './helpers/env.ts';
|
|
|
|
const session: Session = {
|
|
user: { id: 'reader', username: 'reader', displayName: 'Reader', role: 'member', createdAt: 0 },
|
|
accessToken: 'access', refreshToken: 'refresh', accessTokenExpiresAt: 9e15, refreshTokenExpiresAt: 9e15,
|
|
};
|
|
|
|
async function apiWith(transport: FakeTransport): Promise<ReaderApi> {
|
|
const api = new ReaderApi(makePlatform(transport), { load: async () => session, save: async () => {}, clear: async () => {} });
|
|
await api.restore();
|
|
return api;
|
|
}
|
|
|
|
describe('ReaderApi navigation query cache', () => {
|
|
it('refreshes explicitly, separates pages, and leaves polling uncached', async () => {
|
|
const transport = new FakeTransport();
|
|
transport.json({ items: [], total: 0 });
|
|
const api = await apiWith(transport);
|
|
await api.listBooks({ page: 1 });
|
|
await api.listBooks({ page: 2 });
|
|
api.refreshQueries();
|
|
await api.listBooks({ page: 1 });
|
|
expect(transport.requests).toHaveLength(3);
|
|
await api.mediaRequest('/api/v1/media/scan-jobs');
|
|
await api.mediaRequest('/api/v1/media/scan-jobs');
|
|
expect(transport.requests).toHaveLength(5);
|
|
});
|
|
|
|
it('keeps media catalog cached across playback heartbeats', async () => {
|
|
const transport = new FakeTransport();
|
|
transport.json({ items: [] });
|
|
const api = await apiWith(transport);
|
|
await api.mediaRequest('/api/v1/media/browse?channel=music');
|
|
await api.mediaRequest('/api/v1/media/playback/session/progress', 'PUT', { position: 10 });
|
|
await api.mediaRequest('/api/v1/media/browse?channel=music');
|
|
expect(transport.requests).toHaveLength(2);
|
|
});
|
|
|
|
it('deduplicates concurrent shelf reads and reuses them on return', async () => {
|
|
const transport = new FakeTransport();
|
|
transport.json({ items: [{ id: 'book-1' }], total: 1 });
|
|
const api = await apiWith(transport);
|
|
await Promise.all([api.listBooks(), api.listBooks()]);
|
|
await api.listBooks();
|
|
expect(transport.countMatching(request => request.url.startsWith('/api/v1/books'))).toBe(1);
|
|
});
|
|
|
|
it('invalidates navigation data after a write', async () => {
|
|
const transport = new FakeTransport();
|
|
transport.respondWith(request => request.method === 'GET'
|
|
? { status: 200, headers: {}, json: { items: [{ id: 'book-1' }], total: 1 } }
|
|
: { status: 200, headers: {}, json: {} });
|
|
const api = await apiWith(transport);
|
|
await api.listBooks();
|
|
await api.browseBatchShelf({ bookIds: ['book-1'] }, 'remove');
|
|
await api.listBooks();
|
|
expect(transport.countMatching(request => request.url.startsWith('/api/v1/books'))).toBe(2);
|
|
});
|
|
});
|