Files
Codeman/test/spawned-tabs-follow-setting.test.ts
T
Aamer Akhter 5fb56b0889 fix(tabs): put child follow behind a setting, stop unpinning children on order PUTs
Review fixes for "place new, child and web tabs into groups".

- Spawned tabs follow their parent only with the new synced setting
  spawnedTabsFollowParent (App Settings > Appearance > Tabs, off by default).
  TabLayoutService.sessionCreated() reads it fresh through a
  childrenFollowParent dep: off, a spawned session lands after its parent and
  is marked manual in the same write; on, it follows until moved by hand. The
  row menu shows the placement note and "Follow parent again" only when on.
  "New session" in a group menu is unaffected. zh-CN label and a
  Settings-Reference row.
- applyLegacySessionRank marks a child manual only when the requested order
  splits it from its parent's block. A PUT of the current order, or one that
  moves a family together, leaves every stored placement alone.
- The browser never echoes a create or delete through PUT /api/session-order,
  with or without groups: the layout service already re-projects the order and
  broadcasts it, and syncSessionOrder adopts it.
- The placement note in the row menu is plain text described by the menu, not
  a focusable menu item.
- A tab that first appears inside a collapsed group briefly marks that group's
  header.
- i18n skips .tab-name, so tab names are never translated.
- Tests: legacy-order cases for an unchanged order, a family reorder and a
  split; service cases for the setting and for order PUTs; the scheduled
  registration test now exercises register/undo; the grouped-create test binds
  port 0; one route case runs through the real layout service.
2026-10-09 20:34:37 -04:00

69 lines
3.0 KiB
TypeScript

/**
* The "Spawned tabs follow their parent" setting (`spawnedTabsFollowParent`).
*
* SYNCED and OFF by default: the server does the placement, so the value must
* reach it (in SettingsUpdateSchema, never a per-device display key), and the
* layout service reads it at every session creation. These tests read the real
* schema, settings-ui.js, index.html, i18n.js, server.ts and the wiki.
*/
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { SettingsUpdateSchema } from '../src/web/schemas.js';
const root = resolve(import.meta.dirname, '..');
const read = (path: string) => readFileSync(resolve(root, path), 'utf8');
const html = read('src/web/public/index.html');
const settingsUi = read('src/web/public/settings-ui.js');
const i18n = read('src/web/public/i18n.js');
const server = read('src/web/server.ts');
const wiki = read('docs/wiki/Settings-Reference.md');
const LABEL = 'Spawned Tabs Follow Their Parent';
describe('spawnedTabsFollowParent setting', () => {
it('is a synced boolean in the settings schema', () => {
expect(SettingsUpdateSchema.safeParse({ spawnedTabsFollowParent: true }).success).toBe(true);
expect(SettingsUpdateSchema.safeParse({ spawnedTabsFollowParent: 'yes' }).success).toBe(false);
});
it('is not a per-device display key', () => {
const start = settingsUi.indexOf('const displayKeys = new Set([');
const block = settingsUi.slice(start, settingsUi.indexOf(']);', start));
expect(start).toBeGreaterThan(-1);
expect(block).not.toContain('spawnedTabsFollowParent');
});
it('has an unchecked switch in App Settings > Appearance > Tabs, marked synced', () => {
const tabs = html.slice(html.indexOf('<h4>Tabs</h4><span class="set-scope">device</span>'));
const group = tabs.slice(0, tabs.indexOf('</section>'));
const row = group.match(
/<div class="set-row"[^>]*id="appSettingsSpawnedTabsFollowParentItem"[\s\S]*?<\/label>/
)?.[0];
expect(row).toBeTruthy();
expect(row).toContain(LABEL);
expect(row).toContain('<span class="set-tag">synced</span>');
expect(row).toMatch(/<input type="checkbox" id="appSettingsSpawnedTabsFollowParent">/);
});
it('loads as off unless explicitly true, and saves from the switch', () => {
expect(settingsUi).toContain(
"document.getElementById('appSettingsSpawnedTabsFollowParent').checked = settings.spawnedTabsFollowParent === true;"
);
expect(settingsUi).toContain(
"spawnedTabsFollowParent: document.getElementById('appSettingsSpawnedTabsFollowParent').checked,"
);
});
it('is read fresh by the layout service at creation time', () => {
expect(server).toMatch(
/childrenFollowParent: async \(\) => \(await this\.readSettings\(true\)\)\.spawnedTabsFollowParent === true/
);
});
it('has a zh-CN label and a wiki row', () => {
expect(i18n).toMatch(new RegExp(`'${LABEL}': '[^']+'`));
expect(wiki).toMatch(new RegExp(`\\| ${LABEL}\\s+\\|[^\\n]*Synced, off by default`));
});
});