@things-factory/figure-ui 10.1.46 → 10.1.47
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/client/modeller/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +61 -81
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +221 -263
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +531 -223
- package/client/modeller/v3-fields.ts +62 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +93 -9
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +5 -2
- package/dist-client/modeller/figure-v3-links.js +64 -82
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +5 -2
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +2 -2
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +29 -37
- package/dist-client/modeller/v3-canvas.js +188 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +58 -1
- package/dist-client/modeller/v3-editor.js +555 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +30 -0
- package/dist-client/modeller/v3-fields.js +46 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +30 -1
- package/dist-client/modeller/v3-links.js +94 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +62 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +77 -6
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +84 -5
- package/translations/ja.json +86 -7
- package/translations/ko.json +84 -5
- package/translations/ms.json +86 -7
- package/translations/zh.json +86 -7
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **V3 성숙도 탭은 V2 리포트를 V3 수로 다시 보인다** (사용자 2026-09-24: 「분석부분도 많이 부족하고, 왜 이렇게 너프되었는지」).
|
|
3
|
+
*
|
|
4
|
+
* V2 리포트(figure-report.ts)는 개수 · 등급과 눈금 · 등급을 정한 한도 · 여섯 축 radar · 한도 막대 · 그리는 비용 · 재사용
|
|
5
|
+
* 추이를 보였다. 여기서는 그 수들이 V3 자산에서 V2 의 셈 그대로 나오는지, V3 렌더러가 실제로 나눠 쓰는 것을 세는지,
|
|
6
|
+
* 화면이 그 순서와 말로 그리는지를 본다.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
import { addV3Box, addV3Primitive, applyV3Authoring, compileV3Asset, createV3Asset, LIMITS, v3ScoreOf } from '@hatiolab/figure-model'
|
|
14
|
+
import type { V3Asset } from '@hatiolab/figure-model'
|
|
15
|
+
import { acquireV3Geometry, releaseV3Geometry } from '@hatiolab/things-scene'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
BOARD_ASSETS,
|
|
19
|
+
budgetOf,
|
|
20
|
+
curveLayout,
|
|
21
|
+
decadeSteps,
|
|
22
|
+
GRADE_SCALE,
|
|
23
|
+
gradeOf,
|
|
24
|
+
V3_BUDGET_KEYS,
|
|
25
|
+
v3ReportOf
|
|
26
|
+
} from '../client/modeller/v3-maturity.ts'
|
|
27
|
+
import { capacityFrom } from '../client/modeller/scene-budget.ts'
|
|
28
|
+
|
|
29
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
30
|
+
const verdict = read('../client/modeller/figure-v3-verdict.ts')
|
|
31
|
+
const maturity = read('../client/modeller/v3-maturity.ts')
|
|
32
|
+
|
|
33
|
+
/** An asset with the given tokens in its palette, so boxes can differ in colour. */
|
|
34
|
+
function withPalette(...tokens: string[]): V3Asset {
|
|
35
|
+
const asset = createV3Asset('Probe')
|
|
36
|
+
for (const token of tokens) asset.palette[token] = '#336699'
|
|
37
|
+
return asset
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cube = (id: string, x: number, token = 'palette.primary', extra: Record<string, unknown> = {}) => ({
|
|
41
|
+
id,
|
|
42
|
+
size: { x: 100, y: 100, z: 100 },
|
|
43
|
+
position: { x, y: 50, z: 0 },
|
|
44
|
+
material: { token, ...extra }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('부품이 없으면 리포트가 없다 — 0 으로 그리지 않는다', () => {
|
|
48
|
+
assert.equal(v3ReportOf(createV3Asset('Empty')), undefined)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('등급은 V2 의 눈금이다 — 한도의 절반 A · 4분의 3 B · 한도 C · 넘치면 D', () => {
|
|
52
|
+
assert.deepEqual(GRADE_SCALE, [
|
|
53
|
+
['A', 0.5],
|
|
54
|
+
['B', 0.75],
|
|
55
|
+
['C', 1]
|
|
56
|
+
])
|
|
57
|
+
assert.equal(gradeOf(0.5), 'A')
|
|
58
|
+
assert.equal(gradeOf(0.51), 'B')
|
|
59
|
+
assert.equal(gradeOf(0.75), 'B')
|
|
60
|
+
assert.equal(gradeOf(1), 'C')
|
|
61
|
+
assert.equal(gradeOf(1.01), 'D')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('등급은 figure-model 의 v3ScoreOf 그대로다 — 목록과 이 탭이 한 셈으로 매긴다', () => {
|
|
65
|
+
let asset = createV3Asset('Probe')
|
|
66
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
67
|
+
const one = v3ReportOf(asset)!
|
|
68
|
+
assert.deepEqual(one.score, v3ScoreOf(asset))
|
|
69
|
+
assert.deepEqual(V3_BUDGET_KEYS, one.score.budgets.map(b => b.key), '화면이 말을 찾는 키와 셈의 키가 같다')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('draw call 한도 3 은 없다 — 한 화면 166 대 목표에 견준다(수석 아키텍트 판정 09-24)', () => {
|
|
73
|
+
let asset = createV3Asset('Probe')
|
|
74
|
+
for (let i = 0; i < 4; i++) asset = addV3Box(asset, cube(`b${i}`, i * 200))
|
|
75
|
+
const four = v3ReportOf(asset)!
|
|
76
|
+
assert.equal(four.score.grade, 'A', '예전 한도 3 으로는 D 였다')
|
|
77
|
+
const groups = four.score.budgets.find(b => b.key === 'groups')!
|
|
78
|
+
assert.equal(groups.limit, LIMITS.groupsOnScreen / LIMITS.screenInstances)
|
|
79
|
+
assert.ok(four.capacity.instances >= 2 * LIMITS.screenInstances, 'A 는 목표의 두 배가 들어간다는 뜻')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('감춘 부품은 그리지 않으므로 한도에 들지 않는다 — v3CostOf 와 같은 셈', () => {
|
|
83
|
+
let asset = createV3Asset('Probe')
|
|
84
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
85
|
+
asset = addV3Box(asset, cube('b', 200))
|
|
86
|
+
const report = v3ReportOf(asset)!
|
|
87
|
+
assert.equal(report.score.budgets.find(b => b.key === 'groups')!.used, report.cost.groups)
|
|
88
|
+
assert.equal(report.capacity.instances, capacityFrom(report.cost.groups, report.cost.triangles)!.instances)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('투명 재질은 서로 다른 재질로 센다 — 둘이면 한도 1 을 넘고, 그것이 등급을 정한다', () => {
|
|
92
|
+
let asset = withPalette('glass.a', 'glass.b')
|
|
93
|
+
asset = addV3Box(asset, cube('a', 0, 'glass.a', { transparent: true }))
|
|
94
|
+
asset = addV3Box(asset, cube('b', 200, 'glass.b', { transparent: true }))
|
|
95
|
+
const report = v3ReportOf(asset)!
|
|
96
|
+
assert.equal(report.score.budgets.find(b => b.key === 'transparent')!.used, 2)
|
|
97
|
+
assert.equal(report.score.tightest.key, 'transparent')
|
|
98
|
+
assert.equal(report.score.grade, 'D')
|
|
99
|
+
|
|
100
|
+
let same = withPalette('glass.a')
|
|
101
|
+
same = addV3Box(same, cube('a', 0, 'glass.a', { transparent: true }))
|
|
102
|
+
same = addV3Box(same, cube('b', 200, 'glass.a', { transparent: true }))
|
|
103
|
+
assert.equal(v3ReportOf(same)!.score.budgets.find(b => b.key === 'transparent')!.used, 1, '같은 재질은 하나로 나눠 쓴다')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('상태를 따라 움직이는 부품을 센다 — V2 의 「따로 움직이는 부품」', () => {
|
|
107
|
+
let asset = createV3Asset('Probe')
|
|
108
|
+
asset = addV3Box(asset, cube('base', 0))
|
|
109
|
+
asset = addV3Box(asset, cube('arm', 200))
|
|
110
|
+
assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 0, '전제: 움직임이 없으면 0')
|
|
111
|
+
|
|
112
|
+
asset = applyV3Authoring(asset, {
|
|
113
|
+
kind: 'add-motion',
|
|
114
|
+
part: 'arm',
|
|
115
|
+
motion: { kind: 'turn', axis: 'y' },
|
|
116
|
+
state: { id: 'arm.turn', unit: 'deg', min: 0, max: 90 }
|
|
117
|
+
})
|
|
118
|
+
assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 1, '도는 팔 하나')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('서로 다른 형상은 things-scene 이 정점 버퍼를 나눠 쓰는 수와 같다', () => {
|
|
122
|
+
let asset = createV3Asset('Probe')
|
|
123
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
124
|
+
asset = addV3Box(asset, cube('b', 200))
|
|
125
|
+
asset = addV3Box(asset, { id: 'c', size: { x: 100, y: 300, z: 100 }, position: { x: 400, y: 150, z: 0 } })
|
|
126
|
+
asset = addV3Primitive(asset, { id: 'd', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 600, y: 50, z: 0 } })
|
|
127
|
+
asset = addV3Primitive(asset, { id: 'e', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 800, y: 50, z: 0 } })
|
|
128
|
+
asset = addV3Primitive(asset, { id: 'f', kind: 'cylinder', dimensions: { radius: 40, length: 100 }, position: { x: 1000, y: 50, z: 0 } })
|
|
129
|
+
// The same cylinder, rounder: another vertex buffer in the renderer, so another shape here.
|
|
130
|
+
asset.appearance.find(look => look.target === 'f')!.segments = 24
|
|
131
|
+
const report = v3ReportOf(asset)!
|
|
132
|
+
|
|
133
|
+
const bodies = compileV3Asset(asset).evaluate().geometry
|
|
134
|
+
const held = bodies.map(body => acquireV3Geometry((body as { shape: never }).shape, (body as { segments?: number }).segments))
|
|
135
|
+
try {
|
|
136
|
+
assert.equal(report.distinctShapes, new Set(held).size, '렌더러의 은행과 같은 키')
|
|
137
|
+
} finally {
|
|
138
|
+
held.forEach(geometry => releaseV3Geometry(geometry))
|
|
139
|
+
}
|
|
140
|
+
assert.equal(report.distinctShapes, 4, '같은 상자 둘 · 키 큰 상자 · 같은 원기둥 둘 · 분할이 다른 원기둥')
|
|
141
|
+
assert.deepEqual(report.reuse, { distinct: 4, parts: 6, saved: 2 })
|
|
142
|
+
assert.equal(report.axes.find(axis => axis.key === 'reuse')!.ratio, 1 - 4 / 6)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('여섯 축 — V2 의 순서, 잴 수 없는 둘은 비율 없이 까닭을 단다', () => {
|
|
146
|
+
let asset = withPalette('palette.accent')
|
|
147
|
+
asset = addV3Box(asset, cube('a', 50))
|
|
148
|
+
asset = addV3Box(asset, cube('b', 150, 'palette.accent'))
|
|
149
|
+
const axes = v3ReportOf(asset)!.axes
|
|
150
|
+
|
|
151
|
+
assert.deepEqual(
|
|
152
|
+
axes.map(axis => axis.key),
|
|
153
|
+
['scale', 'consistency', 'reuse', 'detail', 'reactive', 'readable']
|
|
154
|
+
)
|
|
155
|
+
const byKey = Object.fromEntries(axes.map(axis => [axis.key, axis]))
|
|
156
|
+
assert.equal(byKey.detail!.ratio, undefined)
|
|
157
|
+
assert.equal(byKey.detail!.blocked, 'figure.text.maturity-blocked-no-detail-level', '디테일 등급은 V3 에 없다(ADR-0087)')
|
|
158
|
+
assert.equal(byKey.reactive!.ratio, undefined)
|
|
159
|
+
assert.equal(byKey.reactive!.blocked, 'figure.text.maturity-blocked-no-binding')
|
|
160
|
+
|
|
161
|
+
assert.equal(byKey.scale!.ratio, 1, `두 부품이면 ${BOARD_ASSETS} 개 목표를 넘긴다`)
|
|
162
|
+
assert.equal(byKey.consistency!.ratio, 1, '재질 둘은 한도 셋 안이다')
|
|
163
|
+
// Two colours, each half of the footprint, no background: entropy ln 2 over the ceiling ln 4.
|
|
164
|
+
assert.equal(byKey.readable!.ratio, Math.log(2) / Math.log(LIMITS.distinctMaterials + 1))
|
|
165
|
+
assert.equal(byKey.readable!.reading, '2')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('재질이 한도를 넘으면 일관성이 한 칸 내려간다 — V2 의 셋 중 하나', () => {
|
|
169
|
+
let asset = withPalette('t1', 't2', 't3')
|
|
170
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
171
|
+
asset = addV3Box(asset, cube('b', 200, 't1'))
|
|
172
|
+
asset = addV3Box(asset, cube('c', 400, 't2'))
|
|
173
|
+
asset = addV3Box(asset, cube('d', 600, 't3'))
|
|
174
|
+
const report = v3ReportOf(asset)!
|
|
175
|
+
assert.equal(report.distinctMaterials, 4)
|
|
176
|
+
const consistency = report.axes.find(axis => axis.key === 'consistency')!
|
|
177
|
+
assert.equal(consistency.ratio, 2 / 3)
|
|
178
|
+
assert.equal(consistency.readingKey, 'figure.text.maturity-consistency-faults')
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('한 색이 발자국을 다 덮으면 식별성은 0 이다 — 위에서 보면 한 덩어리', () => {
|
|
182
|
+
let asset = createV3Asset('Probe')
|
|
183
|
+
asset = addV3Box(asset, { id: 'slab', size: { x: 400, y: 20, z: 400 }, position: { x: 0, y: 10, z: 0 } })
|
|
184
|
+
asset = addV3Box(asset, { id: 'under', size: { x: 100, y: 5, z: 100 }, position: { x: 0, y: 2.5, z: 0 } })
|
|
185
|
+
assert.equal(v3ReportOf(asset)!.axes.find(axis => axis.key === 'readable')!.ratio, 0, '아래 부품은 가려진다')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('추이 — 1 에서 1만까지 1·2·5, 삼각형과 draw call 은 오르고 정점 버퍼와 재질은 평평하다', () => {
|
|
189
|
+
assert.deepEqual(decadeSteps(1, 100), [1, 2, 5, 10, 20, 50, 100])
|
|
190
|
+
let asset = createV3Asset('Probe')
|
|
191
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
192
|
+
asset = addV3Box(asset, cube('b', 200))
|
|
193
|
+
const report = v3ReportOf(asset)!
|
|
194
|
+
const last = report.curve[report.curve.length - 1]!
|
|
195
|
+
assert.equal(report.curve[0]!.instances, 1)
|
|
196
|
+
assert.equal(last.instances, 10000)
|
|
197
|
+
assert.equal(last.triangles, report.cost.triangles * 10000)
|
|
198
|
+
assert.equal(last.drawCalls, report.cost.groups * 10000)
|
|
199
|
+
assert.ok(report.curve.every(p => p.geometries === report.distinctShapes && p.materials === report.distinctMaterials))
|
|
200
|
+
|
|
201
|
+
const layout = curveLayout(report.curve)!
|
|
202
|
+
assert.deepEqual(layout.lines.map(line => [line.key, line.flat, line.dashed]), [
|
|
203
|
+
['triangles', false, false],
|
|
204
|
+
['drawCalls', false, false],
|
|
205
|
+
['geometries', true, false],
|
|
206
|
+
['materials', true, true]
|
|
207
|
+
])
|
|
208
|
+
assert.equal(layout.overlap, 1, '형상 하나 · 재질 하나 — 평평한 선이 겹친다고 적는다')
|
|
209
|
+
assert.deepEqual(layout.xTicks.map(t => t.label), ['1', '10', '100', '1k', '10k'])
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('화면은 V2 리포트의 차례로 그린다 — 개수 · 축 · 한도 · 그리는 비용 · 추이 · 발행 판정', () => {
|
|
213
|
+
const order = ['this.renderVerdict(report)', 'this.renderAxes(report)', 'this.renderBudgets(report)', 'this.renderMetrics(report)', 'this.renderCurve()', 'this.renderGate()']
|
|
214
|
+
// From the full report, past the early return that shows only guidance and the release verdict.
|
|
215
|
+
const at = order.map(call => verdict.indexOf(call, verdict.indexOf('${this.renderVerdict(report)}')))
|
|
216
|
+
assert.ok(at.every(i => i > 0), `모든 절을 그린다: ${order.filter((_, i) => at[i] < 0)}`)
|
|
217
|
+
assert.deepEqual([...at].sort((a, b) => a - b), at)
|
|
218
|
+
assert.ok(verdict.includes('...GRADE_SCALE.map('), '눈금은 등급을 셈한 경계에서 그린다 — 따로 적으면 어긋난다')
|
|
219
|
+
assert.ok(verdict.includes('SidePanelStyles'), '옆 칸의 한 모양을 쓴다')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('radar 와 추이 그림의 조각은 svg 템플릿이다 — html 로 만들면 소리 없이 안 그려진다', () => {
|
|
223
|
+
for (const block of verdict.match(/svg`[\s\S]*?<\/svg>/g) ?? []) {
|
|
224
|
+
assert.doesNotMatch(block, /html`\s*<\s*(polygon|line|circle|rect|path|text|g)\b/)
|
|
225
|
+
}
|
|
226
|
+
assert.ok((verdict.match(/svg`/g) ?? []).length >= 8, '전제: 조각을 센다')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('화면이 키를 이어 붙여 찾는 말도 다섯 말 모두에 있다', () => {
|
|
230
|
+
const keys = [
|
|
231
|
+
...V3_BUDGET_KEYS.map(key => `figure.label.budget-${key}`),
|
|
232
|
+
...['scale', 'consistency', 'reuse', 'detail', 'reactive', 'readable'].flatMap(key => [`figure.label.axis-${key}`, `figure.label.axis-${key}-short`]),
|
|
233
|
+
'figure.text.bound-by-groups',
|
|
234
|
+
'figure.text.bound-by-triangles',
|
|
235
|
+
...[...maturity.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1]!)
|
|
236
|
+
]
|
|
237
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
238
|
+
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
239
|
+
for (const key of keys) assert.ok(table[key], `${lang}: ${key}`)
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면 몇 대 · 등급, D 면 넘친 한도를 적는다', () => {
|
|
244
|
+
assert.equal(budgetOf(undefined), undefined, '잴 것이 없으면 줄도 없다')
|
|
245
|
+
let asset = createV3Asset('Probe')
|
|
246
|
+
asset = addV3Box(asset, cube('a', 0))
|
|
247
|
+
const line = budgetOf(v3ReportOf(asset))!
|
|
248
|
+
assert.deepEqual(
|
|
249
|
+
{ triangles: line.triangles, groups: line.groups, grade: line.grade },
|
|
250
|
+
{ triangles: 12, groups: 1, grade: 'A' }
|
|
251
|
+
)
|
|
252
|
+
assert.equal(line.instances, capacityFrom(1, 12)!.instances)
|
|
253
|
+
|
|
254
|
+
let glass = withPalette('glass.a', 'glass.b')
|
|
255
|
+
glass = addV3Box(glass, cube('a', 0, 'glass.a', { transparent: true }))
|
|
256
|
+
glass = addV3Box(glass, cube('b', 200, 'glass.b', { transparent: true }))
|
|
257
|
+
const over = budgetOf(v3ReportOf(glass))!
|
|
258
|
+
assert.equal(over.grade, 'D')
|
|
259
|
+
assert.deepEqual(over.tightest, { key: 'transparent', used: 2, limit: 1 })
|
|
260
|
+
|
|
261
|
+
const parts = read('../client/modeller/figure-v3-parts.ts')
|
|
262
|
+
const drawn = parts.slice(parts.indexOf(' private renderBudget()'), parts.indexOf(' /** 한꺼번에 감추고'))
|
|
263
|
+
assert.ok(drawn.includes("const over = b.grade === 'D'") && drawn.includes("'figure.text.parts-budget-over'"), 'D 에서 넘친 한도를 적는다')
|
|
264
|
+
assert.ok(parts.includes("new CustomEvent('show-maturity'"), '누르면 성숙도로')
|
|
265
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
266
|
+
assert.ok(editor.includes('.budget=${this.budget}') && editor.includes("@show-maturity=${() => (this.tab = 'verdict')}"))
|
|
267
|
+
assert.ok(verdict.includes('budget: budgetOf(report)'), '성숙도 탭이 같은 보고서에서 알린다')
|
|
268
|
+
})
|
|
@@ -29,7 +29,9 @@ test('복제 · 순서 · 삭제는 figure-model 의 부품 편집으로, 세션
|
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
test('작업대에서 가린 부품은 캔버스에서 빠지고, 다른 도형을 열면 풀린다', () => {
|
|
32
|
-
assert.
|
|
32
|
+
assert.ok(editor.includes('.parts=${this.drawnParts(view.geometry, asset)}'))
|
|
33
|
+
const drawn = editor.slice(editor.indexOf(' private drawnParts('), editor.indexOf(' private hold('))
|
|
34
|
+
assert.ok(drawn.includes('parts.filter(body => !this.hiddenParts.has('), '끄는 값을 그릴 때도 가린 부품은 빠진다')
|
|
33
35
|
const reset = editor.slice(editor.indexOf('protected willUpdate'), editor.indexOf('private run('))
|
|
34
36
|
assert.match(reset, /this\.hiddenParts = new Set\(\)/)
|
|
35
37
|
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **미리보기 — V2 의 그것** (사용자 2026-09-24: 「미리보기도 v2 가 낫다」). 저작 중인 도형을 도면처럼 등록하고,
|
|
3
|
+
* 기준 크기와 한 · 두 · 세 축을 2배로 늘린 일곱을 나란히 FigureInstance 로 세운다.
|
|
4
|
+
*/
|
|
5
|
+
import { test } from 'node:test'
|
|
6
|
+
import assert from 'node:assert/strict'
|
|
7
|
+
import { readFileSync } from 'node:fs'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
import { addV3Box, createV3Asset } from '@hatiolab/figure-model'
|
|
11
|
+
|
|
12
|
+
import { authoredSizeOf, PREVIEW_WAYS, previewInstances } from '../client/modeller/v3-preview-model.ts'
|
|
13
|
+
|
|
14
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
15
|
+
|
|
16
|
+
test('여덟 — 기준과 일곱 조합, 한 줄에 넷, 늘린 축만 두 배 · 높이는 늘 적는다', () => {
|
|
17
|
+
assert.deepEqual(PREVIEW_WAYS.map(w => w.name), ['base', 'x', 'y', 'z', 'x·y', 'x·z', 'y·z', 'x·y·z'])
|
|
18
|
+
const { components, board } = previewInstances('T', { x: 1000, y: 500, z: 400 })
|
|
19
|
+
assert.equal(components.length, 8)
|
|
20
|
+
assert.deepEqual(components.map(c => [c.width, c.depth, c.height]), [
|
|
21
|
+
[1000, 500, 400], [2000, 500, 400], [1000, 1000, 400], [1000, 500, 800],
|
|
22
|
+
[2000, 1000, 400], [2000, 500, 800], [1000, 1000, 800], [2000, 1000, 800]
|
|
23
|
+
])
|
|
24
|
+
assert.ok(components.every(c => c.type === 'T'))
|
|
25
|
+
assert.deepEqual(board, { width: 10000, height: 2000 }, '가장 큰 것이 들어갈 칸 넷씩 두 줄')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('크기를 선언하지 않은 도형은 부품이 차지하는 범위를 기준 크기로', () => {
|
|
29
|
+
const asset = addV3Box(createV3Asset('Fixed'), { id: 'body', size: { x: 300, y: 200, z: 100 }, position: { x: 0, y: 100, z: 0 } })
|
|
30
|
+
assert.deepEqual(authoredSizeOf(asset), { x: 300, y: 200, z: 100 })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('편집 | 미리보기 — 띠에서 오가고, 미리보기는 도면처럼 등록해 세우며 제 타입으로 남의 도면을 건드리지 않는다', () => {
|
|
34
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
35
|
+
assert.ok(editor.includes("(['edit', 'preview'] as const).map("))
|
|
36
|
+
assert.ok(editor.includes('<figure-v3-preview'))
|
|
37
|
+
const preview = read('../client/modeller/figure-v3-preview.ts')
|
|
38
|
+
assert.ok(preview.includes('registerFigureV3(type, asset)') && preview.includes('unregisterFigureV3(this.registered)'))
|
|
39
|
+
assert.ok(preview.includes('`figure-v3-preview-${this.context || \'new\'}`'))
|
|
40
|
+
assert.ok(preview.includes("kind: 'set-state-defaults'"), '이 자세를 기본으로')
|
|
41
|
+
assert.ok(preview.includes('dialShape('), '각도는 다이얼로')
|
|
42
|
+
})
|
|
@@ -61,8 +61,10 @@ test('태그 줄은 V2 와 같은 규칙으로 목록이 된다', () => {
|
|
|
61
61
|
const page = read('../client/pages/figure-v3-modeller-page.ts')
|
|
62
62
|
|
|
63
63
|
test('화면이 속성 탭을 걸고, 명령은 세션의 이력 하나로, 분류 · 태그는 행으로 간다', () => {
|
|
64
|
-
|
|
65
|
-
assert.ok(
|
|
64
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
65
|
+
assert.ok(/renderTab\('properties', 'tune'/.test(editor), '속성 탭은 편집기의 오른쪽 칸에')
|
|
66
|
+
assert.ok(/<figure-v3-properties\s+slot="figure"/.test(page), '도형 속성은 부품을 고르지 않았을 때 속성 탭에 선다')
|
|
67
|
+
assert.ok(editor.includes('<slot name="figure"></slot>'))
|
|
66
68
|
assert.ok(/@v3-command=\$\{this\.onCommand\}/.test(page) && /@v3-catalog=\$\{this\.onCatalog\}/.test(page))
|
|
67
69
|
const run = page.slice(page.indexOf('private runCommand'), page.indexOf('private onCatalog'))
|
|
68
70
|
assert.ok(/this\.session\.command\(actionOf\(\)\)/.test(run), '명령은 세션을 지난다 — 되돌리기가 먹는다')
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **편집 캔버스는 V2 처럼 things-scene 편집 모드 위에 선다** (사용자 2026-09-24: 「v2 방식으로 하면 안되냐」).
|
|
3
|
+
*
|
|
4
|
+
* 부품마다 `figure-v3-part` 컴포넌트로 판에 세우면 고르기 · 주황 테두리 · 기즈모와 스냅 · 박스 선택 · 카메라(감속 ·
|
|
5
|
+
* 커서 쪽 확대 · 더블클릭 맞추기 · 시점 1~6) · 스튜디오 환경 · 좌표 바탕 · 조명과 그림자가 씬에서 온다. 여기서는 그
|
|
6
|
+
* 배선과, 몸체 ↔ 컴포넌트의 대응이 왕복하는지를 잰다.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
import { changedKeys, componentOf, gizmoResultOf } from '../client/modeller/v3-stage-model.ts'
|
|
14
|
+
import type { V3RenderPart } from '../client/modeller/v3-renderer.ts'
|
|
15
|
+
|
|
16
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
17
|
+
const canvas = read('../client/modeller/v3-canvas.ts')
|
|
18
|
+
const board = { width: 2000, height: 2000 }
|
|
19
|
+
|
|
20
|
+
const turned = (deg: number) => {
|
|
21
|
+
const a = (deg * Math.PI) / 180
|
|
22
|
+
return [
|
|
23
|
+
[Math.cos(a), 0, Math.sin(a)],
|
|
24
|
+
[0, 1, 0],
|
|
25
|
+
[-Math.sin(a), 0, Math.cos(a)]
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
const box = (t: number[], r = turned(0)): V3RenderPart => ({
|
|
29
|
+
id: 'block',
|
|
30
|
+
shape: { provider: 'box', dimensions: { width: 200, height: 100, depth: 60 } },
|
|
31
|
+
pose: { r, t },
|
|
32
|
+
material: { token: 'palette.primary' }
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('things-scene 의 판을 편집 모드로 세우고, 보기 설정은 판의 상태 이름 그대로 넣는다', () => {
|
|
36
|
+
assert.ok(canvas.includes("import { create, Model, niceGridStep } from '@hatiolab/things-scene'"))
|
|
37
|
+
assert.match(canvas, /type: 'model-layer'/)
|
|
38
|
+
assert.match(canvas, /mode: 1\n/, '편집 모드 — 시점 단추 · 기즈모 · 도움말 · 단축키')
|
|
39
|
+
assert.ok(canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'))
|
|
40
|
+
assert.ok(canvas.includes('pixelRatio: Math.min(3, (window.devicePixelRatio || 1) * 2)'), 'V2 의 두 배 표본')
|
|
41
|
+
assert.doesNotMatch(canvas, /new THREE\.|WebGLRenderer|OrbitControls|TransformControls/, '제 장면을 짜지 않는다')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('몸체는 자기 상자로 서고, 씬의 낱말(width·depth·height · left·top·zPos · 회전)로 적힌다', () => {
|
|
45
|
+
const state = componentOf(box([100, 50, -30]), { 'palette.primary': '#123456' }, board)!
|
|
46
|
+
assert.equal(state.type, 'figure-v3-part')
|
|
47
|
+
assert.equal(state.name, 'block')
|
|
48
|
+
assert.deepEqual([state.width, state.depth, state.height], [200, 100, 60])
|
|
49
|
+
assert.equal(state.left, 100 + 1000 - 100)
|
|
50
|
+
assert.equal(state.top, -30 + 1000 - 30)
|
|
51
|
+
assert.equal(state.zPos, 0, '가운데가 y 50, 높이 100 이면 바닥이 0')
|
|
52
|
+
assert.equal(state.color, '#123456')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('기즈모가 옮기면 원점이, 돌리면 각도가, 늘리면 자기 축의 배율이 돌아온다', () => {
|
|
56
|
+
const part = box([100, 50, -30])
|
|
57
|
+
const at = componentOf(part, {}, board)!
|
|
58
|
+
assert.deepEqual(gizmoResultOf(part, at, board), {}, '안 움직였으면 아무것도')
|
|
59
|
+
|
|
60
|
+
const moved = gizmoResultOf(part, { ...at, left: (at.left as number) + 40, zPos: 20 }, board)
|
|
61
|
+
assert.deepEqual(moved.position!.map(Math.round), [140, 70, -30])
|
|
62
|
+
assert.equal(moved.rotation, undefined)
|
|
63
|
+
|
|
64
|
+
const spun = gizmoResultOf(part, { ...at, rotation: -Math.PI / 6 }, board)
|
|
65
|
+
assert.deepEqual(spun.rotation!.map(v => Math.round(v * 10) / 10 + 0), [0, 30, 0])
|
|
66
|
+
|
|
67
|
+
const grown = gizmoResultOf(part, { ...at, width: 400, left: (at.left as number) - 100 }, board)
|
|
68
|
+
assert.deepEqual(grown.scale!.map(v => Math.round(v * 1000) / 1000), [2, 1, 1])
|
|
69
|
+
assert.equal(grown.position, undefined, '가운데서 양쪽으로 늘리면 원점은 그대로')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('돌린 몸체도 왕복한다 — 판에 적은 상태를 그대로 되읽으면 아무것도 안 바뀐다', () => {
|
|
73
|
+
const part = box([10, 50, 20], turned(35))
|
|
74
|
+
const at = componentOf(part, {}, board)!
|
|
75
|
+
assert.deepEqual(gizmoResultOf(part, at, board), {})
|
|
76
|
+
assert.equal(changedKeys(at, { ...at }), null, '같은 값은 다시 넣지 않는다 — 씬이 메아리를 내지 않게')
|
|
77
|
+
assert.deepEqual(changedKeys(at, { ...at, left: 1 }), { left: 1 })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('고르기와 끌기는 이름으로 알리고, 스스로 적은 것의 메아리는 끌기로 읽지 않는다', () => {
|
|
81
|
+
assert.ok(canvas.includes("root.on('selected'"))
|
|
82
|
+
assert.ok(canvas.includes("if (this.writing) return"))
|
|
83
|
+
assert.ok(canvas.includes("new CustomEvent('part-transformed', { detail: { id: part.id, result }"))
|
|
84
|
+
assert.ok(canvas.includes('setTimeout(() => this.sync())'), '받지 못한 변경은 입력이 둔 자리로 돌아간다')
|
|
85
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The editing board only grows while a figure is edited (user 2026-09-24: 「조작값을 변경하면, 편집기가 자꾸 리프레시」).
|
|
3
|
+
* A board that outgrows itself is stood up again and the camera is lost; a raised arm made the height larger and the
|
|
4
|
+
* height was measured afresh each time.
|
|
5
|
+
*/
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
import assert from 'node:assert/strict'
|
|
8
|
+
|
|
9
|
+
import { addV3Box, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
|
|
10
|
+
|
|
11
|
+
import { drawableV3Parts } from '../client/modeller/v3-renderer.ts'
|
|
12
|
+
import { boardFor } from '../client/modeller/v3-stage-model.ts'
|
|
13
|
+
|
|
14
|
+
const partsOf = (height: number) =>
|
|
15
|
+
drawableV3Parts(
|
|
16
|
+
compileV3Asset(addV3Box(createV3Asset('Probe'), { id: 'post', size: { x: 100, y: height, z: 100 }, position: { x: 0, y: height / 2, z: 0 } }))
|
|
17
|
+
.evaluate().geometry
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
test('the board keeps the height it had: a part that shrinks back does not stand a new board up', () => {
|
|
21
|
+
const tall = boardFor(partsOf(2000))
|
|
22
|
+
const after = boardFor(partsOf(500), tall)
|
|
23
|
+
assert.equal(after.depth, tall.depth)
|
|
24
|
+
assert.ok(boardFor(partsOf(3000), tall).depth > tall.depth, 'it still grows')
|
|
25
|
+
})
|
package/test/v3-theme.test.ts
CHANGED
|
@@ -20,24 +20,14 @@ const fixed = (text: string) =>
|
|
|
20
20
|
.replace(/var\(--[\w-]+,\s*#[0-9a-fA-F]{3,8}\)/g, '')
|
|
21
21
|
.match(/#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3}\b(?![\w-])/g) ?? []
|
|
22
22
|
|
|
23
|
-
test('
|
|
24
|
-
assert.deepEqual(fixed(canvas), [], 'v3-canvas.ts')
|
|
23
|
+
test('편집기에 박힌 색이 없다 — 토큰의 대체값만 남는다', () => {
|
|
25
24
|
assert.deepEqual(fixed(editor), [], 'v3-editor.ts')
|
|
26
25
|
})
|
|
27
26
|
|
|
28
|
-
test('캔버스 바탕은
|
|
29
|
-
assert.
|
|
30
|
-
assert.
|
|
31
|
-
assert.match(
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('테마를 바꾸면 3D 바탕을 다시 읽는다 — 한 번만 읽으면 가운데만 밝은 판이 남는다', () => {
|
|
35
|
-
assert.match(canvas, /observe\(document\.body, \{ attributes: true, attributeFilter: \['class'\] \}\)/)
|
|
36
|
-
const onChange = canvas.slice(canvas.indexOf('new MutationObserver'), canvas.indexOf('this.themeObserver.observe'))
|
|
37
|
-
assert.match(onChange, /this\.paintBackground\(\)/)
|
|
38
|
-
assert.match(onChange, /this\.draw\(\)/, '다시 읽고 그리지 않으면 다음 움직임까지 옛 색이다')
|
|
39
|
-
const leave = canvas.slice(canvas.indexOf('disconnectedCallback()'))
|
|
40
|
-
assert.match(leave, /this\.themeObserver\?\.disconnect\(\)/, '떠난 캔버스가 body 를 계속 지켜본다')
|
|
27
|
+
test('캔버스 바탕은 V2 의 작업대 회색 — 판의 하늘색이라 환경 탭에서 바꾼다', () => {
|
|
28
|
+
assert.ok(canvas.includes('background-color: ${unsafeCSS(WORKBENCH)}'), '씬이 서기 전 · 2D 쪽도 같은 색')
|
|
29
|
+
assert.ok(canvas.includes("sky: 'color'") && canvas.includes('...sceneLook(this.view ?? DEFAULT_VIEW)'), '3D 바탕은 보기 설정의 skyColor')
|
|
30
|
+
assert.match(read('../client/modeller/figure-view.ts'), /skyColor: WORKBENCH/)
|
|
41
31
|
})
|
|
42
32
|
|
|
43
33
|
test('눌린 단추와 오류 칸은 셸이 두 테마 대비를 맞춘 칸을 쓴다', () => {
|
|
@@ -50,18 +40,19 @@ test('눌린 단추와 오류 칸은 셸이 두 테마 대비를 맞춘 칸을
|
|
|
50
40
|
assert.match(error, /--md-sys-color-on-error-container\)/)
|
|
51
41
|
})
|
|
52
42
|
|
|
53
|
-
test('편집기는 창이 아니라 자기 폭으로 접는다 —
|
|
43
|
+
test('편집기는 창이 아니라 자기 폭으로 접는다 — 레일이 폭을 먼저 가져간다', () => {
|
|
44
|
+
/* The widths - 200 | 1fr | 340, 160 | 300 under 860px, one column with the canvas first under 560px - are
|
|
45
|
+
ox-three-pane's, measured in @operato/layout's own test. The editor must not write the grid again. */
|
|
54
46
|
assert.match(editor, /container-type: inline-size/)
|
|
55
|
-
assert.doesNotMatch(editor, /@media \(max-width/, '창 폭 기준이 남아 있다 — 레일을 펼치면 편집기가
|
|
56
|
-
assert.match(editor,
|
|
57
|
-
|
|
58
|
-
assert.match(one, /grid-template-columns: minmax\(0, 1fr\)/, '420 아래에서 한 줄')
|
|
59
|
-
assert.match(one, /figure-v3-canvas \{\s*order: -1;/, '한 줄에서는 캔버스가 먼저')
|
|
47
|
+
assert.doesNotMatch(editor, /@media \(max-width/, '창 폭 기준이 남아 있다 — 레일을 펼치면 편집기가 넘친다')
|
|
48
|
+
assert.match(editor, /<ox-three-pane>/, '세 칸은 공용 틀')
|
|
49
|
+
assert.doesNotMatch(editor, /grid-template-columns/, '편집기가 격자를 다시 쓰지 않는다')
|
|
60
50
|
})
|
|
61
51
|
|
|
62
|
-
test('
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
test('양쪽 칸은 띠에서 접는다 — 접으면 캔버스가 그 폭을 갖고, 오른쪽 칸은 무엇도 가리지 않는다', () => {
|
|
53
|
+
assert.ok(editor.includes('.pane=${this.panes}'), '띠가 그 틀을 접는다')
|
|
54
|
+
assert.match(editor, /querySelector<OxThreePane>\('ox-three-pane'\)/, '띠에 건네는 틀은 편집기의 세 칸')
|
|
55
|
+
const aside = editor.slice(editor.indexOf(' aside {'), editor.indexOf('}', editor.indexOf(' aside {')))
|
|
65
56
|
assert.doesNotMatch(aside, /z-index/)
|
|
66
|
-
assert.
|
|
57
|
+
assert.doesNotMatch(aside, /border-left/, '칸 경계선은 틀이 긋는다 — 두 줄이 되지 않게')
|
|
67
58
|
})
|
package/test/v3-verdict.test.ts
CHANGED
|
@@ -29,8 +29,10 @@ test('개수는 목록 카드와 같은 셈이다 — V3 원가를 같은 capaci
|
|
|
29
29
|
|
|
30
30
|
assert.equal(cost.groups, 2, '전제: 그려지는 부품 둘이 묶음 둘')
|
|
31
31
|
assert.equal(capacityFrom(cost.groups, cost.triangles)?.instances, Math.floor(SCENE_BUDGET.groupsOnScreen / 2))
|
|
32
|
-
|
|
33
|
-
assert.ok(
|
|
32
|
+
const maturity = read('../client/modeller/v3-maturity.ts')
|
|
33
|
+
assert.ok(/capacityFrom\(cost\.groups, cost\.triangles\)/.test(maturity), '판정 탭이 카드와 같은 함수로 센다')
|
|
34
|
+
assert.ok(/report = asset \? v3ReportOf\(asset\) : undefined/.test(verdict), '탭은 그 셈을 부르고 보여 줄 뿐이다')
|
|
35
|
+
assert.ok(/instances: report\?\.capacity\.instances/.test(verdict), '탭 이름표의 개수도 같은 셈에서 온다')
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
|
|
@@ -56,15 +58,19 @@ test('판정 탭은 발행 패널과 같은 두 판정에 「부품에 맞추기
|
|
|
56
58
|
assert.ok(/<figure-v3-verdict[\s\S]*?@fit-base-to-parts=\$\{this\.fitToParts\}/.test(page), '누르면 공간 선언으로 이어진다')
|
|
57
59
|
})
|
|
58
60
|
|
|
59
|
-
test('
|
|
61
|
+
test('판정과 이력은 편집기의 탭에 서고, 감추되 떼지 않는다', () => {
|
|
62
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
60
63
|
assert.ok(/<figure-v3-verdict[\s\S]*?\.source=\$\{this\.draftSource\(\)\}/.test(page), '편집 중인 원본을 넘긴다')
|
|
61
64
|
assert.ok(/<figure-v3-verdict[\s\S]*?\.figureType=/.test(page), '타입을 바꾸면 다시 판정한다')
|
|
62
|
-
assert.ok(/<figure-v3-verdict\s
|
|
63
|
-
assert.ok(
|
|
65
|
+
assert.ok(/<figure-v3-verdict\s+slot="verdict"/.test(page) && /<figure-history-panel\s+slot="history"/.test(page))
|
|
66
|
+
assert.ok(editor.includes(`<div panel flush ?off=\${tab !== 'verdict'}><slot name="verdict"></slot></div>`), '고르지 않은 탭은 감춘다 — 판정은 마지막 답을 지킨다')
|
|
67
|
+
assert.ok(/@capacity-changed=/.test(page) && page.includes('.instances=${this.instances}'), '개수를 편집기에 넘긴다')
|
|
68
|
+
assert.ok(editor.includes("this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)"), '개수가 탭 이름표에 남는다')
|
|
64
69
|
})
|
|
65
70
|
|
|
66
71
|
test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그대로 찍힌다', () => {
|
|
67
|
-
const
|
|
72
|
+
const maturity = read('../client/modeller/v3-maturity.ts')
|
|
73
|
+
const keys = [...(verdict + maturity).matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
68
74
|
assert.ok(keys.includes('figure.text.verdict-asking'), '전제: 삼항 안의 키도 센다')
|
|
69
75
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
70
76
|
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **보기 — 작업대의 설정** (V3 동등표 ⑦, V2 의 「환경」 탭). 도형에 저장되지 않고, 보는 사람의 브라우저에 남는다.
|
|
3
|
+
*/
|
|
4
|
+
import { test } from 'node:test'
|
|
5
|
+
import assert from 'node:assert/strict'
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
11
|
+
|
|
12
|
+
test('환경 탭은 V2 의 보기 패널 그대로 — 설정은 판의 상태 이름이고, 도형이 아니라 브라우저에 둔다', () => {
|
|
13
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
14
|
+
assert.ok(editor.includes('<figure-settings .view=${this.view} @update-view=${(e: CustomEvent) => this.setView(e.detail.view)}></figure-settings>'))
|
|
15
|
+
assert.ok(editor.includes("localStorage.setItem('figure-v3-view'"))
|
|
16
|
+
assert.doesNotMatch(editor.slice(editor.indexOf('private setView'), editor.indexOf('private setView') + 400), /session/, '설정은 세션(도형)에 닿지 않는다')
|
|
17
|
+
const view = read('../client/modeller/figure-view.ts')
|
|
18
|
+
assert.ok(view.includes("'studio',") && view.includes("'rainy'"), '환경 여덟')
|
|
19
|
+
assert.ok(read('../client/modeller/figure-settings.ts').includes("i18next.t('figure.text.view-settings-are-not-saved')"))
|
|
20
|
+
})
|