chore(sw): remove selected Storybook demos
This commit is contained in:
1 parent
92830fe11d
commit
50bf5bda8e
11 files changed
+2387
-265
No files matched your search
@@ -42,7 +42,7 @@ try {
|
||||
await page.locator('[data-detail-row="문자스타일 상세"] [data-detail-case="hover"] button').hover()
|
||||
await page.screenshot({path:path.join(output,'comparison-pointer-hover.png'),fullPage:true})
|
||||
} else if (mode === 'preservation') {
|
||||
await goto('controls')
|
||||
await goto('composition')
|
||||
for(const width of [390,768,1280,1440]){
|
||||
await page.setViewportSize({width,height:1000})
|
||||
const layout=await page.locator('header.hm-sw-header').first().evaluate(h=>({height:h.getBoundingClientRect().height,overflow:h.scrollWidth-h.clientWidth,documentOverflow:document.documentElement.scrollWidth-document.documentElement.clientWidth}))
|
||||
@@ -54,7 +54,7 @@ try {
|
||||
const box=await page.locator('.hm-sw-property').first().boundingBox()
|
||||
check(`preserve property panel ${id}`,box.width===314&&box.height===height,box)
|
||||
}
|
||||
await goto('controls')
|
||||
await goto('composition')
|
||||
const motion=page.locator('[data-sw-window-move]').first();await motion.hover()
|
||||
check('preserve window motion 1400ms',await motion.evaluate(e=>e.getAnimations({subtree:true}).length===3&&e.getAnimations({subtree:true}).every(a=>a.effect.getTiming().duration===1400)))
|
||||
await page.mouse.move(1,900);check('window motion leave clears animations',await motion.evaluate(e=>e.getAnimations({subtree:true}).length===0))
|
||||
@@ -70,7 +70,8 @@ try {
|
||||
const ids = Object.values(index.entries).filter(e => e.title === 'SW/Header' && e.type === 'story').map(e => e.id)
|
||||
report.storyIds = ids
|
||||
if (mode === 'baseline') await writeFile(path.join(output, 'baseline-story-ids.json'), JSON.stringify(ids))
|
||||
for (const story of ['default', 'controls']) {
|
||||
// These retained examples cover fixed-width and fluid headers after the two Args demos were retired.
|
||||
for (const story of ['overview', 'composition']) {
|
||||
await goto(story)
|
||||
for (const width of [1920,1800,1440,1180,1080,900,768,640,520,390]) {
|
||||
await page.setViewportSize({ width, height:1000 })
|
||||
@@ -94,9 +95,11 @@ try {
|
||||
await writeFile(path.join(output,'baseline.json'),JSON.stringify(snapshots,null,2))
|
||||
} else {
|
||||
const before=JSON.parse(await readFile(path.join(output,'baseline.json'),'utf8'))
|
||||
for(const key of Object.keys(before).filter(k=>!k.startsWith('text'))) check(`unchanged header ${key}`,JSON.stringify(before[key])===JSON.stringify(snapshots[key]))
|
||||
for(const key of Object.keys(snapshots).filter(k=>!k.startsWith('text'))) check(`unchanged header ${key} (capture a current baseline for replacement fixtures)`,JSON.stringify(before[key])===JSON.stringify(snapshots[key]))
|
||||
const oldIds=JSON.parse(await readFile(path.join(output,'baseline-story-ids.json'),'utf8'))
|
||||
check('all previous 31 stories retained',oldIds.length===31&&oldIds.every(id=>ids.includes(id)),{before:oldIds.length,after:ids.length})
|
||||
const retiredIds=['default','controls','responsive','text-stroke','dynamic-data'].map(id=>`sw-header--${id}`)
|
||||
const retainedIds=oldIds.filter(id=>!retiredIds.includes(id))
|
||||
check('retained stories remain; only the five approved demos are absent',ids.length===28&&retainedIds.every(id=>ids.includes(id))&&retiredIds.every(id=>!ids.includes(id)),{before:oldIds.length,after:ids.length})
|
||||
for(const id of ids){await goto(id.replace('sw-header--',''));check(`render ${id}`,!(await page.locator('.sb-errordisplay').isVisible()))}
|
||||
await goto('composition')
|
||||
const presets=['기본 전체','구성 A','구성 B (레이어·선스타일)','구성 C (로고만)','파일 목록 제거','레이어 제거','색상 제거','스타일 그룹 제거','여러 메뉴 제거','순서 변경','숨김 + 순서 변경']
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Scoped verification for the five approved Story removals; serves the production build locally.
|
||||
// Usage: node packages/sw/tests/storybook-cleanup-stage1.browser.mjs [evidence-dir]
|
||||
import { createRequire } from 'node:module'
|
||||
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
import http from 'node:http'
|
||||
import { parse } from '@babel/parser'
|
||||
|
||||
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-cleanup-stage1-qa')
|
||||
await mkdir(out, { recursive: true })
|
||||
const report = { checks: [], errors: [], baseCommit: '92830fe11db7f37eb6a45e8bbc6a5038ec579649' }
|
||||
const check = (name, ok, data) => {
|
||||
report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data })
|
||||
console.log(ok ? 'PASS' : 'FAIL', name)
|
||||
}
|
||||
const retired = ['responsive', 'text-stroke', 'dynamic-data', 'default', 'controls'].map(id => `sw-header--${id}`)
|
||||
const before = JSON.parse(await readFile(path.join(out, 'before-index.json'), 'utf8'))
|
||||
const after = Object.values(JSON.parse(await readFile('storybook-static/index.json', 'utf8')).entries)
|
||||
.filter(e => e.type === 'story' && e.title === 'SW/Header')
|
||||
await writeFile(path.join(out, 'after-index.json'), JSON.stringify(after, null, 2) + '\n')
|
||||
report.deleted = before.filter(e => !after.some(a => a.id === e.id)).map(e => ({ name: e.name, id: e.id }))
|
||||
check('33 -> 28; precisely the five requested IDs removed', before.length === 33 && after.length === 28 &&
|
||||
report.deleted.length === 5 && retired.every(id => report.deleted.some(e => e.id === id)), report.deleted)
|
||||
check('all retained names, IDs, ordering and tags unchanged', JSON.stringify(before.filter(e => !retired.includes(e.id)).map(e => [e.id, e.name, e.tags])) ===
|
||||
JSON.stringify(after.map(e => [e.id, e.name, e.tags])))
|
||||
|
||||
// Compare declaration text with the starting commit, including every retained fixture/helper.
|
||||
const file = 'src/sw/sw-header.stories.tsx'
|
||||
const original = execFileSync('git', ['show', `${report.baseCommit}:${file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n')
|
||||
const current = (await readFile(file, 'utf8')).replaceAll('\r\n', '\n')
|
||||
const declarations = text => {
|
||||
return parse(text, { sourceType: 'module', plugins: ['typescript', 'jsx'] }).program.body.map(statement => {
|
||||
const n = statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement
|
||||
return {
|
||||
name: n.type === 'VariableDeclaration' ? n.declarations.map(d => d.id.name).join(',') :
|
||||
n.type === 'ImportDeclaration' ? `import:${n.source.value}` : n.id?.name ?? n.type,
|
||||
text: text.slice(statement.start, statement.end),
|
||||
}
|
||||
})
|
||||
}
|
||||
const oldDeclarations = new Map(declarations(original).map(d => [d.name, d.text]))
|
||||
const changed = declarations(current).filter(d => d.name !== 'WindowMoveMotion' && oldDeclarations.get(d.name) !== d.text)
|
||||
check('every retained declaration unchanged except WindowMoveMotion', changed.length === 0, changed.map(d => d.name))
|
||||
const section = text => text.match(/<StorySection title="단계별 진행 \(3배 확대\)"[\s\S]*?<\/StorySection>/)?.[0]
|
||||
check('retained step section is byte-identical after newline normalization', !!section(original) && section(original) === section(current))
|
||||
const productionDiff = execFileSync('git', ['diff', report.baseCommit, '--', 'packages/sw/src', 'src/sw/sw-stories.css', 'src/sw/assets', '.storybook'], { encoding: 'utf8' })
|
||||
check('production implementation, CSS, SVG/assets and Storybook configuration unchanged', productionDiff === '')
|
||||
|
||||
const root = path.resolve('storybook-static')
|
||||
const 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) => {
|
||||
try {
|
||||
const pathname = new URL(req.url, 'http://localhost').pathname
|
||||
const file = path.resolve(root, '.' + decodeURIComponent(pathname === '/' ? '/index.html' : pathname))
|
||||
if (path.relative(root, file).startsWith('..')) return res.writeHead(403).end()
|
||||
res.setHeader('Content-Type', mime[path.extname(file)] ?? 'application/octet-stream')
|
||||
res.end(await readFile(file))
|
||||
} catch { res.writeHead(404).end() }
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const base = `http://127.0.0.1:${server.address().port}`
|
||||
const { chromium } = createRequire(import.meta.url)(process.env.SW_PLAYWRIGHT_PATH ?? 'C:/Users/user/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
|
||||
let browser
|
||||
try {
|
||||
browser = await chromium.launch({ headless: true, executablePath: process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' })
|
||||
report.browser = browser.version()
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
|
||||
page.on('pageerror', e => report.errors.push(e.message))
|
||||
page.on('console', m => { if (m.type() === 'error') report.errors.push(m.text()) })
|
||||
const goto = async id => {
|
||||
await page.goto(`${base}/iframe.html?id=${id.startsWith('sw-header--') ? id : `sw-header--${id}`}&viewMode=story`)
|
||||
await page.locator('.hm-sw-header, .hm-sw-theme').first().waitFor()
|
||||
await page.evaluate(() => document.fonts.ready)
|
||||
await page.mouse.move(1, 1)
|
||||
}
|
||||
for (const entry of after) {
|
||||
await goto(entry.id)
|
||||
check(`render ${entry.id}`, !(await page.locator('.sb-errordisplay').isVisible()))
|
||||
}
|
||||
|
||||
await goto('window-move-motion')
|
||||
const titles = await page.locator('section > header h2').allTextContents()
|
||||
check('only the requested section remains', JSON.stringify(titles) === JSON.stringify(['단계별 진행 (3배 확대)']), titles)
|
||||
check('removed live demos, frame specimens and callback logs absent', await page.locator('[data-frame-width], [data-callback-log]').count() === 0)
|
||||
const stages = await page.locator('[data-motion-time]').evaluateAll(elements => elements.map(e => {
|
||||
const zoom = e.querySelector('.sw-story-zoom'), motion = e.querySelector('[data-sw-window-move]')
|
||||
const box = zoom.getBoundingClientRect()
|
||||
return { time: e.dataset.motionTime, zoom: getComputedStyle(zoom).zoom, width: box.width, height: box.height,
|
||||
animations: motion.getAnimations({ subtree: true }).map(a => ({ state: a.playState, time: a.currentTime, duration: a.effect.getTiming().duration })) }
|
||||
}))
|
||||
report.stages = stages
|
||||
const expected = ['rest', ...[0, 240, 480, 520, 800, 1040, 1600, 1640].map(t => String(t * 1400 / 1640))]
|
||||
check('nine original steps and 3x magnification preserved', stages.length === 9 && stages.every((s, i) => s.time === expected[i] && Number(s.zoom) === 3 && s.width === 456 && s.height === 144), stages)
|
||||
check('rest is static; timed specimens pause three 1400ms animations at their original positions', stages[0].animations.length === 0 && stages.slice(1).every(s => s.animations.length === 3 && s.animations.every(a => a.state === 'paused' && a.duration === 1400 && Math.abs(a.time - Number(s.time)) < 0.01)))
|
||||
await page.screenshot({ path: path.join(out, 'window-move-motion.png'), fullPage: true })
|
||||
const motion = page.locator('[data-motion-time="rest"] [data-sw-window-move]')
|
||||
await motion.hover()
|
||||
const timing = await motion.evaluate(async e => {
|
||||
const animations = e.getAnimations({ subtree: true })
|
||||
const before = animations.map(a => a.currentTime)
|
||||
const started = performance.now()
|
||||
await new Promise(resolve => { const tick = () => performance.now() - started >= 180 ? resolve() : requestAnimationFrame(tick); requestAnimationFrame(tick) })
|
||||
return animations.map((a, i) => ({ before: before[i], after: a.currentTime, state: a.playState, duration: a.effect.getTiming().duration, iterations: a.effect.getTiming().iterations === Infinity ? 'infinite' : a.effect.getTiming().iterations }))
|
||||
})
|
||||
check('live Hover motion advances with original 1400ms infinite cycle', timing.length === 3 && timing.every(a => a.state === 'running' && a.after > a.before && a.duration === 1400 && a.iterations === 'infinite'), timing)
|
||||
await page.mouse.down()
|
||||
check('pointer held pauses motion and hides tooltip', await motion.evaluate(e => e.getAnimations({ subtree: true }).every(a => a.playState === 'paused') && getComputedStyle(e.querySelector('[role="tooltip"]')).visibility === 'hidden'))
|
||||
await page.mouse.up()
|
||||
await page.mouse.move(1, 1)
|
||||
check('Hover leave resets live specimen', await motion.evaluate(e => e.getAnimations({ subtree: true }).length === 0))
|
||||
|
||||
await goto('responsive-more')
|
||||
const header = page.locator('[data-frame-width="1440"] header.hm-sw-header').first()
|
||||
await header.getByRole('button', { name: '더보기', exact: true }).click()
|
||||
await header.locator('[role="menuitem"][data-menu="line-style"]').click()
|
||||
check('Responsive More still switches the representative field and opens its dropdown', await header.locator('.hm-sw-header__line-menu').count() > 0 && await header.locator('.hm-sw-header__line-menu').isVisible())
|
||||
await page.keyboard.press('Escape')
|
||||
|
||||
await goto('dynamic-controls')
|
||||
await page.getByRole('checkbox', { name: '구성 color', exact: true }).uncheck()
|
||||
check('Dynamic Controls removes a configured field', await page.locator('header.hm-sw-header [data-control="color"], header.hm-sw-header [data-menu="color"]').count() === 0)
|
||||
await page.getByRole('checkbox', { name: '구성 color', exact: true }).check()
|
||||
check('Dynamic Controls restores a configured field', await page.locator('header.hm-sw-header [data-menu="color"]').count() > 0)
|
||||
await page.getByRole('button', { name: '구성 초기화', exact: true }).click()
|
||||
const documents = page.locator('header.hm-sw-header .hm-sw-header__document-button')
|
||||
await documents.click()
|
||||
await page.getByRole('button', { name: '+ New', exact: true }).click()
|
||||
await documents.click()
|
||||
check('host-owned document list can still grow dynamically', await page.getByText('새 문서 1', { exact: true }).count() > 0)
|
||||
|
||||
await goto('composition')
|
||||
for (const width of [390, 768, 1280, 1440]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
const geometry = await page.locator('header.hm-sw-header').evaluate(e => ({ height: e.getBoundingClientRect().height, overflow: e.scrollWidth - e.clientWidth }))
|
||||
check(`retained fluid header layout at ${width}px`, geometry.height === 49 && geometry.overflow === 0, geometry)
|
||||
}
|
||||
check('no browser errors', report.errors.length === 0, report.errors)
|
||||
} catch (error) {
|
||||
check('suite completes', false, error.stack)
|
||||
} finally {
|
||||
if (browser) await browser.close()
|
||||
await new Promise(resolve => server.close(resolve))
|
||||
report.status = report.checks.every(c => c.status === 'PASS') ? 'PASS' : 'FAIL'
|
||||
await writeFile(path.join(out, 'result.json'), JSON.stringify(report, null, 2) + '\n')
|
||||
console.log(JSON.stringify({ status: report.status, checks: report.checks.length, errors: report.errors }))
|
||||
process.exitCode = report.status === 'PASS' ? 0 : 1
|
||||
}
|
||||
Reference in new issue
Block a user