import { describe, expect, it } from 'vitest'; import type { TilingNode } from '../../tiling/model'; import type { OpenDocRequest } from '../workspace'; import type { TileTabsMap } from './tabsModel'; import { buildInitialTileTabs, maxIdSuffix, serializeLayout, } from './tilingPersistence'; function req(entryId: string): OpenDocRequest { return { providerId: 'fs', entryId, title: entryId }; } function splitRow( id: string, first: TilingNode, second: TilingNode, ): TilingNode { return { kind: 'split', id, orientation: 'row', ratio: 0.5, first, second }; } describe('serializeLayout', () => { it('captures each tile’s documents and active index, skipping empty tabs', () => { const tree = splitRow( 'split-1', { kind: 'tile', id: 'tile-1' }, { kind: 'tile', id: 'tile-2' }, ); const tileTabs: TileTabsMap = { 'tile-1': { tabs: [ { tabId: 'tab-a', slot: { kind: 'loading', request: req('a') }, mode: 'edit', }, { tabId: 'tab-b', slot: { kind: 'loading', request: req('b') }, mode: 'view', }, ], activeTabId: 'tab-b', }, 'tile-2': { tabs: [{ tabId: 'tab-empty', slot: { kind: 'empty' }, mode: 'edit' }], activeTabId: 'tab-empty', }, }; const layout = serializeLayout(tree, 'tile-2', tileTabs); expect(layout.version).toBe(2); expect(layout.activeTileId).toBe('tile-2'); expect(layout.tree).toEqual(tree); // Per-tab mode is persisted alongside each document request. expect(layout.tiles).toEqual([ { tileId: 'tile-1', tabs: [ { request: req('a'), mode: 'edit' }, { request: req('b'), mode: 'view' }, ], activeIndex: 1, }, { tileId: 'tile-2', tabs: [], activeIndex: -1 }, ]); }); }); describe('buildInitialTileTabs', () => { it('falls back to a single empty tile when there is nothing persisted', () => { const result = buildInitialTileTabs(null, 'tile-1'); expect(result.pending).toEqual([]); expect(result.map['tile-1'].tabs).toHaveLength(1); expect(result.map['tile-1'].tabs[0].slot).toEqual({ kind: 'empty' }); }); it('builds loading tabs and pending opens from persisted tiles', () => { const result = buildInitialTileTabs( [ { tileId: 'tile-1', tabs: [ { request: req('a'), mode: 'edit' }, { request: req('b'), mode: 'view' }, ], activeIndex: 1, }, { tileId: 'tile-2', tabs: [], activeIndex: -1 }, ], 'tile-1', ); // Two documents to open, all on tile-1. expect(result.pending.map((p) => p.request)).toEqual([req('a'), req('b')]); expect(result.pending.every((p) => p.tileId === 'tile-1')).toBe(true); const tile1 = result.map['tile-1']; expect(tile1.tabs.map((t) => t.slot.kind)).toEqual(['loading', 'loading']); // Persisted per-tab mode is restored. expect(tile1.tabs.map((t) => t.mode)).toEqual(['edit', 'view']); // active index 1 -> second tab active. expect(tile1.activeTabId).toBe(tile1.tabs[1].tabId); // Empty persisted tile becomes a fresh empty tile. expect(result.map['tile-2'].tabs[0].slot).toEqual({ kind: 'empty' }); // Counter advanced past every minted id (3 tabs total). expect(result.nextTabCounter).toBe(4); }); it('upgrades a v1 persisted tile (bare requests) to edit mode', () => { // v1 stored tabs as bare OpenDocRequests, with no mode field. const v1Tiles = [ { tileId: 'tile-1', tabs: [req('a')], activeIndex: 0 }, ] as unknown as Parameters[0]; const result = buildInitialTileTabs(v1Tiles, 'tile-1'); expect(result.map['tile-1'].tabs.map((t) => t.mode)).toEqual(['edit']); expect(result.pending.map((p) => p.request)).toEqual([req('a')]); }); it('clamps an out-of-range active index', () => { const result = buildInitialTileTabs( [ { tileId: 'tile-1', tabs: [{ request: req('a'), mode: 'edit' }], activeIndex: 9, }, ], 'tile-1', ); expect(result.map['tile-1'].activeTabId).toBe( result.map['tile-1'].tabs[0].tabId, ); }); }); describe('maxIdSuffix', () => { it('finds the largest numeric suffix across tile and split ids', () => { const tree = splitRow( 'split-7', { kind: 'tile', id: 'tile-2' }, splitRow( 'split-3', { kind: 'tile', id: 'tile-9' }, { kind: 'tile', id: 'tile-4' }, ), ); expect(maxIdSuffix(tree)).toBe(9); }); it('returns the suffix of a lone tile', () => { expect(maxIdSuffix({ kind: 'tile', id: 'tile-1' })).toBe(1); }); });