import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' const storeSource = readFileSync(new URL('./workspace-tabs/workspaceTabs.ts', import.meta.url), 'utf8') const unsavedSource = readFileSync(new URL('./workspace-tabs/unsavedWork.ts', import.meta.url), 'utf8') const tabsSource = readFileSync(new URL('../components/WorkspaceTabs.vue', import.meta.url), 'utf8') const layoutSource = readFileSync(new URL('../layouts/AppLayout.vue', import.meta.url), 'utf8') const tabStyles = readFileSync(new URL('../styles/workspace-tabs.css', import.meta.url), 'utf8') test('workspace tabs are isolated by account, restored from session storage, and capped without evicting protected work', () => { assert.match(storeSource, /cc:client-workspace-tabs:\$\{ownerId\}/) assert.match(storeSource, /sessionStorage\.getItem/) assert.match(storeSource, /sessionStorage\.setItem/) assert.match(storeSource, /MAX_WORKSPACE_TABS = 12/) assert.match(storeSource, /MAX_CACHED_WORKSPACE_VIEWS = 24/) assert.match(storeSource, /!protectedKeys\.has\(tab\.key\)/) assert.match(storeSource, /sort\(\(left, right\) => left\.lastVisitedAt - right\.lastVisitedAt\)/) }) test('workspace tabs exclude public and forbidden routes and use one tab per route name', () => { assert.match(storeSource, /excludedRouteNames = new Set\(\['login', 'register', 'terms', 'privacy', 'forbidden'\]\)/) assert.match(storeSource, /const routeName = String\(route\.name\)/) assert.match(storeSource, /key: routeName/) assert.match(storeSource, /tabs\.value\.find\(\(tab\) => tab\.key === routeName\)/) }) test('closing tabs invalidates their cached instance and returns the most recently visited fallback', () => { assert.match(storeSource, /function bumpCacheEpoch/) assert.match(storeSource, /for \(const key of keys\) bumpCacheEpoch\(key\)/) assert.match(storeSource, /sort\(\(left, right\) => right\.lastVisitedAt - left\.lastVisitedAt\)/) assert.match(storeSource, /return \{ closedActive, fallbackPath:/) }) test('the shell renders cached route views below an accessible workspace tab strip', () => { assert.match(layoutSource, //) assert.match(layoutSource, /workspace\.cacheKey\(viewRoute\.name\)/) assert.match(layoutSource, /scrollPositions\.set\(previousRouteName, window\.scrollY\)/) assert.match(layoutSource, /window\.scrollTo\(0, scrollPositions\.get\(routeName\) \?\? 0\)/) assert.match(tabsSource, /role="tablist"/) assert.match(tabsSource, /role="tab"/) assert.match(tabsSource, /:aria-selected=/) assert.match(tabsSource, /关闭当前/) assert.match(tabsSource, /关闭其他/) assert.match(tabsSource, /关闭右侧/) assert.match(tabsSource, /关闭全部/) }) test('unsaved work blocks destructive closes and browser unload without blocking cached tab switches', () => { assert.match(unsavedSource, /dirtyRouteKeys/) assert.match(tabsSource, /unsaved\.findDirty\(keys\)/) assert.match(tabsSource, /放弃未保存的修改/) assert.match(layoutSource, /window\.addEventListener\('beforeunload', handleBeforeUnload\)/) assert.match(layoutSource, /if \(!unsaved\.hasDirty\) return/) assert.doesNotMatch(tabsSource, /beforeEach|onBeforeRouteLeave/) }) test('unsaved registrations use instance ownership so stale cached views cannot replace or remove a reopened view', () => { assert.match(unsavedSource, /const owners = new Map\(\)/) assert.match(unsavedSource, /function claim\(key: string, owner: symbol\)/) assert.match(unsavedSource, /if \(owner && owners\.get\(entry\.key\) !== owner\) return/) assert.match(unsavedSource, /if \(owner && owners\.get\(key\) !== owner\) return/) assert.match(unsavedSource, /store\.claim\(options\.key, owner\)/) assert.match(unsavedSource, /store\.remove\(options\.key, owner\)/) }) test('unsaved registration only tracks the dirty ref and cannot subscribe to its own store writes', () => { assert.match(unsavedSource, /watch\(options\.dirty, \(dirty\) =>/) assert.match(unsavedSource, /\}, \{ immediate: true \}\)/) assert.doesNotMatch(unsavedSource, /watchEffect/) assert.match(unsavedSource, /current\.routeKey === entry\.routeKey[\s\S]*current\.dirty === entry\.dirty\) return/) }) test('the tab strip keeps touch targets and horizontal navigation usable on narrow screens', () => { assert.match(tabStyles, /overflow-x: auto/) assert.match(tabStyles, /scrollbar-width: none/) assert.match(tabStyles, /::-webkit-scrollbar \{ display: none; width: 0; height: 0; \}/) assert.match(tabsSource, /@wheel="handleTabWheel"/) assert.match(tabsSource, /scroller\.scrollLeft = Math\.max/) assert.match(tabStyles, /@media \(max-width: 720px\)/) assert.match(tabStyles, /\.workspace-tab__close \{ width: 40px; height: 40px;/) assert.match(tabStyles, /\.workspace-tabs__more \{ width: 48px; height: 48px;/) assert.match(tabStyles, /prefers-reduced-motion: reduce/) })