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'); }); });