diff --git a/packages/sw/tests/overview.browser.mjs b/packages/sw/tests/overview.browser.mjs index 5b60cc2..e78cecc 100644 --- a/packages/sw/tests/overview.browser.mjs +++ b/packages/sw/tests/overview.browser.mjs @@ -5,28 +5,55 @@ import { execFileSync } from 'node:child_process' import path from 'node:path' import http from 'node:http' import { parse } from '@babel/parser' -import { swStoryEntries, retainedInventory, inventorySignature, retiredDropdownIds, integratedChangedFiles } from './storybook-inventory.mjs' +import { swStoryEntries, retainedInventory, inventorySignature, retiredDropdownIds, integratedChangedFiles, integratedStoryIds, retiredPropertyPanelIds, retiredUnusedDemoIds, movedHomeMenuIds, retiredHomeMenuCompareIds, movedToPopupIds } from './storybook-inventory.mjs' import { loadPlaywright } from './playwright-runtime.mjs' const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-overview-qa') await mkdir(out, { recursive: true }) -const report = { baseCommit: '50bf5bda8e956a4973b923d64131f2f2285bd38c', checks: [], errors: [], sizes: [] } +// 2026-10-20 (test alignment): 50bf5bd and its before-index.json stay the baseline. The checks below accept only the +// approved changes after it (storybook-inventory.mjs and the ledgers here) and the approved main merge 553e4df. +const report = { baseCommit: '50bf5bda8e956a4973b923d64131f2f2285bd38c', approvedMainCommit: '553e4df4a865aaf6514b1c2b86cb2e3e9a658e5f', checks: [], errors: [], sizes: [] } const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); console.log(ok ? 'PASS' : 'FAIL', name) } const before = JSON.parse(await readFile(path.join(out, 'before-index.json'), 'utf8')) -const after = swStoryEntries(JSON.parse(await readFile('storybook-static/index.json', 'utf8'))) +const index = JSON.parse(await readFile('storybook-static/index.json', 'utf8')) +const after = swStoryEntries(index) await writeFile(path.join(out, 'after-index.json'), JSON.stringify(after, null, 2) + '\n') -check('24 retained Story IDs, names and tags; only approved Dropdown retirements', after.length === 24 && JSON.stringify(inventorySignature(retainedInventory(before))) === JSON.stringify(inventorySignature(after))) +// 2b0e1a2 sidebar simplification: these two keep their ID and direct URL with the verification tag. +const verificationTaggedIds = ['sw-header--window-move-motion', 'sw-header--k-guide'] +const removedHeaderIds = [...retiredPropertyPanelIds, ...retiredUnusedDemoIds, ...movedToPopupIds] +// 170a5c2 home menu merge: the sidebar Story sw-header--home-menu plus the moved sw-header--home-menu-* Stories. +const addedHeaderIds = [...integratedStoryIds.filter(id => id.startsWith('sw-header--')), 'sw-header--home-menu', ...Object.values(movedHomeMenuIds).filter(id => !retiredHomeMenuCompareIds.includes(id))] +const expectedRetained = retainedInventory(before).filter(entry => !removedHeaderIds.includes(entry.id)) + .map(entry => verificationTaggedIds.includes(entry.id) ? { ...entry, tags: [...entry.tags, 'verification'] } : entry) +const inventory = { expected: expectedRetained.length + addedHeaderIds.length, actual: after.length, missingAdded: addedHeaderIds.filter(id => !after.some(e => e.id === id)), movedToPopup: movedToPopupIds.map(id => index.entries[id]?.title ?? null) } +check(`${inventory.expected} SW/Header Story IDs: the 24 retained at 50bf5bd (names, tags) minus approved retirements and the Popup move, plus approved additions`, + after.length === inventory.expected && inventory.missingAdded.length === 0 && inventory.movedToPopup.every(title => title === 'SW/Popup') + && JSON.stringify(inventorySignature(expectedRetained)) === JSON.stringify(inventorySignature(after.filter(e => !addedHeaderIds.includes(e.id)))), inventory) const source = await readFile('src/sw/sw-header.stories.tsx', 'utf8') const baseline = execFileSync('git', ['show', `${report.baseCommit}:src/sw/sw-header.stories.tsx`], { encoding: 'utf8' }) const stories = text => Object.fromEntries(parse(text, { sourceType:'module', plugins:['typescript','jsx'] }).program.body .filter(n => n.type === 'ExportNamedDeclaration' && n.declaration.type === 'VariableDeclaration').map(n => [n.declaration.declarations[0].id.name, text.slice(n.start,n.end).replaceAll('\r\n','\n')])) const oldStories = stories(baseline), newStories = stories(source) const retiredExports = ['LayerDropdown','ColorDropdown','LineStyleDropdown','TextDimensionStyleDropdown','ComposedControls'] -check('retained non-Overview declarations byte-identical; relocated composition separately verified', Object.keys(oldStories).filter(k => k !== 'Overview' && !retiredExports.includes(k)).every(k => oldStories[k] === newStories[k])) +// A declaration changed after 50bf5bd must equal its version at the approving commit; a removed one must be an approved +// retirement, or the Popup move compared in its new file. All others stay byte-identical to 50bf5bd. +const gitStories = (commit, file) => stories(execFileSync('git', ['show', `${commit}:${file}`], { encoding: 'utf8' })) +const storyId = name => `sw-header--${name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}` +const approvedDeclarations = { ResponsiveMore: '170a5c23aed10f8fc65e5b20e9b94bd3c1ff1227', WindowMoveMotion: '2b0e1a2262002aedb01c29c5d18ffae1219bf3ef', KGuide: '2b0e1a2262002aedb01c29c5d18ffae1219bf3ef' } // measured More section; verification tag +const movedDeclarations = { PropertyPanelCompare: ['src/sw/sw-property-panel.stories.tsx', '2b0e1a2262002aedb01c29c5d18ffae1219bf3ef'] } +const movedNow = Object.fromEntries(await Promise.all(Object.entries(movedDeclarations).map(async ([name, [file]]) => [name, stories(await readFile(file, 'utf8'))[name]]))) +const declarationIssues = Object.keys(oldStories).filter(k => k !== 'Overview' && !retiredExports.includes(k)).filter(k => { + if (movedDeclarations[k]) return !!newStories[k] || movedNow[k] !== gitStories(movedDeclarations[k][1], movedDeclarations[k][0])[k] + if (!newStories[k]) return ![...retiredPropertyPanelIds, ...retiredUnusedDemoIds].includes(storyId(k)) + return newStories[k] !== (approvedDeclarations[k] ? gitStories(approvedDeclarations[k], 'src/sw/sw-header.stories.tsx')[k] : oldStories[k]) +}) +check('retained non-Overview declarations byte-identical to 50bf5bd or their approving commit; only approved removals; relocated composition separately verified', declarationIssues.length === 0, declarationIssues) check('retired Dropdown IDs absent', retiredDropdownIds.every(id => !after.some(e => e.id === id))) -// Since 2026-10-19 the production source changes only in the integrated-improvement files (their own suites verify them). -const sourceDiff = execFileSync('git', ['diff', '--name-only', report.baseCommit, '--', 'packages/sw/src'], { encoding:'utf8' }).trim().split(/\r?\n/).filter(Boolean) -check('package production source untouched except the integrated-improvement files', sourceDiff.every(file => integratedChangedFiles.includes(file)), sourceDiff.filter(file => !integratedChangedFiles.includes(file))) +// The package source may differ from the approved main merge only in the integrated-improvement files (their own suites +// verify them). Approved package changes after 50bf5bd are listed as sinceBaseline; update approvedMainCommit on the next approved merge. +const sourceFiles = (...refs) => execFileSync('git', ['diff', '--name-only', ...refs, '--', 'packages/sw/src'], { encoding:'utf8' }).trim().split(/\r?\n/).filter(Boolean) +const sourceDiff = { unapproved: sourceFiles(report.approvedMainCommit).filter(file => !integratedChangedFiles.includes(file)), sinceBaseline: sourceFiles(report.baseCommit).length } +check('package production source unchanged since the approved main merge except the integrated-improvement files', sourceDiff.unapproved.length === 0, sourceDiff) const root = path.resolve('storybook-static'), mime = { '.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.ttf':'font/ttf','.png':'image/png' } const server = http.createServer(async(req,res) => {