@things-factory/figure-ui 10.1.29 → 10.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-ui",
3
- "version": "10.1.29",
3
+ "version": "10.1.31",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -37,8 +37,8 @@
37
37
  "@operato/shell": "^10.0.0",
38
38
  "@operato/styles": "^10.0.0",
39
39
  "@operato/utils": "^10.0.0",
40
- "@things-factory/figure-service": "^10.1.29",
40
+ "@things-factory/figure-service": "^10.1.31",
41
41
  "three": "^0.185.1"
42
42
  },
43
- "gitHead": "e6bf9dec003fe5e794746aaeb5852b3b50478232"
43
+ "gitHead": "5213e6f9913b40ea2618a2111d9884b792dbaa01"
44
44
  }
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict'
2
- import { readFileSync } from 'node:fs'
3
- import { join } from 'node:path'
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
3
+ import { join, relative } from 'node:path'
4
4
  import test from 'node:test'
5
5
 
6
6
  /**
@@ -22,10 +22,13 @@ import test from 'node:test'
22
22
  */
23
23
 
24
24
  const HERE = import.meta.dirname
25
- const BRAND = join(HERE, '..', 'client', 'themes', 'brand.css')
26
- const THEMES = ['light.css', 'dark.css'].map(f => join(HERE, '..', 'client', 'themes', f))
27
- /** 저작 하네스. 이 저장소 안의 두 번째 사본 자리다. */
28
- const HARNESS = join(HERE, '..', '..', '..', 'test', 'browser', 'modeller-page.mjs')
25
+ const FOLDER = 'harness-theme'
26
+ const BRAND = join(HERE, '..', 'client', FOLDER, 'brand.css')
27
+ const THEMES = ['light.css', 'dark.css'].map(f => join(HERE, '..', 'client', FOLDER, f))
28
+ /** 저작 하네스 둘. 이 저장소 안의 두 번째 사본 자리다. */
29
+ const HARNESSES = ['modeller-page.mjs', 'real-modeller.mjs'].map(f =>
30
+ join(HERE, '..', '..', '..', 'test', 'browser', f)
31
+ )
29
32
 
30
33
  const read = (path: string) => readFileSync(path, 'utf8')
31
34
 
@@ -69,7 +72,7 @@ function colorOf(css: string, name: string): string {
69
72
  test('브랜드 색의 정본이 있다 — 없으면 아래 테스트가 다 통과해 버린다', () => {
70
73
  const brand = definitions(read(BRAND))
71
74
 
72
- for (const name of ['--brand-figure', '--brand-figure-2', '--brand-figure-wash']) {
75
+ for (const name of ['--figure-brand', '--figure-brand-2', '--figure-brand-wash']) {
73
76
  assert.ok(brand.has(name), `${name} 이 brand.css 에 없다`)
74
77
  }
75
78
  })
@@ -92,30 +95,108 @@ test('하네스가 색을 스스로 정하지 않는다 — 이것이 원래 결
92
95
  * 하네스에서 보이는 화면과 앱에서 보일 화면이 다른 색이었고, 어느 쪽이 맞는지 알 방법이
93
96
  * 없었다.
94
97
  */
95
- const css = read(HARNESS)
96
- const own = [...definitions(css).keys()].filter(
97
- name => name.startsWith('--md-sys-color-') || name.startsWith('--brand-')
98
+ for (const path of HARNESSES) {
99
+ const own = [...definitions(read(path)).keys()].filter(
100
+ name => name.startsWith('--md-sys-color-') || name.startsWith('--figure-')
101
+ )
102
+
103
+ assert.deepEqual(own, [], `${path} 가 색을 스스로 정한다: ${own.join(' · ')}`)
104
+ }
105
+ })
106
+
107
+ /*
108
+ 이 셋은 `client/themes` 에 있었다. `@things-factory/builder` 의 `ThemeOverridePlugin` 이 모든 모듈의
109
+ `client/themes/*.css` 를 **파일 이름 하나당 하나만** 테마 묶음에 싣는다(앱이 맨 앞). 그래서 그 자리에
110
+ 두면 두 가지로 무너진다 — 앱이 같은 이름을 갖는 순간 통째로 빠지거나(실제로 `light.css` · `dark.css`
111
+ 가 figure 앱에서 그랬다), 이름을 비켜서 두면 앞의 것과 같은 선택자로 md-sys 칸을 덮어 묶음 순서가
112
+ 색을 정한다. md-sys 슬롯은 모듈이 선언하지 않는다(ADR-0060 결정 2).
113
+
114
+ 폴더가 `themes` 가 아니면 플러그인이 아예 보지 않는다. 그것이 이 폴더 이름이 하는 일이므로,
115
+ 누가 되돌려 놓으면 여기서 빨강이 된다.
116
+ */
117
+ test('figure-ui 는 client/themes 를 갖지 않는다 — 플러그인이 보는 자리다', () => {
118
+ const at = join(HERE, '..', 'client', 'themes')
119
+
120
+ assert.ok(
121
+ !existsSync(at),
122
+ `client/themes 가 다시 생겼다. 테마 묶음이 이 파일들을 집어 앱 색을 순서로 정하게 된다: ${at}`
98
123
  )
124
+ })
99
125
 
100
- assert.deepEqual(own, [], `하네스가 색을 스스로 정한다: ${own.join(' · ')}`)
126
+ /*
127
+ `copy:files` 는 `copyfiles` 이고, 그것은 **지운 파일을 지우지 않는다.** `build:client` 도 clean 을 하지 않는다
128
+ (`copy:files && tsc` 뿐). 그래서 소스에서 css 를 옮기거나 지워도 산출물에는 옛 파일이 남고, 테마 묶음을 고르는
129
+ `ThemeOverridePlugin` 은 **산출물**을 glob 하므로 그 옛 파일을 계속 집는다.
130
+
131
+ 이 폴더를 옮길 때 실제로 그랬다 — 소스는 `harness-theme` 인데 `dist-client/themes/` 의 세 파일이 그대로 남아
132
+ 있었다. 위의 시험들은 소스를 읽으니 전부 초록이었다. UX 레인이 warehouse 에서 같은 것을 밟았다(화면이 옛 색
133
+ 그대로였고 시험은 초록이었다).
134
+
135
+ 그래서 산출물에만 있는 css 를 여기서 잡는다. 산출물이 아직 없으면 볼 것이 없다 — 그때는 건너뛴다.
136
+ */
137
+ test('산출물에 소스에 없는 css 가 없다 — copyfiles 는 지운 것을 지우지 않는다', () => {
138
+ const source = join(HERE, '..', 'client')
139
+ const built = join(HERE, '..', 'dist-client')
140
+
141
+ if (!existsSync(built)) return
142
+
143
+ const cssUnder = (root: string): string[] => {
144
+ const found: string[] = []
145
+ const walk = (at: string) => {
146
+ for (const entry of readdirSync(at)) {
147
+ const full = join(at, entry)
148
+ if (statSync(full).isDirectory()) walk(full)
149
+ else if (entry.endsWith('.css')) found.push(relative(root, full))
150
+ }
151
+ }
152
+ walk(root)
153
+ return found.sort()
154
+ }
155
+
156
+ const orphans = cssUnder(built).filter(at => !existsSync(join(source, at)))
157
+
158
+ assert.deepEqual(
159
+ orphans,
160
+ [],
161
+ `산출물에만 있는 css: ${orphans.join(' · ')}\n` +
162
+ `소스에서 옮기거나 지운 것이 산출물에 남아 있다. 테마 묶음은 산출물을 보므로 이것이 계속 실린다.\n` +
163
+ ` rm -rf packages/figure-ui/dist-client && npm run build:client`
164
+ )
165
+ })
166
+
167
+ /*
168
+ 옮긴 자리를 하네스가 가리키지 않으면 하네스는 스타일시트 없이 뜬다 — 오류 없이 무채색 화면이
169
+ 되고, 아래 대비 시험들은 **어느 화면에도 없는 값**을 재게 된다.
170
+ */
171
+ test('하네스 둘이 옮긴 팔레트를 가리킨다 — 밝은 것과 어두운 것 모두', () => {
172
+ for (const path of HARNESSES) {
173
+ const html = read(path)
174
+
175
+ for (const file of ['light.css', 'dark.css']) {
176
+ assert.ok(
177
+ html.includes(`/pkg/figure-ui/dist-client/${FOLDER}/${file}`),
178
+ `${path} 가 ${FOLDER}/${file} 를 싣지 않는다`
179
+ )
180
+ }
181
+ }
101
182
  })
102
183
 
103
184
  test('밝은 면과 어두운 면을 함께 든다 — 한쪽만 있으면 어두운 화면이 안 바뀐다', () => {
104
185
  const brand = read(BRAND)
105
186
 
106
- assert.ok(definitions(brand).has('--brand-figure'), '밝은 면용이 없다')
107
- assert.ok(definitions(brand).has('--brand-figure-2'), '어두운 면용이 없다')
187
+ assert.ok(definitions(brand).has('--figure-brand'), '밝은 면용이 없다')
188
+ assert.ok(definitions(brand).has('--figure-brand-2'), '어두운 면용이 없다')
108
189
  })
109
190
 
110
191
  test('브랜드 색 위의 흰 글자가 읽힌다 — 작은 글자 기준 4.5', () => {
111
- const figure = colorOf(read(BRAND), '--brand-figure')
192
+ const figure = colorOf(read(BRAND), '--figure-brand')
112
193
  const measured = contrast(figure, '#ffffff')
113
194
 
114
195
  assert.ok(measured >= 4.5, `${figure} 위 흰 글자 대비가 ${measured.toFixed(2)} 다`)
115
196
  })
116
197
 
117
198
  test('어두운 면 위의 브랜드 색이 읽힌다 — 그쪽은 -2 가 맡는다', () => {
118
- const bright = colorOf(read(BRAND), '--brand-figure-2')
199
+ const bright = colorOf(read(BRAND), '--figure-brand-2')
119
200
  const surface = colorOf(read(THEMES[1]!), '--md-sys-color-surface')
120
201
  const measured = contrast(bright, surface)
121
202
 
@@ -124,9 +205,9 @@ test('어두운 면 위의 브랜드 색이 읽힌다 — 그쪽은 -2 가 맡
124
205
 
125
206
  test('밝은 쪽 값을 어두운 면에 쓰지 않는다 — 이름이 뜻이고 값은 자리에 따라 다르다', () => {
126
207
  /*
127
- * `--brand-figure` 를 어두운 면에 그대로 쓰면 대비 1.6 이라 사라진다. 그 실수를 잡는다.
208
+ * `--figure-brand` 를 어두운 면에 그대로 쓰면 대비 1.6 이라 사라진다. 그 실수를 잡는다.
128
209
  */
129
- const figure = colorOf(read(BRAND), '--brand-figure')
210
+ const figure = colorOf(read(BRAND), '--figure-brand')
130
211
  const darkSurface = colorOf(read(THEMES[1]!), '--md-sys-color-surface')
131
212
 
132
213
  assert.ok(
@@ -135,7 +216,7 @@ test('밝은 쪽 값을 어두운 면에 쓰지 않는다 — 이름이 뜻이
135
216
  )
136
217
 
137
218
  const dark = read(THEMES[1]!)
138
- assert.match(dark, /--md-sys-color-primary:\s*var\(--brand-figure-2\)/, '어두운 테마가 -2 를 안 쓴다')
219
+ assert.match(dark, /--md-sys-color-primary:\s*var\(--figure-brand-2\)/, '어두운 테마가 -2 를 안 쓴다')
139
220
  })
140
221
 
141
222
  test('글자 셋이 다 읽힌다 — 흐린 것까지', () => {