fix(sw): stabilize theme regression test and update integration docs
This commit is contained in:
1 parent
73e30242f2
commit
47972c55ff
11 files changed
+186
-23
No files matched your search
@@ -268,6 +268,8 @@ AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`C
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
**Git Submodule + npm Workspace로 연결한 경우**(Registry 없이 HMDesign 저장소를 예: `HMDesign/` 폴더에 Submodule로 고정하고 `workspaces`에 `HMDesign/packages/<패키지>`를 등록한 구성): `node_modules/@hm-design/<패키지>`는 `HMDesign/packages/<패키지>`로 연결되므로 지침 위치는 위 표와 같습니다(`HMDesign/packages/<패키지>/ai/`와 같은 파일). 공개 진입점은 `dist/`를 가리키므로 HMDesign 폴더 안에서 `npm ci`와 `npm run build:packages`를 먼저 실행해야 하며, `dist/`가 없으면 Import 오류를 컴포넌트 결함으로 보고하지 말고 빌드 필요로 보고합니다. HMDesign 저장소 루트의 `AGENTS.md`·`CLAUDE.md`는 HMDesign 개발용이므로 소비 프로젝트 지침으로 쓰지 않습니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -268,6 +268,8 @@ AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`C
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
**Git Submodule + npm Workspace로 연결한 경우**(Registry 없이 HMDesign 저장소를 예: `HMDesign/` 폴더에 Submodule로 고정하고 `workspaces`에 `HMDesign/packages/<패키지>`를 등록한 구성): `node_modules/@hm-design/<패키지>`는 `HMDesign/packages/<패키지>`로 연결되므로 지침 위치는 위 표와 같습니다(`HMDesign/packages/<패키지>/ai/`와 같은 파일). 공개 진입점은 `dist/`를 가리키므로 HMDesign 폴더 안에서 `npm ci`와 `npm run build:packages`를 먼저 실행해야 하며, `dist/`가 없으면 Import 오류를 컴포넌트 결함으로 보고하지 말고 빌드 필요로 보고합니다. HMDesign 저장소 루트의 `AGENTS.md`·`CLAUDE.md`는 HMDesign 개발용이므로 소비 프로젝트 지침으로 쓰지 않습니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -268,6 +268,8 @@ AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`C
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
**Git Submodule + npm Workspace로 연결한 경우**(Registry 없이 HMDesign 저장소를 예: `HMDesign/` 폴더에 Submodule로 고정하고 `workspaces`에 `HMDesign/packages/<패키지>`를 등록한 구성): `node_modules/@hm-design/<패키지>`는 `HMDesign/packages/<패키지>`로 연결되므로 지침 위치는 위 표와 같습니다(`HMDesign/packages/<패키지>/ai/`와 같은 파일). 공개 진입점은 `dist/`를 가리키므로 HMDesign 폴더 안에서 `npm ci`와 `npm run build:packages`를 먼저 실행해야 하며, `dist/`가 없으면 Import 오류를 컴포넌트 결함으로 보고하지 말고 빌드 필요로 보고합니다. HMDesign 저장소 루트의 `AGENTS.md`·`CLAUDE.md`는 HMDesign 개발용이므로 소비 프로젝트 지침으로 쓰지 않습니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -5,7 +5,8 @@
|
||||
// 2) Regression: every existing SW Story renders identically with the new cascade and with the legacy cascade
|
||||
// (token blocks re-declared on every `.hm-sw-header, .hm-sw-theme`, i.e. the code before this change): computed
|
||||
// font / color / geometry of every SW element and an element screenshot comparison. No Story sets an override,
|
||||
// so any difference would be a regression.
|
||||
// so any difference would be a regression. Pixels are compared with a control shot taken after the CURRENT token
|
||||
// blocks are injected once more (2026-10-20): re-declaring them alone re-rasterizes a few gradient pixels.
|
||||
// Usage: node packages/sw/tests/theme-inheritance.browser.mjs <storybook-url> <out-dir>
|
||||
import { createRequire } from 'node:module'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
@@ -137,13 +138,19 @@ const fmt = map => Object.entries(map).map(([family, count]) => `${count}× ${fa
|
||||
|
||||
// ---------- 2. Regression: new cascade vs legacy cascade on every existing SW Story ----------
|
||||
const here = path.dirname(fileURLToPath(import.meta.url))
|
||||
const legacy = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
||||
const outermost = ':is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *)'
|
||||
const tokenBlocks = (await Promise.all(['sw.css', 'sw-viewer.css', 'sw-footer.css'].map(file => readFile(path.join(here, '../src/theme', file), 'utf8'))))
|
||||
.map(text => {
|
||||
const start = text.indexOf(':is(.hm-sw-header, .hm-sw-theme):not(:where(.hm-sw-header, .hm-sw-theme) *) {')
|
||||
const start = text.indexOf(`${outermost} {`)
|
||||
const end = text.indexOf('\n}', start)
|
||||
if (start < 0 || end < 0) throw new Error('token block not found')
|
||||
return `.hm-sw-header,\n.hm-sw-theme ${text.slice(text.indexOf('{', start), end + 2)}`
|
||||
}).join('\n')
|
||||
return text.slice(text.indexOf('{', start), end + 2)
|
||||
})
|
||||
const legacy = tokenBlocks.map(block => `.hm-sw-header,\n.hm-sw-theme ${block}`).join('\n')
|
||||
// Control (2026-10-20): the SAME token blocks on the SAME selector, i.e. the current cascade declared once more.
|
||||
// Re-declaring the token blocks re-rasterizes a few gradient pixels (±1–2 per channel at the end of the Footer
|
||||
// command row) even though no value changes, so the legacy shot is also compared with this control shot.
|
||||
const control = tokenBlocks.map(block => `${outermost} ${block}`).join('\n')
|
||||
const index = await (await fetch(`${storybook}/index.json`)).json()
|
||||
const ids = Object.values(index.entries).filter(entry => entry.type === 'story' && /^sw-(header|popup|viewer|footer|home-menu)/.test(entry.id)).map(entry => entry.id)
|
||||
const snapshot = page => page.evaluate(() => [...document.querySelectorAll('.hm-sw-header, .hm-sw-theme')].flatMap(root => [root, ...root.querySelectorAll('*')]).map(element => {
|
||||
@@ -161,21 +168,37 @@ for (const width of [1920, 1440, 1180, 900, 560]) for (const id of ids) {
|
||||
await page.waitForTimeout(150)
|
||||
// Pixels that change between two shots of the SAME CSS (live Story content) are masked out as noise.
|
||||
const shotA2 = await shot()
|
||||
// Control: the current cascade declared again. Its computed values must equal the first state.
|
||||
await page.addStyleTag({ content: control })
|
||||
await page.waitForTimeout(150)
|
||||
const c = await snapshot(page)
|
||||
const shotC = await shot()
|
||||
await page.waitForTimeout(150)
|
||||
const shotC2 = await shot()
|
||||
await page.addStyleTag({ content: legacy })
|
||||
await page.waitForTimeout(150)
|
||||
const b = await snapshot(page)
|
||||
const shotB = await shot()
|
||||
const changed = a.length === b.length ? a.filter((value, i) => value !== b[i]).length : -1
|
||||
let pixels = -1, noise = 0
|
||||
if (shotA.width === shotB.width && shotA.height === shotB.height && shotA2.height === shotA.height) {
|
||||
const changedValues = (x, y) => x.length === y.length ? x.filter((value, i) => value !== y[i]).length : -1
|
||||
const changed = changedValues(a, b), controlChanged = changedValues(a, c)
|
||||
let pixels = -1, noise = 0, controlPixels = -1
|
||||
if ([shotA2, shotC, shotC2, shotB].every(item => item.width === shotA.width && item.height === shotA.height)) {
|
||||
pixels = 0
|
||||
controlPixels = 0
|
||||
for (let i = 0; i < shotA.data.length; i += 4) {
|
||||
const same = (x, y) => x[i] === y[i] && x[i + 1] === y[i + 1] && x[i + 2] === y[i + 2] && x[i + 3] === y[i + 3]
|
||||
if (!same(shotA.data, shotA2.data)) noise++
|
||||
else if (!same(shotA.data, shotB.data)) pixels++
|
||||
if (!same(shotA.data, shotC.data)) controlPixels++
|
||||
// Live Story content changing at rest (before or after the control) is masked out as noise.
|
||||
if (!same(shotA.data, shotA2.data) || !same(shotC.data, shotC2.data)) noise++
|
||||
// First shot and control shot both render the unchanged cascade (re-raster artifacts appear only in the
|
||||
// control; a transient relayout may appear in either one). The legacy cascade fails when its pixel matches
|
||||
// neither of them exactly.
|
||||
else if (!same(shotC.data, shotB.data) && !same(shotA.data, shotB.data)) pixels++
|
||||
}
|
||||
}
|
||||
regression.push({ id, width, elements: a.length, changedElements: changed, diffPixels: pixels, maskedNoisePixels: noise, result: changed === 0 && pixels === 0 ? 'PASS' : 'FAIL' })
|
||||
// Computed values: legacy vs first state (and control vs first state). Pixels: legacy vs first state and control.
|
||||
regression.push({ id, width, elements: a.length, changedElements: changed, controlChangedElements: controlChanged, diffPixels: pixels,
|
||||
controlRasterPixels: controlPixels, maskedNoisePixels: noise, result: changed === 0 && controlChanged === 0 && pixels === 0 ? 'PASS' : 'FAIL' })
|
||||
await page.close()
|
||||
}
|
||||
// Negative control: the legacy cascade must reproduce the reported defect on the themed Story (the comparison above is
|
||||
|
||||
@@ -268,6 +268,8 @@ AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`C
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
**Git Submodule + npm Workspace로 연결한 경우**(Registry 없이 HMDesign 저장소를 예: `HMDesign/` 폴더에 Submodule로 고정하고 `workspaces`에 `HMDesign/packages/<패키지>`를 등록한 구성): `node_modules/@hm-design/<패키지>`는 `HMDesign/packages/<패키지>`로 연결되므로 지침 위치는 위 표와 같습니다(`HMDesign/packages/<패키지>/ai/`와 같은 파일). 공개 진입점은 `dist/`를 가리키므로 HMDesign 폴더 안에서 `npm ci`와 `npm run build:packages`를 먼저 실행해야 하며, `dist/`가 없으면 Import 오류를 컴포넌트 결함으로 보고하지 말고 빌드 필요로 보고합니다. HMDesign 저장소 루트의 `AGENTS.md`·`CLAUDE.md`는 HMDesign 개발용이므로 소비 프로젝트 지침으로 쓰지 않습니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
Reference in new issue
Block a user