fix(storybook): show Dropdown directly under Header

This commit is contained in:
shkim4 committed 2026-10-19 12:36:10 +09:00
1 parent 9482d45f34
commit c705a1fc59
36 files changed
+8837 -42

No files matched your search

@@ -6,20 +6,24 @@ import { execFileSync } from 'node:child_process'
import path from 'node:path'
import http from 'node:http'
import { parse } from '@babel/parser'
import { retiredDropdownIds, swStoryEntries, inventorySignature } from './storybook-inventory.mjs'
import { retiredDropdownIds, swStoryEntries, retainedInventory, inventorySignature } from './storybook-inventory.mjs'
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-dropdown-consolidation-qa')
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-dropdown-page-qa')
await mkdir(out, { recursive: true })
const report = { baseCommit: 'c165753bbe24de6581c66704bde4ba3246049ec2', checks: [], errors: [], callbacks: [], geometry: [] }
const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${ok ? '' : ` ${JSON.stringify(data)}`}`) }
const before = JSON.parse(await readFile('docs/sw/storybook-dropdown-consolidation-qa/before-full-index.json', 'utf8'))
const after = JSON.parse(await readFile('storybook-static/index.json', 'utf8'))
const beforeSW = swStoryEntries(before), afterSW = swStoryEntries(after)
const previous = JSON.parse(await readFile('docs/sw/storybook-dropdown-page-qa/before-full-index.json', 'utf8'))
const previousSW = swStoryEntries(previous)
report.menuCorrectionBaseCommit = '9482d45f340981c4b4ad6537cb678bf4afcdc681'
await writeFile(path.join(out, 'after-full-index.json'), JSON.stringify(after, null, 2) + '\n')
await writeFile(path.join(out, 'story-inventory.json'), JSON.stringify({ before: beforeSW, after: afterSW, removed: beforeSW.filter(e => retiredDropdownIds.includes(e.id)) }, null, 2) + '\n')
check('28 -> 24 SW Stories, exactly four approved Dropdown retirements', beforeSW.length === 28 && afterSW.length === 24 &&
JSON.stringify(inventorySignature(beforeSW.filter(e => !retiredDropdownIds.includes(e.id)))) === JSON.stringify(inventorySignature(afterSW)))
check('Dropdown group has only Composed Controls with original ID', afterSW.filter(e => e.title === 'SW/Header/Dropdown').length === 1 && after.entries['sw-header--composed-controls']?.title === 'SW/Header/Dropdown')
JSON.stringify(inventorySignature(retainedInventory(beforeSW))) === JSON.stringify(inventorySignature(afterSW)))
check('Dropdown is one Story directly under Header with original ID, no Dropdown folder', afterSW.filter(e => e.name === 'Dropdown').length === 1 && after.entries['sw-header--composed-controls']?.title === 'SW/Header' && !afterSW.some(e => e.title.startsWith('SW/Header/')))
check('menu correction keeps all 24 IDs; only composed Story name/title change', previousSW.length === 24 && afterSW.length === 24 && JSON.stringify(inventorySignature(retainedInventory(previousSW))) === JSON.stringify(inventorySignature(afterSW)) && JSON.stringify(previousSW.filter(e => e.id !== 'sw-header--composed-controls')) === JSON.stringify(afterSW.filter(e => e.id !== 'sw-header--composed-controls')))
const nonSW = index => Object.values(index.entries).filter(e => !e.title.startsWith('SW/Header'))
check('all non-SW index entries identical', JSON.stringify(nonSW(before)) === JSON.stringify(nonSW(after)))
const sourcePath = 'src/sw/sw-header.stories.tsx'
@@ -72,6 +76,7 @@ try {
const events = name => report.callbacks.filter(line => line.startsWith(name)).length
const once = async (name, action) => { const previous = events(name); await action(); check(`${name} callback exactly once`, events(name) === previous + 1, report.callbacks.slice(-5)) }
const labels = ['레이어', '색상', '선스타일', '문자스타일', '치수스타일']
check('Dropdown page contains the Composed Controls group', await page.getByRole('heading', { name: 'Dropdown', level: 1, exact: true }).count() === 1 && await page.getByRole('heading', { name: 'Composed Controls', level: 2, exact: true }).count() === 1 && await page.getByRole('group', { name: 'Composed Controls', exact: true }).count() === 1)
if (process.argv[3] !== 'followup') {
check('one copy of five Dropdown fields and the details group', await host.locator('.hm-sw-header__field').count() === 5 && await host.getByRole('group', { name: '상세보기', exact: true }).count() === 1 && await field('속성Bar').count() === 1 && await field('속성창').count() === 1)
const hitTest = locator => locator.evaluate(e => {
@@ -98,7 +103,7 @@ try {
check(`${label} Escape closes and restores focus at ${width}`, await menu().count() === 0 && await trigger.evaluate(e => e === document.activeElement))
await trigger.click(); await trigger.click()
check(`${label} trigger toggles closed at ${width}`, await menu().count() === 0)
await trigger.click(); await page.getByRole('heading', { name: 'Composed Controls', exact: true }).click()
await trigger.click(); await page.getByRole('heading', { name: 'Dropdown', level: 1, exact: true }).click()
check(`${label} outside click closes at ${width}`, await menu().count() === 0)
}
await page.screenshot({ path: path.join(out, `composed-${width}.png`), fullPage: true })
@@ -248,8 +253,10 @@ try {
check('Window Move Motion retains only nine 3x steps', JSON.stringify(await page.locator('section > header h2').allTextContents()) === JSON.stringify(['단계별 진행 (3배 확대)']) && await page.locator('[data-motion-time]').count() === 9 && await page.locator('.sw-story-zoom').first().evaluate(e => getComputedStyle(e).zoom === '3'))
await page.goto(`${base}/?path=/story/sw-header--composed-controls`)
await page.locator('#storybook-preview-iframe').waitFor()
await page.getByRole('link', { name: 'Composed Controls', exact: true }).waitFor()
check('manager shows Dropdown group with the single Composed Controls page', await page.getByText('Dropdown', { exact: true }).isVisible() && await page.getByRole('link', { name: 'Composed Controls', exact: true }).count() === 1)
const dropdownLink = page.getByRole('link', { name: 'Dropdown', exact: true })
await dropdownLink.waitFor()
await dropdownLink.scrollIntoViewIfNeeded()
check('manager shows a single Dropdown link directly under Header, no Composed Controls submenu', await dropdownLink.count() === 1 && await dropdownLink.getAttribute('href') === '/?path=/story/sw-header--composed-controls' && await page.getByRole('link', { name: 'Composed Controls', exact: true }).count() === 0)
check('retired Dropdown links absent in manager', await page.locator(retiredDropdownIds.map(id => `a[href*="${id}"]`).join(',')).count() === 0)
await page.screenshot({ path: path.join(out, 'composed-manager.png'), fullPage: true })
check('no browser console or page errors', report.errors.length === 0, report.errors)
@@ -6,7 +6,7 @@ import { execFileSync } from 'node:child_process'
import path from 'node:path'
import http from 'node:http'
import { parse } from '@babel/parser'
import { swStoryEntries, retiredDropdownIds, inventorySignature } from './storybook-inventory.mjs'
import { swStoryEntries, retiredDropdownIds, retainedInventory, inventorySignature } from './storybook-inventory.mjs'
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-cleanup-stage1-qa')
await mkdir(out, { recursive: true })
@@ -22,7 +22,7 @@ await writeFile(path.join(out, 'after-index.json'), JSON.stringify(after, null,
report.deleted = before.filter(e => !after.some(a => a.id === e.id)).map(e => ({ name: e.name, id: e.id }))
check('33 -> 24; precisely the approved stage-1 and Dropdown IDs removed', before.length === 33 && after.length === 24 &&
report.deleted.length === 9 && retired.every(id => report.deleted.some(e => e.id === id)), report.deleted)
check('all retained names, IDs and tags unchanged', JSON.stringify(inventorySignature(before.filter(e => !retired.includes(e.id)))) ===
check('retained names, IDs and tags preserved except approved Dropdown rename', JSON.stringify(inventorySignature(retainedInventory(before))) ===
JSON.stringify(inventorySignature(after)))
// Compare declaration text with the starting commit, including every retained fixture/helper.
@@ -1,6 +1,8 @@
// Current SW inventory after the approved stage-1 and Dropdown consolidations.
export const retiredStage1Ids = ['responsive', 'text-stroke', 'dynamic-data', 'default', 'controls'].map(id => `sw-header--${id}`)
export const retiredDropdownIds = ['layer-dropdown', 'color-dropdown', 'line-style-dropdown', 'text-dimension-style-dropdown'].map(id => `sw-header--${id}`)
// Include the old title when reading historical evidence; live hierarchy is asserted separately.
export const swStoryEntries = index => Object.values(index.entries).filter(entry => entry.type === 'story' && (entry.title === 'SW/Header' || entry.title === 'SW/Header/Dropdown'))
export const retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
.map(entry => entry.id === 'sw-header--composed-controls' ? { ...entry, name: 'Dropdown', title: 'SW/Header' } : entry)
export const inventorySignature = entries => entries.map(entry => [entry.id, entry.name, entry.tags]).sort((a, b) => a[0].localeCompare(b[0]))