waybetter-for-x/apps/desktop/tests/workspaces.test.ts
2026-10-01 06:01:13 +07:00

99 lines
6.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
EMPTY_WORKSPACES, WORKSPACE_LIMITS, captureWorkspace, normalizeWorkspaces, planRestore, removeWorkspace, renameWorkspace,
setDefaultWorkspace, upsertWorkspace, workspaceId, workspaceTabs,
} from '../../../waybetter/packages/shell/src/frame/workspaceModel.ts';
import '../src/i18n/index.ts';
/** A dockview `toJSON()` as the shell produces it: two groups side by side, a deck tab, live state. */
const live = () => ({
grid: {
root: { type: 'branch', data: [
{ type: 'leaf', data: { views: ['ta', 'tb', 'deck-d1'], activeView: 'tb', id: '1' }, size: 700 },
{ type: 'leaf', data: { views: ['better-twttr', 'ts'], activeView: 'better-twttr', id: '2' }, size: 500 },
], size: 900 },
width: 1200, height: 900, orientation: 'HORIZONTAL',
},
panels: {
ta: { id: 'ta', contentComponent: 'web', title: 'Home / X', params: { kind: 'web', url: 'https://x.com/home', profile: 'default', loading: true, favicon: 'https://abs.twimg.com/favicon.ico', besideOf: '2' } },
tb: { id: 'tb', contentComponent: 'web', title: 'Notifications', params: { kind: 'web', url: 'https://x.com/notifications', profile: 'work', pinned: true } },
'deck-d1': { id: 'deck-d1', contentComponent: 'deck', title: 'Deck', params: { kind: 'deck', deckId: 'd1' } },
'better-twttr': { id: 'better-twttr', contentComponent: 'panel', title: 'wayBetter', params: { kind: 'panel', section: 'themes', nonce: 123 } },
ts: { id: 'ts', contentComponent: 'start', title: 'New tab', params: { kind: 'start', profile: 'work' } },
bad: { id: 'bad', contentComponent: 'web', params: { kind: 'web', url: 'javascript:alert(1)' } },
} as Record<string, { id: string; contentComponent?: string; title?: string; params: Record<string, unknown> }>,
activeGroup: '2',
});
test('save keeps each tab URL, account and pane theme; drops transient state and unsafe tabs', () => {
const workspace = captureWorkspace({
id: 'launch', name: ' Launch day ', layout: live(), paneThemes: { ta: 'nord', gone: 'paper', ts: 'Not Valid' },
decks: [{ id: 'd1', name: 'Deck', columns: [] } as { id: string }, { id: 'd2' }], now: new Date('2026-09-29T10:00:00Z'),
});
assert.equal(workspace.name, 'Launch day');
assert.equal(workspace.savedAt, '2026-09-29T10:00:00.000Z');
assert.deepEqual(workspace.layout.panels.ta!.params, { kind: 'web', url: 'https://x.com/home', profile: 'default' });
assert.deepEqual(workspace.layout.panels.tb!.params, { kind: 'web', url: 'https://x.com/notifications', profile: 'work', pinned: true });
assert.deepEqual(workspace.layout.panels['better-twttr']!.params, { kind: 'panel', section: 'themes' });
assert.ok(!('bad' in workspace.layout.panels));
assert.deepEqual(workspace.paneThemes, { ta: 'nord' });
assert.deepEqual(workspace.decks, [{ id: 'd1', name: 'Deck', columns: [] }], 'only decks the layout shows');
assert.deepEqual(workspaceTabs(workspace).map(tab => tab.kind), ['web', 'web', 'deck', 'panel', 'start']);
assert.ok(!JSON.stringify(workspace).includes('loading') && !JSON.stringify(workspace).includes('nonce'));
});
test('restore opens fresh tabs (new ids) with the same URLs, accounts, order and pane themes', () => {
const workspace = captureWorkspace({ id: 'launch', name: 'Launch', layout: live(), paneThemes: { ta: 'nord', tb: 'paper' } });
let n = 0;
const plan = planRestore(workspace, () => `new${++n}`, profile => profile !== 'work');
const leaves = (plan.layout.grid as { root: { data: Array<{ data: { views: string[]; activeView: string } }> } }).root.data.map(leaf => leaf.data);
assert.deepEqual(leaves[0]!.views, ['new1', 'new2', 'deck-d1'], 'web tabs renamed, the deck keeps its id');
assert.equal(leaves[0]!.activeView, 'new2');
assert.deepEqual(leaves[1]!.views, ['better-twttr', 'new3']);
assert.equal(plan.layout.panels.new1!.id, 'new1');
assert.equal((plan.layout.panels.new1!.params as { url: string }).url, 'https://x.com/home');
assert.ok(!('profile' in (plan.layout.panels.new2!.params as object)), 'a removed account falls back to the default');
assert.deepEqual(plan.paneThemes, { new1: 'nord', new2: 'paper' });
assert.deepEqual(plan.ids, { ta: 'new1', tb: 'new2', 'deck-d1': 'deck-d1', 'better-twttr': 'better-twttr', ts: 'new3' });
// Restoring twice never reuses a live tab's id.
const again = planRestore(workspace, () => `again${++n}`);
assert.ok(Object.keys(again.layout.panels).every(id => !id.startsWith('new')));
});
test('the store is bounded, names are required and the default must exist', () => {
let store = EMPTY_WORKSPACES;
const make = (id: string) => captureWorkspace({ id, name: id, layout: live() });
for (let n = 0; n < WORKSPACE_LIMITS.count; n += 1) store = upsertWorkspace(store, make(`w${n}`));
assert.throws(() => upsertWorkspace(store, make('one-more')), /At most/);
store = upsertWorkspace(store, { ...make('w0'), name: 'Replaced' });
assert.equal(store.items.length, WORKSPACE_LIMITS.count);
assert.equal(store.items[0]!.name, 'Replaced');
store = setDefaultWorkspace(store, 'w3');
assert.equal(store.default, 'w3');
assert.equal(setDefaultWorkspace(store, 'nope').default, null);
store = removeWorkspace(store, 'w3');
assert.equal(store.default, null, 'deleting the default clears it');
assert.throws(() => renameWorkspace(store, 'w1', ' '));
assert.equal(renameWorkspace(store, 'w1', 'Night shift').items[1]!.name, 'Night shift');
assert.equal(workspaceId('Launch Day!', ['launch-day']), 'launch-day-2');
assert.equal(workspaceId('日本', []), 'workspace');
assert.throws(() => captureWorkspace({ id: 'x', name: 'Empty', layout: { grid: { root: {} }, panels: {} } }), /no tabs/);
const huge = live();
for (let n = 0; n < 70; n += 1) huge.panels[`t${n}`] = { id: `t${n}`, params: { kind: 'start' } };
(huge.grid.root.data[0]!.data.views as string[]).push(...Array.from({ length: 70 }, (_, n) => `t${n}`));
assert.throws(() => captureWorkspace({ id: 'big', name: 'Big', layout: huge }), /at most 64 tabs/);
});
test('whatever native code returns is normalized, never trusted blindly', () => {
const store = normalizeWorkspaces({ version: 1, default: 'gone', items: [
captureWorkspace({ id: 'ok', name: 'OK', layout: live() }),
{ id: 'Bad Id', name: 'x', layout: live() },
{ id: 'noname', name: '', layout: live() },
{ id: 'ok', name: 'Duplicate', layout: live() },
{ id: 'nolayout', name: 'No layout' },
] });
assert.deepEqual(store.items.map(item => item.id), ['ok']);
assert.equal(store.default, null);
assert.deepEqual(normalizeWorkspaces('garbage'), EMPTY_WORKSPACES);
});