mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
181 lines
6.7 KiB
TypeScript
181 lines
6.7 KiB
TypeScript
/**
|
|
* @fileoverview Pure compatibility translation between legacy session order and owner tab layouts.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
applyLegacySessionRank,
|
|
recomposeGlobalSessionOrder,
|
|
type OwnerOrderProjection,
|
|
} from '../src/tab-layout-legacy-order.js';
|
|
import { normalizeTabLayout, validateTabLayout, type TabLayout, type TabRefMetadata } from '../src/tab-layout.js';
|
|
|
|
const session = (id: string, placement?: 'manual') =>
|
|
placement ? { kind: 'session' as const, id, placement } : { kind: 'session' as const, id };
|
|
const webview = (id: string) => ({ kind: 'webview' as const, id });
|
|
const metadata = (id: string, overrides: Partial<TabRefMetadata> = {}): TabRefMetadata => ({
|
|
kind: 'session',
|
|
id,
|
|
ownerValid: true,
|
|
visible: true,
|
|
order: 0,
|
|
...overrides,
|
|
});
|
|
const layout = (overrides: Partial<TabLayout> = {}): TabLayout => ({
|
|
version: 1,
|
|
groups: [],
|
|
ungrouped: [],
|
|
updatedAt: '2026-08-23T12:00:00.000Z',
|
|
...overrides,
|
|
});
|
|
|
|
describe('applyLegacySessionRank', () => {
|
|
it('ranks only authoritative sessions while anchoring webviews and refs absent from metadata', () => {
|
|
const input = layout({
|
|
ungrouped: [session('a'), webview('w1'), session('unknown'), session('b')],
|
|
});
|
|
|
|
expect(applyLegacySessionRank(input, ['b', 'a'], [metadata('a'), metadata('b')])).toEqual(
|
|
layout({
|
|
ungrouped: [session('b'), webview('w1'), session('unknown'), session('a')],
|
|
})
|
|
);
|
|
});
|
|
|
|
it('ranks sessions only inside their current containers without changing groups or webview slots', () => {
|
|
const input = layout({
|
|
groups: [
|
|
{ id: 'first', name: 'First', refs: [session('a'), webview('w1'), session('d')] },
|
|
{ id: 'second', name: 'Second', refs: [session('b'), webview('w2'), session('c')] },
|
|
],
|
|
ungrouped: [session('e'), webview('w3'), session('f')],
|
|
});
|
|
const facts = ['a', 'b', 'c', 'd', 'e', 'f'].map((id, order) => metadata(id, { order }));
|
|
|
|
const result = applyLegacySessionRank(input, ['f', 'c', 'd', 'e', 'b', 'a'], facts);
|
|
|
|
expect(result.groups).toEqual([
|
|
{ id: 'first', name: 'First', refs: [session('d'), webview('w1'), session('a')] },
|
|
{ id: 'second', name: 'Second', refs: [session('c'), webview('w2'), session('b')] },
|
|
]);
|
|
expect(result.ungrouped).toEqual([session('f'), webview('w3'), session('e')]);
|
|
});
|
|
|
|
it('materializes ranked children with represented owner-valid parents before normalization', () => {
|
|
const input = layout({
|
|
groups: [
|
|
{
|
|
id: 'family',
|
|
name: 'Family',
|
|
refs: [session('parent'), session('child'), session('other')],
|
|
},
|
|
],
|
|
});
|
|
const facts = [
|
|
metadata('parent', { order: 0 }),
|
|
metadata('child', { order: 1, parentSessionId: 'parent' }),
|
|
metadata('other', { order: 2 }),
|
|
];
|
|
|
|
const result = applyLegacySessionRank(input, ['child', 'other', 'parent'], facts);
|
|
|
|
expect(result.groups[0].refs).toEqual([session('child', 'manual'), session('other'), session('parent')]);
|
|
expect(normalizeTabLayout(result, facts)).toEqual(result);
|
|
});
|
|
|
|
it('keeps a ranked child manual in its group when normalization materializes its missing parent', () => {
|
|
const input = layout({
|
|
groups: [{ id: 'child-group', name: 'Child', refs: [session('child')] }],
|
|
});
|
|
const facts = [metadata('parent', { order: 0 }), metadata('child', { order: 1, parentSessionId: 'parent' })];
|
|
|
|
const result = applyLegacySessionRank(input, ['child', 'parent'], facts);
|
|
|
|
expect(result.groups[0].refs).toEqual([session('child', 'manual')]);
|
|
expect(result.ungrouped).toEqual([session('parent')]);
|
|
expect(normalizeTabLayout(result, facts)).toEqual(result);
|
|
});
|
|
|
|
it('does not mutate inputs and returns a validated canonical deep clone', () => {
|
|
const input = layout({
|
|
groups: [{ id: 'g', name: ' Work ', refs: [session('a'), webview('w')] }],
|
|
ungrouped: [session('b')],
|
|
});
|
|
const requested = ['b', 'a'];
|
|
const facts = [metadata('a', { order: 0 }), metadata('b', { order: 1 })];
|
|
const beforeInput = structuredClone(input);
|
|
const beforeRequested = [...requested];
|
|
const beforeFacts = structuredClone(facts);
|
|
|
|
const result = applyLegacySessionRank(input, requested, facts);
|
|
|
|
expect(input).toEqual(beforeInput);
|
|
expect(requested).toEqual(beforeRequested);
|
|
expect(facts).toEqual(beforeFacts);
|
|
expect(result.groups[0].name).toBe('Work');
|
|
expect(validateTabLayout(result)).toEqual(result);
|
|
expect(result).not.toBe(input);
|
|
expect(result.groups[0]).not.toBe(input.groups[0]);
|
|
expect(result.groups[0].refs[0]).not.toBe(input.groups[0].refs[0]);
|
|
});
|
|
});
|
|
|
|
describe('recomposeGlobalSessionOrder', () => {
|
|
it('replaces only owner slots, preserves interleaving, appends new IDs once, and deduplicates', () => {
|
|
const current = ['a1', 'foreign', 'b1', 'a2', 'unmapped', 'a1'];
|
|
const projections: OwnerOrderProjection[] = [
|
|
{
|
|
owner: 'alice',
|
|
ownedIds: ['a1', 'a2', 'a3', 'a3'],
|
|
order: ['a3', 'a2', 'a2', 'a1', 'foreign'],
|
|
},
|
|
];
|
|
const beforeCurrent = [...current];
|
|
const beforeProjections = structuredClone(projections);
|
|
|
|
const result = recomposeGlobalSessionOrder(current, projections);
|
|
|
|
expect(result).toEqual(['a3', 'foreign', 'b1', 'a2', 'unmapped', 'a1']);
|
|
expect(current).toEqual(beforeCurrent);
|
|
expect(projections).toEqual(beforeProjections);
|
|
});
|
|
|
|
it('starts from merged preferred order when preserving admin cross-owner intent', () => {
|
|
expect(
|
|
recomposeGlobalSessionOrder(
|
|
['a1', 'b1', 'a2', 'server-only'],
|
|
[{ owner: 'alice', ownedIds: ['a1', 'a2'], order: ['a2', 'a1'] }],
|
|
['b1', 'a1', 'a2']
|
|
)
|
|
).toEqual(['b1', 'a2', 'a1', 'server-only']);
|
|
});
|
|
|
|
it('derives sequential owner projections from the latest order without a lost update', () => {
|
|
const initial = ['a1', 'b1', 'a2', 'b2'];
|
|
const afterAlice = recomposeGlobalSessionOrder(initial, [
|
|
{ owner: 'alice', ownedIds: ['a1', 'a2'], order: ['a2', 'a1'] },
|
|
]);
|
|
const afterBob = recomposeGlobalSessionOrder(afterAlice, [
|
|
{ owner: 'bob', ownedIds: ['b1', 'b2'], order: ['b2', 'b1'] },
|
|
]);
|
|
|
|
expect(afterAlice).toEqual(['a2', 'b1', 'a1', 'b2']);
|
|
expect(afterBob).toEqual(['a2', 'b2', 'a1', 'b1']);
|
|
});
|
|
|
|
it('handles prototype-like owner names and IDs without corrupting membership', () => {
|
|
expect(
|
|
recomposeGlobalSessionOrder(
|
|
['__proto__', 'foreign', 'constructor'],
|
|
[
|
|
{
|
|
owner: 'constructor',
|
|
ownedIds: ['__proto__', 'constructor', 'toString'],
|
|
order: ['toString', 'constructor', '__proto__'],
|
|
},
|
|
]
|
|
)
|
|
).toEqual(['toString', 'foreign', 'constructor', '__proto__']);
|
|
});
|
|
});
|