@things-factory/figure-ui 10.1.147 → 10.1.149
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/bootstrap.ts +10 -2
- package/client/graphql/index.ts +20 -0
- package/client/modeller/canvas.ts +6 -2
- package/client/modeller/carrier-declaration.ts +63 -0
- package/client/modeller/editor.ts +62 -27
- package/client/modeller/figure-carrier.ts +117 -0
- package/client/modeller/figure-cavity-drawing.ts +3 -9
- package/client/modeller/figure-first-save.ts +4 -7
- package/client/modeller/figure-history-panel.ts +2 -3
- package/client/modeller/figure-links.ts +46 -60
- package/client/modeller/figure-material-panel.ts +4 -14
- package/client/modeller/figure-mesh-choice.ts +68 -0
- package/client/modeller/figure-parts.ts +23 -1
- package/client/modeller/figure-preview.ts +2 -2
- package/client/modeller/figure-properties.ts +5 -3
- package/client/modeller/figure-section-editor.ts +12 -33
- package/client/modeller/figure-section-panel.ts +6 -7
- package/client/modeller/figure-slot-layout-panel.ts +208 -0
- package/client/modeller/figure-verdict.ts +21 -31
- package/client/modeller/first-save.ts +23 -0
- package/client/modeller/links.ts +52 -1
- package/client/modeller/material-edit.ts +38 -0
- package/client/modeller/maturity.ts +28 -0
- package/client/modeller/mesh-import.ts +77 -0
- package/client/modeller/modeller-route.ts +9 -0
- package/client/modeller/mounting-part.ts +15 -0
- package/client/modeller/newest-answer.ts +21 -0
- package/client/modeller/preview-model.ts +5 -0
- package/client/modeller/refusals.ts +168 -82
- package/client/modeller/section-controls.ts +25 -1
- package/client/modeller/section-drawing.ts +43 -1
- package/client/modeller/slot-layout-host.ts +140 -0
- package/client/pages/figure-modeller-page.ts +6 -5
- package/client/viewparts/detail-route.ts +7 -0
- package/client/viewparts/figure-detail-dock.ts +1 -3
- package/dist-client/bootstrap.d.ts +1 -1
- package/dist-client/bootstrap.js +9 -2
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/graphql/index.d.ts +5 -0
- package/dist-client/graphql/index.js +19 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/canvas.d.ts +2 -0
- package/dist-client/modeller/canvas.js +10 -2
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/carrier-declaration.d.ts +31 -0
- package/dist-client/modeller/carrier-declaration.js +53 -0
- package/dist-client/modeller/carrier-declaration.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +13 -0
- package/dist-client/modeller/editor.js +62 -27
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-carrier.d.ts +26 -0
- package/dist-client/modeller/figure-carrier.js +123 -0
- package/dist-client/modeller/figure-carrier.js.map +1 -0
- package/dist-client/modeller/figure-cavity-drawing.js +3 -11
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -1
- package/dist-client/modeller/figure-first-save.d.ts +4 -0
- package/dist-client/modeller/figure-first-save.js +5 -8
- package/dist-client/modeller/figure-first-save.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.js +2 -4
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +4 -1
- package/dist-client/modeller/figure-links.js +47 -57
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.d.ts +0 -1
- package/dist-client/modeller/figure-material-panel.js +4 -14
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-mesh-choice.d.ts +16 -0
- package/dist-client/modeller/figure-mesh-choice.js +75 -0
- package/dist-client/modeller/figure-mesh-choice.js.map +1 -0
- package/dist-client/modeller/figure-parts.d.ts +1 -1
- package/dist-client/modeller/figure-parts.js +22 -0
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +2 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +2 -1
- package/dist-client/modeller/figure-properties.js +5 -3
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +2 -2
- package/dist-client/modeller/figure-section-editor.js +12 -33
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.js +6 -7
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +36 -0
- package/dist-client/modeller/figure-slot-layout-panel.js +198 -0
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -0
- package/dist-client/modeller/figure-verdict.d.ts +1 -1
- package/dist-client/modeller/figure-verdict.js +19 -37
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/first-save.d.ts +10 -0
- package/dist-client/modeller/first-save.js +16 -0
- package/dist-client/modeller/first-save.js.map +1 -1
- package/dist-client/modeller/links.d.ts +26 -1
- package/dist-client/modeller/links.js +43 -0
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/material-edit.d.ts +11 -0
- package/dist-client/modeller/material-edit.js +28 -0
- package/dist-client/modeller/material-edit.js.map +1 -0
- package/dist-client/modeller/maturity.d.ts +15 -0
- package/dist-client/modeller/maturity.js +23 -0
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/modeller/mesh-import.d.ts +47 -0
- package/dist-client/modeller/mesh-import.js +47 -0
- package/dist-client/modeller/mesh-import.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +5 -0
- package/dist-client/modeller/modeller-route.js +9 -0
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/mounting-part.d.ts +14 -0
- package/dist-client/modeller/mounting-part.js +10 -0
- package/dist-client/modeller/mounting-part.js.map +1 -0
- package/dist-client/modeller/newest-answer.d.ts +12 -0
- package/dist-client/modeller/newest-answer.js +21 -0
- package/dist-client/modeller/newest-answer.js.map +1 -0
- package/dist-client/modeller/preview-model.d.ts +2 -0
- package/dist-client/modeller/preview-model.js +4 -0
- package/dist-client/modeller/preview-model.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +26 -5
- package/dist-client/modeller/refusals.js +184 -86
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +17 -1
- package/dist-client/modeller/section-controls.js +18 -0
- package/dist-client/modeller/section-controls.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +24 -1
- package/dist-client/modeller/section-drawing.js +42 -0
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/slot-layout-host.d.ts +81 -0
- package/dist-client/modeller/slot-layout-host.js +105 -0
- package/dist-client/modeller/slot-layout-host.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.js +5 -4
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/detail-route.d.ts +5 -0
- package/dist-client/viewparts/detail-route.js +8 -0
- package/dist-client/viewparts/detail-route.js.map +1 -0
- package/dist-client/viewparts/figure-detail-dock.d.ts +0 -1
- package/dist-client/viewparts/figure-detail-dock.js +1 -3
- package/dist-client/viewparts/figure-detail-dock.js.map +1 -1
- package/package.json +9 -8
- package/test/align.test.ts +0 -9
- package/test/browser/assistant-stand-in.js +32 -0
- package/test/browser/build.mjs +6 -1
- package/test/browser/editing-page.js +224 -0
- package/test/browser/editing.check.mjs +550 -0
- package/test/browser/links-page.js +113 -0
- package/test/browser/links.check.mjs +568 -0
- package/test/browser/modeller.check.mjs +21 -0
- package/test/browser/parts-page.js +120 -0
- package/test/browser/parts.check.mjs +328 -0
- package/test/browser/places-mesh-page.js +36 -0
- package/test/browser/places-mesh.check.mjs +239 -0
- package/test/browser/product-page.check.mjs +682 -0
- package/test/browser/product-page.js +276 -0
- package/test/browser/screen-page.js +112 -0
- package/test/browser/screen-product-page.js +62 -0
- package/test/browser/screen.check.mjs +1003 -0
- package/test/browser/section-page.js +256 -0
- package/test/browser/section.check.mjs +719 -0
- package/test/browser/serve.mjs +20 -12
- package/test/carrier-declaration.test.ts +69 -0
- package/test/cavity-drawing.test.ts +2 -18
- package/test/clip-ai-proposal.test.ts +1 -5
- package/test/clip-consent.test.ts +0 -5
- package/test/clips.test.ts +0 -7
- package/test/command-preview.test.ts +0 -68
- package/test/controls.test.ts +0 -9
- package/test/detail-dock.test.ts +6 -25
- package/test/face-mapping.test.ts +1 -20
- package/test/fasten-drawing.test.ts +0 -23
- package/test/fields.test.ts +6 -33
- package/test/figure-assistant-surface.test.ts +32 -38
- package/test/first-minute.test.ts +3 -55
- package/test/first-save-suggest.test.ts +11 -8
- package/test/first-save.test.ts +4 -26
- package/test/gizmo.test.ts +0 -25
- package/test/joint-fan.test.ts +0 -10
- package/test/keys.test.ts +0 -7
- package/test/layout.test.ts +4 -73
- package/test/links.test.ts +48 -67
- package/test/live.test.ts +0 -34
- package/test/material.test.ts +17 -26
- package/test/maturity.test.ts +13 -16
- package/test/mesh-import.test.ts +76 -0
- package/test/modeller-route.test.ts +3 -19
- package/test/package-download.test.ts +6 -7
- package/test/parts-wiring.test.ts +9 -51
- package/test/preview.test.ts +5 -46
- package/test/properties.test.ts +11 -26
- package/test/proposal-record.test.ts +2 -19
- package/test/recipes.test.ts +0 -15
- package/test/refusals.test.ts +257 -151
- package/test/release-entry.test.ts +1 -9
- package/test/release-panel.test.ts +1 -22
- package/test/resize-with-placement.test.ts +0 -32
- package/test/revert-choice.test.ts +1 -22
- package/test/revert-keeping.test.ts +1 -12
- package/test/save-landed.test.ts +16 -14
- package/test/scene-canvas.test.ts +2 -13
- package/test/section-editor.test.ts +30 -83
- package/test/section-panel.test.ts +22 -17
- package/test/slot-layout-host.test.ts +117 -0
- package/test/theme.test.ts +5 -9
- package/test/verdict.test.ts +44 -41
- package/test/view.test.ts +5 -12
- package/translations/en.json +61 -1
- package/translations/es.json +61 -1
- package/translations/ja.json +61 -1
- package/translations/ko.json +61 -1
- package/translations/ms.json +61 -1
- package/translations/zh.json +61 -1
- package/test/thumbnail.test.ts +0 -29
package/test/links.test.ts
CHANGED
|
@@ -69,7 +69,8 @@ test('움직임 — 미끄러짐 · 돌기, 축, 무엇이 모느냐를 읽는
|
|
|
69
69
|
|
|
70
70
|
import { readFileSync } from 'node:fs'
|
|
71
71
|
import { fileURLToPath } from 'node:url'
|
|
72
|
-
import { choiceOf } from '../client/modeller/links.ts'
|
|
72
|
+
import { alignRowsOf, choiceOf, followCommandOf, ownFacesOf } from '../client/modeller/links.ts'
|
|
73
|
+
import { faceViewOf } from '../client/modeller/fasten-geometry.ts'
|
|
73
74
|
import { moving } from './joints.ts'
|
|
74
75
|
|
|
75
76
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
@@ -81,20 +82,6 @@ test('고르개의 값은 지금 따르는 것을 가리킨다', () => {
|
|
|
81
82
|
assert.equal(choiceOf({ of: 'part', part: 'plate', axis: 'x' }), 'part:plate:x')
|
|
82
83
|
})
|
|
83
84
|
|
|
84
|
-
test('편집기는 세 칸의 명령을 세션의 이력 하나로 돌리고, 만날 수 있는 거절을 사람의 말로 한다', () => {
|
|
85
|
-
const editor = read('../client/modeller/editor.ts')
|
|
86
|
-
assert.match(editor, /@command=\$\{\(e: CustomEvent\) => this\.linkCommand\(e\.detail\.action\)\}/)
|
|
87
|
-
assert.match(editor, /this\.session!\.command\(action\)/)
|
|
88
|
-
assert.match(editor, /code === 'DIMENSION_CYCLE'[\s\S]*?figure\.text\.follow-would-loop/)
|
|
89
|
-
assert.match(editor, /code === 'ATTACH_CYCLE'[\s\S]*?figure\.text\.fasten-would-loop/)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('떼면 지금 크기로 고정한다 — 따르던 값을 잃지 않는다', () => {
|
|
93
|
-
const links = read('../client/modeller/figure-links.ts')
|
|
94
|
-
assert.ok(links.includes("this.command({ kind: 'unlink-dimension', part: this.partId, axis, mm: Math.max(1, Math.round(current)) })"))
|
|
95
|
-
assert.ok(links.includes('const current = dimensionNow(this.revision!, this.partId, axis) ?? boxOf(this.revision!, this.partId)?.dims[AXES.indexOf(axis)] ?? 100'))
|
|
96
|
-
})
|
|
97
|
-
|
|
98
85
|
test('세 칸의 말은 다섯 말 모두에 있다', () => {
|
|
99
86
|
const links = read('../client/modeller/figure-links.ts')
|
|
100
87
|
const keys = [...links.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
@@ -142,19 +129,9 @@ test('담는 능력이 없는 도형은 자리가 있어도 보드가 아무것
|
|
|
142
129
|
asset.document.capabilities.names = ['FlowNode']
|
|
143
130
|
assert.equal(holdsThings(asset), true, 'FlowNode 는 Capacity 를 품는다')
|
|
144
131
|
|
|
145
|
-
const links = read('../client/modeller/figure-links.ts')
|
|
146
|
-
assert.match(links, /holdsThings\(asset\)/)
|
|
147
|
-
assert.match(links, /kind: 'set-capabilities', names/)
|
|
148
|
-
const editor = read('../client/modeller/editor.ts')
|
|
149
|
-
/* A slice and `includes`, not a lazy regex over the whole editor: that hangs when it does not match. */
|
|
150
|
-
const start = editor.indexOf('<figure-links')
|
|
151
|
-
const block = editor.slice(start, editor.indexOf('</figure-links>', start))
|
|
152
|
-
assert.ok(start >= 0 && block.includes('@part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}'), '자리 절의 능력 선언이 편집기에 닿는다')
|
|
153
132
|
})
|
|
154
133
|
|
|
155
134
|
test('받는 종류가 없으면 자리를 만들지 않는다 — 형식에 「아무거나」가 없다', () => {
|
|
156
|
-
const links = read('../client/modeller/figure-links.ts')
|
|
157
|
-
assert.match(links, /\?disabled=\$\{this\.locked \|\| !accepts\.length \|\| !capacityOk\}/)
|
|
158
135
|
assert.deepEqual(acceptsOf(' , ,'), [])
|
|
159
136
|
})
|
|
160
137
|
|
|
@@ -201,10 +178,32 @@ test('자리를 크기에 비례로 두면 그렇게 읽고, 다시 권하지
|
|
|
201
178
|
asset = applyFigureAuthoring(asset, { kind: 'scale-position', part: 'flange', axis: 'y' })
|
|
202
179
|
assert.equal(positionIsShare(asset, 'flange', 'y'), true)
|
|
203
180
|
assert.equal(positionIsShare(asset, 'base', 'y'), false)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('떼면 지금 크기로 고정한다 — 따르던 값을 잃지 않는다', () => {
|
|
184
|
+
let asset = sizedPair()
|
|
185
|
+
/* The flange's radius made a fifth of the figure's height: 20 mm now, its width 40. */
|
|
186
|
+
asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: 'flange', axis: 'x', source: { of: 'instance', axis: 'y' }, times: 0.2 })
|
|
187
|
+
const unlink = (a: FigureAsset, part: string, axis: 'x' | 'y') => followCommandOf(revisionOf(a), part, axis, 'own', followOf(a, part, axis))
|
|
188
|
+
assert.deepEqual(unlink(asset, 'flange', 'x'), { kind: 'unlink-dimension', part: 'flange', axis: 'x', mm: 20 }, 'fixed at its own measure, the radius -- not the 40 it is drawn wide')
|
|
189
|
+
/* A measure under a millimetre is fixed at one: the kernel takes no size of zero. */
|
|
190
|
+
const thin = applyFigureAuthoring(asset, { kind: 'link-dimension', part: 'base', axis: 'y', source: { of: 'instance', axis: 'y' }, times: 0.004 })
|
|
191
|
+
assert.deepEqual(unlink(thin, 'base', 'y'), { kind: 'unlink-dimension', part: 'base', axis: 'y', mm: 1 })
|
|
192
|
+
assert.equal(unlink(asset, 'base', 'x'), undefined, 'a measure that follows nothing has nothing to let go of')
|
|
193
|
+
const fixed = applyFigureAuthoring(asset, unlink(asset, 'flange', 'x')!)
|
|
194
|
+
assert.equal(dimensionNow(revisionOf(fixed), 'flange', 'x'), 20, 'let go, it keeps its size')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('made to follow in proportion, a measure is the share of the figure size it is now, to a millionth, and keeps its size', () => {
|
|
198
|
+
const asset = sizedPair()
|
|
199
|
+
asset.designInputs['size.y'] = 700
|
|
200
|
+
const share = followCommandOf(revisionOf(asset), 'flange', 'y', 'share:y', followOf(asset, 'flange', 'y'))
|
|
201
|
+
assert.deepEqual(share, { kind: 'link-dimension', part: 'flange', axis: 'y', source: { of: 'instance', axis: 'y' }, times: 0.014286, replace: false }, 'a length of 10 is 0.014286 of a height of 700')
|
|
202
|
+
assert.equal(followCommandOf(revisionOf(asset), 'flange', 'y', 'share:x', followOf(asset, 'flange', 'y')), undefined, 'no share of a size the figure does not declare')
|
|
203
|
+
const linked = applyFigureAuthoring(asset, share!)
|
|
204
|
+
assert.equal(Math.round(dimensionNow(revisionOf(linked), 'flange', 'y')! * 1000) / 1000, 10, 'following, the length is what it was')
|
|
205
|
+
/* Following already, the next rule replaces it; another part's measure is named as it is. */
|
|
206
|
+
assert.deepEqual(followCommandOf(revisionOf(linked), 'flange', 'y', 'part:base:x', followOf(linked, 'flange', 'y')), { kind: 'link-dimension', part: 'flange', axis: 'y', source: { of: 'part', part: 'base', axis: 'x' }, replace: true })
|
|
208
207
|
})
|
|
209
208
|
|
|
210
209
|
test('놓일 수 있는 크기 — 남은 만큼 채우던 부품을 비례로 바꾸면 다시 재어 넓힌다', () => {
|
|
@@ -223,8 +222,6 @@ test('놓일 수 있는 크기 — 남은 만큼 채우던 부품을 비례로
|
|
|
223
222
|
assert.equal(rangeDiffers(range.declared, range.measured), true)
|
|
224
223
|
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: range.measured, replace: true })
|
|
225
224
|
assert.equal(rangeDiffers(instanceRangeOf(asset)!.declared, instanceRangeOf(asset)!.measured), false, '다시 재면 같다')
|
|
226
|
-
const editor = read('../client/modeller/editor.ts')
|
|
227
|
-
assert.ok(editor.includes("this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })"))
|
|
228
225
|
})
|
|
229
226
|
|
|
230
227
|
test('놓일 수 있는 크기 — 저작자가 끝을 적어 좁힌다(갠트리, 09-25)', () => {
|
|
@@ -241,9 +238,6 @@ test('놓일 수 있는 크기 — 저작자가 끝을 적어 좁힌다(갠트
|
|
|
241
238
|
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: next, replace: true })
|
|
242
239
|
assert.deepEqual(instanceRangeOf(asset)!.declared.x, { min: 150, max: 20000 })
|
|
243
240
|
assert.throws(() => applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: rangeWith(next, 'x', 'min', 300)!, replace: true }), (e: { code?: string }) => e.code === 'INSTANCE_SIZE_RANGE', '저작한 크기(200)를 벗어나면 거절')
|
|
244
|
-
const editor = read('../client/modeller/editor.ts')
|
|
245
|
-
assert.ok(editor.includes("const next = rangeWith(range.declared, axis, bound, (e.target as HTMLInputElement).valueAsNumber)"))
|
|
246
|
-
assert.ok(editor.includes("if (next) this.linkCommand({ kind: 'declare-instance-size', range: next, replace: true })"))
|
|
247
241
|
})
|
|
248
242
|
|
|
249
243
|
test('한 비율로 커지는 도형 — 자기 숫자인 길이를 세고, 한 번에 따라가게 하며, 무엇이 남는지 이름을 댄다(아키텍트 판정 09-25)', () => {
|
|
@@ -261,9 +255,6 @@ test('한 비율로 커지는 도형 — 자기 숫자인 길이를 세고, 한
|
|
|
261
255
|
asset = applyFigureAuthoring(asset, { kind: 'follow-size-in-proportion' })
|
|
262
256
|
assert.deepEqual(proportionToFollow(asset), { count: 0, kept: ['lamp.motion'] }, '다 따르고, mm 로 움직이는 것만 남는다')
|
|
263
257
|
assert.equal(Math.round(valueNow(revisionOf(asset), 'lamp.dimension.x.value')! * 1e6) / 1e6, 60, '따르게 해도 지금 크기는 그대로')
|
|
264
|
-
const editor = read('../client/modeller/editor.ts')
|
|
265
|
-
assert.ok(editor.includes("this.linkCommand({ kind: 'follow-size-in-proportion' })"))
|
|
266
|
-
assert.ok(editor.includes("?? valueNow(shown, layout?.args[4])"), '길이를 따르는 간격도 숫자로 보인다')
|
|
267
258
|
})
|
|
268
259
|
|
|
269
260
|
test('관절이 있는 도형에는 한 비율로 키우기를 권하고, 선언하면 규칙으로 보인다(수석 아키텍트 판정 09-24)', () => {
|
|
@@ -271,11 +262,6 @@ test('관절이 있는 도형에는 한 비율로 키우기를 권하고, 선언
|
|
|
271
262
|
assert.equal(turnsAboutAJoint(asset), false)
|
|
272
263
|
asset = moving(asset, 'flange', { kind: 'turn', axis: 'y', id: 'flange.turn', min: 0, max: 90 })
|
|
273
264
|
assert.equal(turnsAboutAJoint(asset), true)
|
|
274
|
-
const editor = read('../client/modeller/editor.ts')
|
|
275
|
-
const rule = editor.slice(editor.indexOf(' private renderSizingRule('), editor.indexOf(' /** Choose a part,'))
|
|
276
|
-
assert.ok(rule.includes("asset.sizing === 'uniform'") && rule.includes("'figure.label.sizing-uniform'"), '선언하면 규칙을 말한다')
|
|
277
|
-
assert.ok(rule.includes("turnsAboutAJoint(asset) ? html`<p hint>${i18next.t('figure.text.sizing-uniform-proposed')}"), '관절이 있으면 권한다')
|
|
278
|
-
assert.ok(rule.includes("this.linkCommand({ kind: 'scale-uniformly' })"), '누르면 선언한다 — 사람이 정한다')
|
|
279
265
|
})
|
|
280
266
|
|
|
281
267
|
|
|
@@ -304,6 +290,20 @@ test('붙일 때 면 위 어디에 — 그 면을 가로지르는 두 축만,
|
|
|
304
290
|
assert.deepEqual(leaf.pose.t.map(Math.round), [500, 1000, 55], '문짝 아래가 문틀 아래에, 앞이 문틀 앞에')
|
|
305
291
|
})
|
|
306
292
|
|
|
293
|
+
test('the fastening form asks across the face in rows only where the drawing cannot set it: no drawing, a field followed, a share of the figure size (2026-10-02)', () => {
|
|
294
|
+
let asset = createFigureAsset('Door')
|
|
295
|
+
asset = addFigureBox(asset, { id: 'jamb', size: { x: 100, y: 2100, z: 150 }, position: { x: 0, y: 1050, z: 0 } })
|
|
296
|
+
asset = addFigureBox(asset, { id: 'leaf', size: { x: 900, y: 2000, z: 40 }, position: { x: 600, y: 1000, z: 0 } })
|
|
297
|
+
const right = { part: 'jamb', face: 'right' }
|
|
298
|
+
const view = faceViewOf(revisionOf(asset), 'leaf', right)
|
|
299
|
+
assert.deepEqual(view?.axes, ['y', 'z'])
|
|
300
|
+
assert.deepEqual(alignRowsOf(asset, 'leaf', right, view, false), [], 'the drawing sets both')
|
|
301
|
+
assert.deepEqual(alignRowsOf(asset, 'leaf', right, undefined, false), [{ axis: 'y', edges: true }, { axis: 'z', edges: true }], 'with no drawing, both are asked')
|
|
302
|
+
assert.deepEqual(alignRowsOf(asset, 'leaf', right, view, false, { y: 'leaf.position.y' }), [{ axis: 'y', edges: true }], 'the axis that follows a position field')
|
|
303
|
+
const sized = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range: { x: { min: 100, max: 5000 }, y: { min: 100, max: 5000 }, z: { min: 10, max: 500 } }, at: { x: 1000, y: 2100, z: 150 } })
|
|
304
|
+
assert.deepEqual(alignRowsOf(sized, 'leaf', right, faceViewOf(revisionOf(sized), 'leaf', right), true).map(r => r.axis), ['y', 'z'], 'the axes a share of the figure size may set')
|
|
305
|
+
})
|
|
306
|
+
|
|
307
307
|
test('붙여도 제자리 — 폼은 지금 서 있는 자리를 가운데서 거리로 채운다 (문짝이 문틀 앞 100 mm 에서 가운데로 옮겨졌다, 09-26)', async () => {
|
|
308
308
|
const { alignOf, placeKeptOf } = await import('../client/modeller/links.ts')
|
|
309
309
|
const { compileFigureAsset } = await import('@hatiolab/figure-model')
|
|
@@ -331,14 +331,6 @@ test('붙여도 제자리 — 폼은 지금 서 있는 자리를 가운데서
|
|
|
331
331
|
row = applyFigureAuthoring(row, { kind: 'attach', part: 'roller', face: 'bottom', to: { part: 'plate', face: 'top' } })
|
|
332
332
|
row = applyFigureAuthoring(row, { kind: 'attach', part: 'cap', face: 'bottom', to: { part: 'roller', face: 'top' } })
|
|
333
333
|
assert.deepEqual([hangsFrom(row, 'roller', 'plate'), hangsFrom(row, 'cap', 'plate'), hangsFrom(row, 'plate', 'roller')], [true, true, false])
|
|
334
|
-
const form = read('../client/modeller/figure-links.ts')
|
|
335
|
-
/* The target (a part or the plane, user 2026-10-01) and its face: the form is filled in whenever either changes. */
|
|
336
|
-
assert.ok(form.includes("this.aimAt(asset, value === PLANE ? { ...f, toPlane: true, target: '' } : { ...f, toPlane: false, target: value })"), '상대를 바꿀 때마다')
|
|
337
|
-
assert.ok(form.includes("this.aimAt(asset, { ...f, targetFace: c.targetFace as FigureFace })"), '상대 면을 바꿀 때마다')
|
|
338
|
-
assert.ok(!form.includes("figure.label.stands-on-plane"), '부품 붙이기 폼에 모델 설치 기준을 섞지 않는다')
|
|
339
|
-
/* Kept in the list, not choosable, with the reason (ADR-0097 §5.5). */
|
|
340
|
-
assert.ok(form.includes('const loops = (one: string) => hangsFrom(asset, one, this.partId)'))
|
|
341
|
-
assert.ok(form.includes("html`<option value=${one} disabled>${i18next.t('figure.text.target-would-loop'"))
|
|
342
334
|
})
|
|
343
335
|
|
|
344
336
|
test('따라 움직이는 관절 — 같은 방식으로 움직이고 제 조작값이 있는 관절만 고르고, 기록에는 배율과 더하기만 (집게 두 손가락, 09-25)', async () => {
|
|
@@ -563,8 +555,10 @@ test('벽 배치 — 장착 평면은 Z = 0, 등으로 붙이고 가로질러 X
|
|
|
563
555
|
a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: (await import('@hatiolab/figure-model')).proposeFigureOccupancy(a), placement: 'wall' as never })
|
|
564
556
|
assert.deepEqual(mountingPlaneOf(a), { normal: 'z', faces: ['back'] })
|
|
565
557
|
assert.deepEqual(alignAxesOf(a, 'plate', 'plane').map(x => x.axis), ['x', 'y'])
|
|
566
|
-
|
|
567
|
-
assert.
|
|
558
|
+
assert.deepEqual(ownFacesOf(a, 'plate', true), ['back'], 'on the plane, only the face the plane takes')
|
|
559
|
+
assert.deepEqual(ownFacesOf(a, 'plate', false), ['left', 'right', 'bottom', 'top', 'back', 'front', 'centre'], 'to a part, its six outer faces and its centre')
|
|
560
|
+
const floor = addFigureBox(createFigureAsset('Floor'), { id: 'plate', size: { x: 200, y: 300, z: 20 }, position: { x: 0, y: 0, z: 0 } })
|
|
561
|
+
assert.deepEqual(ownFacesOf(floor, 'plate', true), ['bottom', 'top'], 'on the floor, its bottom or its top')
|
|
568
562
|
})
|
|
569
563
|
|
|
570
564
|
test('부품 칸의 두 제목이 같은 말이 아니다 — 자리 숫자는 「위치와 크기」, 붙이기 칸은 「붙이기」 (09-26)', () => {
|
|
@@ -593,10 +587,6 @@ test('보이는 때 — 명령이 남긴 것을 읽고, 폼은 그 명령을 낸
|
|
|
593
587
|
const shown = editFigurePart(asset, { kind: 'set-visibility', target: 'lamp', shownWhen: { input: 'gate-open', atLeast: 9 } })
|
|
594
588
|
assert.deepEqual(shownWhenOf(shown, 'lamp'), { input: 'gate-open', atLeast: 9 })
|
|
595
589
|
assert.equal(shownWhenOf(editFigurePart(shown, { kind: 'set-visibility', target: 'lamp', shownWhen: null }), 'lamp'), null)
|
|
596
|
-
const form = read('../client/modeller/figure-links.ts')
|
|
597
|
-
const section = form.slice(form.indexOf(' private renderShown('), form.indexOf(' private renderMotion('))
|
|
598
|
-
assert.ok(section.includes("kind: 'set-visibility'"), '폼은 그 명령을 낸다')
|
|
599
|
-
assert.ok(form.includes('${this.renderShown(asset)}'), '움직임 칸에')
|
|
600
590
|
})
|
|
601
591
|
|
|
602
592
|
/* 문틀은 폭 · 높이 · 깊이로 — 상자처럼 크기를 따르고, 손잡이로 키운다 (figure-model 의 portal 단면, 09-26 · ADR-0100). */
|
|
@@ -614,14 +604,6 @@ test('팔레트의 문틀은 문 크기로 서고, 세 축 모두 크기를 따
|
|
|
614
604
|
|
|
615
605
|
/* 크기를 따라감 한 번 — 치수 칸의 단추 하나, 크기를 대신 정했으면 한 줄로 말한다 (ADR 판정 Q1, 09-26). */
|
|
616
606
|
test('치수 칸 — 「도형 크기를 따라감」 단추는 follow-size 한 번, 다 따르면 사라진다; 크기를 대신 정하면 한 줄', () => {
|
|
617
|
-
const form = read('../client/modeller/figure-links.ts')
|
|
618
|
-
/* The whole method: the resize-with-placement block above the advanced settings made it longer than a fixed slice. */
|
|
619
|
-
const dims = form.slice(form.indexOf(' private renderDimensions('), form.indexOf(' /** The same source, the same share'))
|
|
620
|
-
assert.ok(dims.includes("this.command({ kind: 'follow-size', part: this.partId })"))
|
|
621
|
-
assert.ok(dims.includes("followOf(asset, this.partId, axis).of === 'instance'"), '다 따르면 단추가 없다')
|
|
622
|
-
const editor = read('../client/modeller/editor.ts')
|
|
623
|
-
assert.ok(editor.includes("if (action.kind === 'follow-size' && !hadSize)"), '크기를 대신 정했을 때만')
|
|
624
|
-
assert.ok(editor.includes("notify({ level: 'info', message: this.notice })"), '그 한 줄은 앱의 알림으로')
|
|
625
607
|
const ko = JSON.parse(read('../translations/ko.json'))
|
|
626
608
|
assert.match(ko['figure.text.size-declared-to-follow'], /\{x\} × \{y\} × \{z\} mm/)
|
|
627
609
|
})
|
|
@@ -658,9 +640,8 @@ test('붙이기 칸은 부품 제 붙임으로 열린다 — 가운데로 붙은
|
|
|
658
640
|
const bed = fasteningOf(revisionOf(asset), 'bed')!
|
|
659
641
|
assert.deepEqual([bed.face, bed.toPlane], ['bottom', true])
|
|
660
642
|
assert.equal(fasteningOf(revisionOf(addFigureBox(createFigureAsset('Loose'), { id: 'b', size: { x: 1, y: 1, z: 1 }, position: { x: 0, y: 0, z: 0 } })), 'b'), null, '안 붙은 부품은 기본값으로')
|
|
661
|
-
|
|
662
|
-
assert.ok(
|
|
663
|
-
assert.ok(panel.includes("...(f.toPlane ? [] : (['centre'] as const))"), '내 면에 가운데가 있다')
|
|
643
|
+
assert.equal(ownFacesOf(asset, 'roller', false).at(-1), 'centre', 'its own faces include its centre')
|
|
644
|
+
assert.ok(!ownFacesOf(asset, 'roller', true).includes('centre'), 'on the floor, by a face only')
|
|
664
645
|
})
|
|
665
646
|
|
|
666
647
|
test('a follower of several leaders and a stroke that is a sum of lengths are written back unchanged by the motion form (ADR-0107)', async () => {
|
package/test/live.test.ts
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { test } from 'node:test'
|
|
6
6
|
import assert from 'node:assert/strict'
|
|
7
|
-
import { readFileSync } from 'node:fs'
|
|
8
|
-
import { fileURLToPath } from 'node:url'
|
|
9
7
|
|
|
10
8
|
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
|
|
11
9
|
|
|
@@ -13,8 +11,6 @@ import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed,
|
|
|
13
11
|
import { moving } from './joints.ts'
|
|
14
12
|
import { revisionOf } from './revision-of.ts'
|
|
15
13
|
|
|
16
|
-
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
17
|
-
|
|
18
14
|
const slider = () => {
|
|
19
15
|
let asset = createFigureAsset('Slider')
|
|
20
16
|
asset = addFigureBox(asset, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
@@ -69,36 +65,6 @@ test('이름표를 끌면 값이 크기에 맞는 걸음으로 움직인다 —
|
|
|
69
65
|
assert.equal(scrubbed(0.4, 3, 0.01), 0.43, '0.43000000000000005 가 아니다')
|
|
70
66
|
})
|
|
71
67
|
|
|
72
|
-
test('속성창 숫자 칸은 치는 동안 알리고, 이름표를 끌 수 있다 — 기록은 change 에서', () => {
|
|
73
|
-
const editor = read('../client/modeller/editor.ts')
|
|
74
|
-
assert.ok(editor.includes('@input=${(e: Event) => this.typing(group.cells[axis] as never, e)}'))
|
|
75
|
-
assert.ok(editor.includes('@input=${(e: Event) => this.typing(field as never, e)}'))
|
|
76
|
-
assert.ok(editor.includes('@pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}'))
|
|
77
|
-
for (const event of ['@state-live=', '@states-live=', '@part-live=']) assert.ok(editor.includes(event), event)
|
|
78
|
-
assert.ok(editor.includes('liveOverSession(shown, this.live)'), '캔버스는 세션 위에 끄는 값을 그린다 (행동은 아래 시험)')
|
|
79
|
-
const run = editor.slice(editor.indexOf(' private run(action'), editor.indexOf(' private hold('))
|
|
80
|
-
assert.ok(run.includes('this.live = NOTHING_LIVE'), '기록하거나 거절되면 끄는 값을 버린다')
|
|
81
|
-
const material = read('../client/modeller/figure-material-panel.ts')
|
|
82
|
-
assert.ok(material.includes('@input=${(e: Event) => this.glow(Number((e.target as HTMLInputElement).value), true)}'))
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('슬라이더와 클립 막대는 끄는 동안 알리고, 놓을 때 한 번 기록한다', () => {
|
|
86
|
-
const controls = read('../client/modeller/figure-controls.ts')
|
|
87
|
-
assert.ok(controls.includes('@input=${(e: Event) => this.drag(c, number(e))}') && controls.includes('@change=${(e: Event) => this.try(c, number(e))}'))
|
|
88
|
-
assert.ok(controls.includes("new CustomEvent('state-live'"))
|
|
89
|
-
const clips = read('../client/modeller/figure-clips.ts')
|
|
90
|
-
assert.ok(clips.includes('@input=${(e: Event) => this.scrub(clip, number(e), true)}'))
|
|
91
|
-
assert.ok(clips.includes("new CustomEvent('states-live'"))
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('클립 트랙의 머리는 이름 한 줄, 그 아래 반복 방식과 지우기 — 이름이 한 글자씩 쌓이지 않는다', () => {
|
|
95
|
-
const clips = read('../client/modeller/figure-clips.ts')
|
|
96
|
-
const head = clips.slice(clips.indexOf(' div[track-head] {'), clips.indexOf(' div[keys] {'))
|
|
97
|
-
assert.ok(head.includes('display: grid;') && !head.includes('display: flex;'), '공통 스타일의 폭 100% 가 이름을 누르지 않는다')
|
|
98
|
-
assert.ok(head.includes('grid-column: 1 / -1;'), '이름은 제 줄')
|
|
99
|
-
assert.ok(head.includes('width: 30px;'), '지우기는 작은 버튼')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
68
|
test('끄는 동안은 세션이 지금 든 자세 위에 그린다 — 다른 관절이 저장된 각도로 돌아가지 않는다', () => {
|
|
103
69
|
/* Two joints: the author turned one with its control; then drags the other. */
|
|
104
70
|
let asset = slider()
|
package/test/material.test.ts
CHANGED
|
@@ -6,14 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
8
8
|
import assert from 'node:assert/strict'
|
|
9
|
-
import { readFileSync } from 'node:fs'
|
|
10
|
-
import { fileURLToPath } from 'node:url'
|
|
11
9
|
|
|
12
10
|
import { addFigureBox, addFigurePrimitive, compileFigureAsset, createFigureAsset, editFigurePart, figureCostOf } from '@hatiolab/figure-model'
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
const material = read('../client/modeller/figure-material-panel.ts')
|
|
16
|
-
const editor = read('../client/modeller/editor.ts')
|
|
12
|
+
import { glowOf, materialEditOf, offeredPaletteOf } from '../client/modeller/material-edit.ts'
|
|
17
13
|
|
|
18
14
|
const probe = () => {
|
|
19
15
|
let asset = createFigureAsset('Probe')
|
|
@@ -41,12 +37,16 @@ test('재질 칸이 보내는 모양 그대로 figure-model 이 받고, 그려
|
|
|
41
37
|
})
|
|
42
38
|
|
|
43
39
|
test('발광 세기 0 은 발광을 없앤다 — 빈 칸을 남기지 않는다(형식이 undefined 칸을 거절한다)', () => {
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const glowing = { token: 'palette.primary', transparent: true, emissive: { token: 'palette.accent', intensity: 0.5, on: true } }
|
|
41
|
+
const palette = { 'palette.primary': '#477b9e', 'palette.accent': '#f28c28' }
|
|
42
|
+
const off = materialEditOf('body', glowing, { ...glowOf(glowing, 0), transparent: undefined }, palette, palette) as { material: Record<string, unknown> }
|
|
43
|
+
assert.deepEqual(off.material, { token: 'palette.primary' }, 'no glow and no empty field left')
|
|
44
|
+
assert.equal(drawn(editFigurePart(probe(), off as never), 'body').material.emissive, undefined, 'figure-model takes it')
|
|
45
|
+
assert.deepEqual(glowOf({ token: 'palette.primary' }, 0.4), { emissive: { token: 'palette.primary', intensity: 0.4, on: true } }, 'a new glow takes the part\'s own colour')
|
|
46
|
+
assert.equal((glowOf(glowing, 0.4).emissive as { token: string }).token, 'palette.accent', 'a glow keeps the colour picked for it')
|
|
46
47
|
})
|
|
47
48
|
|
|
48
49
|
test('곡면 분할은 둥근 몸체에만 내고, 고르면 삼각형 수가 그만큼 바뀐다', async () => {
|
|
49
|
-
assert.match(material, /if \(!isRoundBody\(/, 'the panel asks the one rule')
|
|
50
50
|
const { isRoundBody } = await import('../client/modeller/starting-shape.ts')
|
|
51
51
|
const { boxShape, discShape } = await import('../client/modeller/drawn-shapes.ts')
|
|
52
52
|
assert.equal(isRoundBody(discShape(50, 10) as never), true, 'a circle pushed is round')
|
|
@@ -57,23 +57,14 @@ test('곡면 분할은 둥근 몸체에만 내고, 고르면 삼각형 수가
|
|
|
57
57
|
assert.ok(fine > coarse, `48 분할이 8 분할보다 삼각형이 많다 (${coarse} → ${fine})`)
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
test('색 역할은 날것의 이름이 아니라 쓰임새로 부른다', () => {
|
|
61
|
-
assert.match(material, /title=\$\{purposeOf\(token\)\}/)
|
|
62
|
-
assert.doesNotMatch(editor, /token === 'palette\.primary' \? '기본 몸체' : token/, '옛 날것 이름 고르개가 남아 있다')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('편집기는 재질 칸의 편집을 세션의 이력 하나로 돌린다', () => {
|
|
66
|
-
assert.match(editor, /<figure-material-panel[\s\S]*?@part-edit=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.edit\(e\.detail\.edit\)\)\}/)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
60
|
test('자산에 없는 색 역할은 그 색을 함께 보낸다 — 새 도형의 팔레트는 기본색 하나다', () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
assert.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
assert.
|
|
61
|
+
const asset = probe()
|
|
62
|
+
const offered = offeredPaletteOf({ 'palette.primary': '#000000', 'palette.accent': '#f28c28', 'palette.dark': '#2b2b2b' }, asset.palette)
|
|
63
|
+
assert.equal(offered['palette.primary'], asset.palette['palette.primary'], '자산의 색이 앱의 색 위에 선다')
|
|
64
|
+
assert.equal(offered['palette.accent'], '#f28c28', '앱 팔레트를 보인다')
|
|
65
|
+
const accent = materialEditOf('body', { token: 'palette.primary' }, { token: 'palette.accent', emissive: { token: 'palette.dark', intensity: 0.5, on: true } }, asset.palette, offered) as { palette?: Record<string, string> }
|
|
66
|
+
assert.deepEqual(accent.palette, { 'palette.accent': '#f28c28', 'palette.dark': '#2b2b2b' }, '몸체 색과 발광 색 모두')
|
|
67
|
+
assert.equal(drawn(editFigurePart(asset, accent as never), 'body').material.token, 'palette.accent', 'figure-model 이 받는다')
|
|
68
|
+
const own = materialEditOf('body', { token: 'palette.accent' }, { token: 'palette.primary' }, asset.palette, offered)
|
|
69
|
+
assert.equal('palette' in own, false, '자산에 있는 색이면 보낼 것이 없다')
|
|
79
70
|
})
|
package/test/maturity.test.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
curveLayout,
|
|
20
20
|
decadeSteps,
|
|
21
21
|
GRADE_SCALE,
|
|
22
|
+
gradeBoundsOf,
|
|
22
23
|
gradeOf,
|
|
23
24
|
FIGURE_BUDGET_KEYS,
|
|
24
25
|
figureReportOf
|
|
@@ -204,14 +205,17 @@ test('추이 — 1 에서 1만까지 1·2·5, 삼각형과 draw call 은 오르
|
|
|
204
205
|
assert.deepEqual(layout.xTicks.map(t => t.label), ['1', '10', '100', '1k', '10k'])
|
|
205
206
|
})
|
|
206
207
|
|
|
207
|
-
test('
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
208
|
+
test('눈금은 등급을 셈한 경계에서 그린다 — 따로 적으면 어긋난다', () => {
|
|
209
|
+
/* The order of the tab's sections, and that it draws this scale, are the browser check's (test/browser/screen.check.mjs). */
|
|
210
|
+
const bounds = gradeBoundsOf()
|
|
211
|
+
assert.deepEqual(bounds.map(([grade]) => grade), ['A', 'B', 'C', 'D'])
|
|
212
|
+
for (const [grade, said] of bounds.slice(0, -1)) {
|
|
213
|
+
const bound = Number(/^≤ ([\d.]+)%$/.exec(said)![1]) / 100
|
|
214
|
+
assert.equal(gradeOf(bound), grade, `${grade} ${said}: 경계에서는 그 등급`)
|
|
215
|
+
assert.notEqual(gradeOf(bound + 0.001), grade, `${grade} ${said}: 경계를 넘으면 다음 등급`)
|
|
216
|
+
}
|
|
217
|
+
assert.deepEqual(bounds.at(-1), ['D', '> 100%'])
|
|
218
|
+
assert.equal(gradeOf(1.001), 'D')
|
|
215
219
|
})
|
|
216
220
|
|
|
217
221
|
test('radar 와 추이 그림의 조각은 svg 템플릿이다 — html 로 만들면 소리 없이 안 그려진다', () => {
|
|
@@ -252,12 +256,5 @@ test('부품 목록 머리의 값 한 줄 — 삼각형 · 묶음 · 한 화면
|
|
|
252
256
|
const over = budgetOf(figureReportOf(glass))!
|
|
253
257
|
assert.equal(over.grade, 'D')
|
|
254
258
|
assert.deepEqual(over.tightest, { key: 'transparent', used: 2, limit: 1 })
|
|
255
|
-
|
|
256
|
-
const parts = read('../client/modeller/figure-parts.ts')
|
|
257
|
-
const drawn = parts.slice(parts.indexOf(' private renderBudget()'), parts.indexOf(' /** 한꺼번에 감추고'))
|
|
258
|
-
assert.ok(drawn.includes("const over = b.grade === 'D'") && drawn.includes("'figure.text.parts-budget-over'"), 'D 에서 넘친 한도를 적는다')
|
|
259
|
-
assert.ok(parts.includes("new CustomEvent('show-maturity'"), '누르면 성숙도로')
|
|
260
|
-
const editor = read('../client/modeller/editor.ts')
|
|
261
|
-
assert.ok(editor.includes('.budget=${this.budget}') && editor.includes("@show-maturity=${() => (this.tab = 'verdict')}"))
|
|
262
|
-
assert.ok(verdict.includes('budget: budgetOf(report)'), '성숙도 탭이 같은 보고서에서 알린다')
|
|
259
|
+
/* The line drawn in the parts list, and its click to the verdict tab, are the browser check's (test/browser/screen.check.mjs). */
|
|
263
260
|
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A GLB made into a mesh part (ADR-0129): the whole file or one node of it, at 1:1, standing where it stood in the file,
|
|
3
|
+
* with what the server read of it. The kernel side (mesh@1) is tested in figure-model; the server's reading in
|
|
4
|
+
* figure-service (figure-glb, figure-mesh-db).
|
|
5
|
+
*/
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
import assert from 'node:assert/strict'
|
|
8
|
+
import { readFileSync } from 'node:fs'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
import { meshChoicesOf, meshPartIdOf, meshPartOf, type MeshUpload } from '../client/modeller/mesh-import.js'
|
|
12
|
+
|
|
13
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
14
|
+
const HASH = 'c'.repeat(64)
|
|
15
|
+
const box = (x0: number, y0: number, z0: number, x1: number, y1: number, z1: number) => ({ min: { x: x0, y: y0, z: z0 }, max: { x: x1, y: y1, z: z1 } })
|
|
16
|
+
|
|
17
|
+
/* A crane: a 6 m girder, a hoist hung 1 m along it and turned a quarter, a marker with no mesh, a sheared node. */
|
|
18
|
+
const upload: MeshUpload = {
|
|
19
|
+
hash: HASH,
|
|
20
|
+
summary: {
|
|
21
|
+
bounds: box(-3000, -800, -400, 3000, 200, 400),
|
|
22
|
+
triangles: 5000,
|
|
23
|
+
primitives: 4,
|
|
24
|
+
unit: 'm',
|
|
25
|
+
nodes: [
|
|
26
|
+
{ index: 0, name: 'Girder', parent: null, bounds: box(-3000, -800, -400, 3000, 200, 400), triangles: 5000, primitives: 4, position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 } },
|
|
27
|
+
{ index: 1, name: 'Hoist Unit #2', parent: 0, bounds: box(-200, -300, -150, 200, 0, 150), triangles: 1200, primitives: 2, position: { x: 1000, y: -500, z: 0 }, rotation: { x: 0, y: 90, z: 0 } },
|
|
28
|
+
{ index: 2, name: 'marker', parent: 0, bounds: null, triangles: 0, primitives: 0, position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, refused: 'no-mesh' },
|
|
29
|
+
{ index: 3, name: 'skewed', parent: 0, bounds: null, triangles: 10, primitives: 1, position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, refused: 'sheared' }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('the whole file first, then each node that can be a part; no marker, nothing sheared', () => {
|
|
35
|
+
assert.deepEqual(meshChoicesOf(upload), [
|
|
36
|
+
{ name: '', triangles: 5000 },
|
|
37
|
+
{ node: 0, name: 'Girder', triangles: 5000 },
|
|
38
|
+
{ node: 1, name: 'Hoist Unit #2', triangles: 1200 }
|
|
39
|
+
])
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('the whole file: at 1:1 along its longest axis, at the origin, with the file named by its hash', () => {
|
|
43
|
+
const part = meshPartOf(upload, {}, 'crane') as any
|
|
44
|
+
assert.equal(part.kind, 'mesh')
|
|
45
|
+
assert.deepEqual(part.dimensions, { size: 6000 })
|
|
46
|
+
assert.deepEqual(part.mesh, { asset: HASH, format: 'glb', unit: 'm', bounds: upload.summary.bounds, triangles: 5000, primitives: 4 })
|
|
47
|
+
assert.deepEqual(part.position, { x: 0, y: 0, z: 0 })
|
|
48
|
+
assert.equal(part.rotation, undefined)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('a node: its own bounds and counts, standing and turned where the file had it', () => {
|
|
52
|
+
const part = meshPartOf(upload, { node: 1 }, 'hoist') as any
|
|
53
|
+
assert.deepEqual(part.dimensions, { size: 400 })
|
|
54
|
+
assert.equal(part.mesh.node, 1)
|
|
55
|
+
assert.deepEqual(part.mesh.bounds, box(-200, -300, -150, 200, 0, 150))
|
|
56
|
+
assert.equal(part.mesh.triangles, 1200)
|
|
57
|
+
assert.deepEqual(part.position, { x: 1000, y: -500, z: 0 })
|
|
58
|
+
assert.deepEqual(part.rotation, { x: 0, y: 90, z: 0 })
|
|
59
|
+
assert.throws(() => meshPartOf(upload, { node: 3 }, 'x'), 'a node that cannot be a part is not made one')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("a part's id from a node's name: letter-led and clean, another number when taken", () => {
|
|
63
|
+
assert.equal(meshPartIdOf('Hoist Unit #2', new Set()), 'Hoist-Unit-2')
|
|
64
|
+
assert.equal(meshPartIdOf('2nd beam', new Set()), 'nd-beam')
|
|
65
|
+
assert.equal(meshPartIdOf('###', new Set()), 'mesh')
|
|
66
|
+
assert.equal(meshPartIdOf('hoist', new Set(['hoist', 'hoist-2'])), 'hoist-3')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
/* The screen itself -- the palette's button, the upload, the question, the step added -- is driven in browser/places-mesh.check.mjs. */
|
|
70
|
+
test('the mesh words are in every language', () => {
|
|
71
|
+
for (const lang of ['en', 'es', 'ja', 'ko', 'ms', 'zh']) {
|
|
72
|
+
const t = JSON.parse(read(`../translations/${lang}.json`))
|
|
73
|
+
for (const key of ['figure.button.import-mesh', 'figure.text.import-mesh-hint', 'figure.text.mesh-whole-file', 'figure.text.mesh-triangles', 'figure.title.choose-mesh-part'])
|
|
74
|
+
assert.ok(t[key], `${lang} ${key}`)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
@@ -16,33 +16,17 @@ test('every figure and a new one open the one modeller', () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test('one modeller page at one address: the route and the shell table agree', () => {
|
|
19
|
+
/* The shell's router runs route.ts; this suite has no router to drive it with, so the import is read as it is written. */
|
|
19
20
|
const route = read('../client/route.ts')
|
|
20
|
-
assert.equal(route.match(/case 'figure-modeller':/g)?.length, 1)
|
|
21
21
|
assert.match(route, /import\('\.\/pages\/figure-modeller-page'\)/)
|
|
22
22
|
const config = read('../things-factory.config.js')
|
|
23
23
|
assert.equal(config.match(/tagname: 'figure-modeller-page', page: 'figure-modeller'/g)?.length, 1)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
test('the catalogue opens details first; editing is an explicit action in the detail dock', () => {
|
|
27
|
+
/* The catalogue page (its grid and the shell) is not driven here; the dock's edit button is, in product-page.check.mjs. */
|
|
27
28
|
const list = read('../client/pages/figure-list-page.ts')
|
|
28
29
|
assert.match(list, /openFigureDetail\(record\)/)
|
|
29
|
-
const detail = read('../client/viewparts/figure-detail-dock.ts')
|
|
30
|
-
assert.match(detail, /navigate\(modellerPathOf\(saved\)\)/)
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
const page = read('../client/pages/figure-modeller-page.ts')
|
|
35
|
-
const hold = page.slice(page.indexOf('private holdUnsaved'), page.indexOf('connectedCallback()'))
|
|
36
|
-
assert.match(hold, /session\.read\(\)\.dirty/, 'only when there is something unsaved')
|
|
37
|
-
assert.match(hold, /event\.preventDefault\(\)/)
|
|
38
|
-
assert.match(page, /addEventListener\('beforeunload', this\.holdUnsaved\)/)
|
|
39
|
-
assert.match(page, /removeEventListener\('beforeunload', this\.holdUnsaved\)/)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('기존 도형을 열면 rail 은 카탈로그를 가리킨다 — 「새 Figure」를 가리키지 않는다(2026-10-03)', async () => {
|
|
43
|
-
const { readFileSync } = await import('node:fs')
|
|
44
|
-
const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
|
|
45
|
-
const context = page.slice(page.indexOf('get context()'), page.indexOf('render()', page.indexOf('get context()')))
|
|
46
|
-
/* The rail's rule (@operato/menu menu-active.ts): a page that names its menu wins over a menu with the same page name. */
|
|
47
|
-
assert.ok(context.includes("menu: view.id ? 'figure-list' : undefined"), 'id 가 있으면 카탈로그, 새 도형이면 이름을 대지 않는다')
|
|
48
|
-
})
|
|
32
|
+
/* Reloading with unsaved work, and the rail on an opened figure, are checked on the page: test/browser/product-page.check.mjs. */
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { test } from 'node:test'
|
|
5
5
|
import assert from 'node:assert/strict'
|
|
6
|
-
import { readFileSync } from 'node:fs'
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
import { packageHrefOf } from '../client/modeller/modeller-route.ts'
|
|
9
8
|
|
|
9
|
+
/* The history panel draws the link from this for each released version (product-page.check.mjs reads it there). */
|
|
10
10
|
test('each released version links to the package door by its full reference, only once the figure has a full name', () => {
|
|
11
|
-
|
|
12
|
-
assert.
|
|
13
|
-
assert.
|
|
14
|
-
|
|
15
|
-
assert.match(row, /<a package download href=/)
|
|
11
|
+
assert.equal(packageHrefOf('/domain/x', 'acme', 'agv', 3), '/domain/x/figure-package?refs=acme%2Fagv%403')
|
|
12
|
+
assert.equal(packageHrefOf('', 'acme', 'roller-conveyor', 12), '/figure-package?refs=acme%2Froller-conveyor%4012')
|
|
13
|
+
assert.equal(packageHrefOf('/domain/x', undefined, 'agv', 3), undefined, 'no namespace yet')
|
|
14
|
+
assert.equal(packageHrefOf('/domain/x', 'acme', null, 3), undefined, 'no type yet')
|
|
16
15
|
})
|