Files
tessera-ctl/apps/web/src/lib/stores/custom-module-cache-store.test.ts
T
schalli ac104495b4 feat(quick-261008-who): eigene Module beim Start vorladen — Durchstich
- Spalte User.customModulePreloadIds, PUT /custom-modules/:id/preload (Grenze 8, 404 fuer Fremdes)
- Vorlade-Planer und -Komponente, Speicher preload()/pinned, Ersatzgroesse im Behaelter
- E2E-Skript gegen die API

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 09:14:45 +02:00

137 lines
5.4 KiB
TypeScript

import { CUSTOM_MODULE_PRELOAD_MAX } from '@tessera/shared';
import { beforeEach, describe, expect, it } from 'vitest';
import {
CUSTOM_MODULE_KEEP_ALIVE_MAX,
touchLru,
useCustomModuleCacheStore,
} from './custom-module-cache-store';
describe('touchLru', () => {
it('setzt ein neues Modul nach vorn ohne zu verwerfen', () => {
expect(touchLru(['a', 'b'], 'c', 5)).toEqual({ recent: ['c', 'a', 'b'], evicted: [] });
});
it('holt ein vorhandenes Modul nach vorn, ohne es zu verdoppeln', () => {
expect(touchLru(['a', 'b', 'c'], 'c', 5)).toEqual({ recent: ['c', 'a', 'b'], evicted: [] });
});
it('verwirft ueber der Grenze das am laengsten unbenutzte', () => {
expect(touchLru(['e', 'd', 'c', 'b', 'a'], 'f', 5)).toEqual({
recent: ['f', 'e', 'd', 'c', 'b'],
evicted: ['a'],
});
});
it('haelt das gerade benutzte Modul auch bei unsinniger Grenze', () => {
expect(touchLru(['a'], 'b', 0)).toEqual({ recent: ['b'], evicted: ['a'] });
});
});
describe('useCustomModuleCacheStore', () => {
const slot = () => document.createElement('div');
const remember = (id: string) =>
useCustomModuleCacheStore.getState().remember({ id, name: id, url: `https://x.test/${id}` });
beforeEach(() => {
useCustomModuleCacheStore.getState().clear();
});
it('haelt die Einhaengereihenfolge stabil und verwirft nach LRU', () => {
const ids = Array.from({ length: CUSTOM_MODULE_KEEP_ALIVE_MAX + 1 }, (_, i) => `m${i}`);
for (const id of ids) remember(id);
const { activate } = useCustomModuleCacheStore.getState();
for (const id of ids.slice(0, CUSTOM_MODULE_KEEP_ALIVE_MAX)) activate(id, slot());
// m0 erneut benutzen -> m1 ist jetzt am laengsten unbenutzt.
activate('m0', slot());
expect(useCustomModuleCacheStore.getState().openIds).toEqual(['m0', 'm1', 'm2', 'm3', 'm4']);
activate('m5', slot());
const state = useCustomModuleCacheStore.getState();
expect(state.openIds).toEqual(['m0', 'm2', 'm3', 'm4', 'm5']);
expect(state.recent[0]).toBe('m5');
// Metadaten bleiben — nur der offene Rahmen wird verworfen.
expect(state.modules.m1).toBeDefined();
});
it('aktiviert kein Modul ohne Metadaten', () => {
useCustomModuleCacheStore.getState().activate('unbekannt', slot());
expect(useCustomModuleCacheStore.getState().openIds).toEqual([]);
expect(useCustomModuleCacheStore.getState().active).toBeNull();
});
it('deaktiviert nur den eigenen Platzhalter', () => {
remember('a');
const first = slot();
const { activate, deactivate } = useCustomModuleCacheStore.getState();
activate('a', first);
deactivate(slot());
expect(useCustomModuleCacheStore.getState().active?.slot).toBe(first);
deactivate(first);
expect(useCustomModuleCacheStore.getState().active).toBeNull();
expect(useCustomModuleCacheStore.getState().openIds).toEqual(['a']);
});
it('vergisst ein Modul samt offenem Rahmen', () => {
remember('a');
useCustomModuleCacheStore.getState().activate('a', slot());
useCustomModuleCacheStore.getState().forget('a');
const state = useCustomModuleCacheStore.getState();
expect(state.modules).toEqual({});
expect(state.openIds).toEqual([]);
expect(state.recent).toEqual([]);
expect(state.active).toBeNull();
});
});
describe('useCustomModuleCacheStore — preload (quick-261008-who)', () => {
const mod = (id: string) => ({ id, name: id, url: `https://x.test/${id}` });
beforeEach(() => {
useCustomModuleCacheStore.getState().clear();
});
it('merkt Metadaten, haengt hinten an openIds, pinnt und laesst active unberuehrt', () => {
useCustomModuleCacheStore.getState().preload(mod('a'));
useCustomModuleCacheStore.getState().preload(mod('b'));
const s = useCustomModuleCacheStore.getState();
expect(s.modules.a).toEqual(mod('a'));
expect(s.openIds).toEqual(['a', 'b']);
expect(s.pinned).toEqual(['a', 'b']);
expect(s.active).toBeNull();
});
it('zweimal aufgerufen aendert nichts', () => {
useCustomModuleCacheStore.getState().preload(mod('a'));
const before = useCustomModuleCacheStore.getState();
useCustomModuleCacheStore.getState().preload(mod('a'));
expect(useCustomModuleCacheStore.getState()).toBe(before);
});
it('ignoriert mehr als CUSTOM_MODULE_PRELOAD_MAX gepinnte', () => {
for (let i = 0; i < CUSTOM_MODULE_PRELOAD_MAX + 2; i++) {
useCustomModuleCacheStore.getState().preload(mod(`m${i}`));
}
const s = useCustomModuleCacheStore.getState();
expect(s.pinned).toHaveLength(CUSTOM_MODULE_PRELOAD_MAX);
expect(s.openIds).toHaveLength(CUSTOM_MODULE_PRELOAD_MAX);
});
it('forget und clear entfernen auch aus pinned', () => {
useCustomModuleCacheStore.getState().preload(mod('a'));
useCustomModuleCacheStore.getState().preload(mod('b'));
useCustomModuleCacheStore.getState().forget('a');
expect(useCustomModuleCacheStore.getState().pinned).toEqual(['b']);
useCustomModuleCacheStore.getState().clear();
expect(useCustomModuleCacheStore.getState().pinned).toEqual([]);
});
it('activate eines gepinnten Moduls laesst es gepinnt', () => {
useCustomModuleCacheStore.getState().preload(mod('a'));
useCustomModuleCacheStore.getState().activate('a', document.createElement('div'));
const s = useCustomModuleCacheStore.getState();
expect(s.pinned).toEqual(['a']);
expect(s.openIds).toEqual(['a']);
expect(s.active?.id).toBe('a');
});
});