@things-factory/figure-ui 10.1.146 → 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
|
@@ -1,58 +1,52 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The modeller's AI - the framework dock talks about the figure on screen (design/ai/shared-dock.md §3).
|
|
3
3
|
*
|
|
4
|
-
* What
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* What the page does with the dock -- ① a saved figure provides a surface anchored on its row ② the draft is read at the
|
|
5
|
+
* moment of sending ③ an unsaved figure provides nothing ④ the surface is taken back when the page goes inactive or
|
|
6
|
+
* leaves ⑤ a proposal is taken through the page, and a new figure's opens a new figure -- is checked on the page itself
|
|
7
|
+
* in test/browser/product-page.check.mjs. Here: the offer the page takes it by, and the words every outcome is said in.
|
|
8
8
|
*/
|
|
9
9
|
import assert from 'node:assert/strict'
|
|
10
10
|
import { readFileSync } from 'node:fs'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { test } from 'node:test'
|
|
13
|
+
import { addFigureBox, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
import { createFigureProductSession } from '../client/modeller/product-session.js'
|
|
16
|
+
import { takeFigureOffer, type FigureApplyDetail } from '../client/modeller/ai-proposal.js'
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
assert.match(source, /anchor: \(\) => \(\{ anchorType: 'figure', anchorId: id \}\)/)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('the draft is read when the message is sent, as host context the figure tools read', () => {
|
|
23
|
-
assert.match(source, /hostContext: \(\) => \(\{[\s\S]*?figureId: id,[\s\S]*?draftSource: JSON\.parse\(this\.session\.source\(\)\),[\s\S]*?context: this\.session\.read\(\)\.context,\s*revision: this\.session\.read\(\)\.revision,/)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('an unsaved figure provides nothing', () => {
|
|
27
|
-
assert.match(source, /if \(!view\.id\) \{\s*this\.withdrawAssistant\(\)/)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('the surface is taken back when the page goes inactive or leaves', () => {
|
|
31
|
-
const withdraws = source.match(/this\.withdrawAssistant\(\)/g) ?? []
|
|
32
|
-
assert.ok(withdraws.length >= 3, 'unsaved, inactive and disconnected each withdraw')
|
|
33
|
-
assert.doesNotMatch(source, /figure-ai-context/, 'the old window-event channel is gone')
|
|
34
|
-
})
|
|
18
|
+
const say = (key: string) => key
|
|
19
|
+
const K1 = '11111111-1111-4111-8111-111111111111'
|
|
20
|
+
const lid = [{ kind: 'add-part', part: { id: 'lid', kind: 'box', dimensions: { width: 100, height: 10, depth: 100 } } }] as never[]
|
|
35
21
|
|
|
36
|
-
test('the dock applies a proposal through the page, on the version the model saw', () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
test('the dock applies a proposal through the page, on the version the model saw', async () => {
|
|
23
|
+
const source = serializeFigureAsset(addFigureBox(createFigureAsset('A'), { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } }))
|
|
24
|
+
const session = createFigureProductSession({ load: async (id: string) => ({ id, source }), save: async (q: { id?: string; source: string }) => ({ id: q.id ?? 'a', source: q.source }) })
|
|
25
|
+
await session.open('a')
|
|
26
|
+
const seen = session.read()
|
|
27
|
+
/* The person edits meanwhile: the version the model saw is gone, and the proposal is refused, not put on top. */
|
|
28
|
+
session.command({ kind: 'rename', name: 'B' } as never)
|
|
29
|
+
const stale: FigureApplyDetail = { key: K1, steps: lid, create: false, context: seen.context, revision: seen.revision }
|
|
30
|
+
assert.equal(takeFigureOffer(session, stale, { onSavedFigure: true, say }), false)
|
|
31
|
+
assert.deepEqual({ applied: stale.applied, code: stale.code }, { applied: false, code: 'REVISION_CONFLICT' })
|
|
32
|
+
assert.deepEqual(session.proposalsInEffect(), [])
|
|
33
|
+
/* On the version now: applied as one step, under the proposal's key. */
|
|
34
|
+
const now = session.read()
|
|
35
|
+
const fresh: FigureApplyDetail = { key: K1, steps: lid, create: false, context: now.context, revision: now.revision }
|
|
36
|
+
assert.equal(takeFigureOffer(session, fresh, { onSavedFigure: true, say }), true)
|
|
37
|
+
assert.equal(fresh.applied, true)
|
|
38
|
+
assert.equal(session.read().revision, now.revision! + 1)
|
|
39
|
+
assert.deepEqual(session.proposalsInEffect(), [K1])
|
|
40
|
+
/* A new figure's proposal is not for a page on a saved figure: left untaken, so the handler opens a new one. */
|
|
41
|
+
const create: FigureApplyDetail = { key: K1, steps: lid, create: true }
|
|
42
|
+
assert.equal(takeFigureOffer(session, create, { onSavedFigure: true, say }), false)
|
|
43
|
+
assert.equal(create.applied, undefined)
|
|
43
44
|
})
|
|
44
45
|
|
|
45
46
|
test('a figure proposal is answered by its handler, in words, never left without one', () => {
|
|
46
|
-
const handler = readFileSync(fileURLToPath(new URL('../client/figure-assistant.ts', import.meta.url)), 'utf8')
|
|
47
|
-
assert.match(handler, /registerProposalHandler\(FIGURE_CANDIDATE_TOOL,/)
|
|
48
|
-
assert.match(handler, /export const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate'/)
|
|
49
|
-
assert.match(handler, /runFigureProposal\(/)
|
|
50
|
-
assert.match(handler, /context\.notify\(/)
|
|
51
47
|
for (const lang of ['en', 'ko', 'ja', 'zh', 'ms', 'es']) {
|
|
52
48
|
const words = JSON.parse(readFileSync(fileURLToPath(new URL(`../translations/${lang}.json`, import.meta.url)), 'utf8'))
|
|
53
49
|
for (const key of ['applied', 'stale', 'refused', 'needs-modeller', 'not-opened', 'new-figure-unsaved'])
|
|
54
50
|
assert.ok(words[`figure.text.proposal-${key}`], `${lang} has no words for proposal-${key}`)
|
|
55
51
|
}
|
|
56
|
-
const index = readFileSync(fileURLToPath(new URL('../client/index.ts', import.meta.url)), 'utf8')
|
|
57
|
-
assert.match(index, /figure-assistant\.js/)
|
|
58
52
|
})
|
|
@@ -31,78 +31,26 @@ test('a selected part at the head: its kind, and what its size follows only when
|
|
|
31
31
|
assert.ok(!/규칙을 따름|규칙으로 결정됨|규칙이 정하는|도형을 따라감|크기 고정|붙임 없음/.test(JSON.stringify(ko)), 'the words nobody knew are gone')
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
test('a new figure asks what to start with -- a shape, a recipe, an AI draft where offered -- and adds nothing by itself', () => {
|
|
35
|
-
const editor = read('../client/modeller/editor.ts')
|
|
36
|
-
const start = editor.slice(editor.indexOf(' private renderStart() {'), editor.indexOf(' /**\n * Which part the panel speaks of'))
|
|
37
|
-
assert.ok(start.includes("['box', 'cylinder', 'plate']") && start.includes('@click=${() => this.add(kind)}'), 'a shape, on the author\'s click')
|
|
38
|
-
assert.ok(start.includes('this.catalogOpen = true'), 'a recipe')
|
|
39
|
-
assert.ok(start.includes('${this.aiDraft') && start.includes("new CustomEvent('ask-ai-draft'"), 'an AI draft, only where the page offers it')
|
|
40
|
-
assert.ok(editor.includes('${view.geometry.length === 0 && !unavailable ? this.renderStart() : nothing}'), 'only while the figure is empty')
|
|
41
|
-
assert.ok(editor.includes('<summary>${i18next.t(\'figure.label.figure-info-later\')}</summary>'), 'the figure\'s own settings wait, folded')
|
|
42
|
-
const page = read('../client/pages/figure-modeller-page.ts')
|
|
43
|
-
assert.ok(page.includes("aiDraft @ask-ai-draft=${() => askAssistant({ draft: i18next.t('figure.text.ai-draft-request') })}"), 'the app puts a draft request in the dock, not sent')
|
|
44
|
-
const parts = read('../client/modeller/figure-parts.ts')
|
|
45
|
-
assert.ok(parts.includes('if (!b || (!this.parts.length && !b.level)) return nothing'), 'the detail level comes after the first part (U09)')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
34
|
test('the side tabs say their names, in every language', () => {
|
|
49
|
-
|
|
50
|
-
assert.ok(editor.includes("const name = i18next.t('figure.label.tab-short-' + tab)"))
|
|
51
|
-
assert.ok(editor.includes('aria-label=${name}'), 'its accessible name is the name it shows')
|
|
52
|
-
assert.ok(editor.includes('<span tab-label aria-hidden="true">${name}</span>'))
|
|
35
|
+
/* The name under each icon, and its accessible name, are the browser check's (test/browser/screen.check.mjs). */
|
|
53
36
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
|
|
54
37
|
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
55
38
|
for (const tab of ['properties', 'capability', 'motion', 'verdict', 'view', 'history']) assert.ok(table[`figure.label.tab-short-${tab}`], `${lang} ${tab}`)
|
|
56
39
|
}
|
|
57
40
|
})
|
|
58
41
|
|
|
59
|
-
test('narrow, the parts list folds for the canvas, said once, and stays open once the author opens it', () => {
|
|
60
|
-
const editor = read('../client/modeller/editor.ts')
|
|
61
|
-
const fit = editor.slice(editor.indexOf(' private fitPanesTo(width: number) {'), editor.indexOf(' /**\n * Refusals and notices'))
|
|
62
|
-
assert.ok(fit.includes('if (this.foldedForWidth && !panes.foldStart) this.keptOpenByAuthor = true'), 'opened by the author, left open')
|
|
63
|
-
assert.ok(fit.includes("notify({ level: 'info', message: i18next.t('figure.text.parts-panel-folded-narrow') })"), 'said')
|
|
64
|
-
assert.ok(fit.includes('if (this.foldedForWidth && panes.foldStart) panes.foldStart = false'), 'wide again, opened again')
|
|
65
|
-
assert.ok(editor.includes('private static readonly FOLD_START_UNDER = 1100'))
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('a palette part\'s own sizes are its shape\'s fields, not a rule\'s: no "set by a rule" size row for them', () => {
|
|
69
|
-
const editor = read('../client/modeller/editor.ts')
|
|
70
|
-
assert.ok(editor.includes("sizeRuled: (['x', 'y', 'z'] as const).every(axis => followOf(shown.asset, ownerOf(this.selected), axis).of !== 'own')"))
|
|
71
|
-
assert.ok(editor.includes('if (!held?.sizeRuled ||'))
|
|
72
|
-
})
|
|
73
|
-
|
|
74
42
|
/* Designer 2026-09-27: the placement is chosen before any part, as the figure's own setting -- no room, no part made up. */
|
|
75
43
|
test('the start card and the figure panel choose the placement as the figure\'s own setting', async () => {
|
|
76
44
|
const { createFigureAsset, applyFigureAuthoring } = await import('@hatiolab/figure-model')
|
|
77
45
|
const hung = applyFigureAuthoring(createFigureAsset('New'), { kind: 'set-placement', placement: 'ceiling' })
|
|
78
46
|
assert.equal(hung.placement, 'ceiling')
|
|
79
47
|
assert.equal(hung.occupancy, undefined)
|
|
80
|
-
|
|
81
|
-
assert.ok(editor.includes("this.linkCommand({ kind: 'set-placement', placement: one })"), 'on the start card')
|
|
82
|
-
const properties = read('../client/modeller/figure-properties.ts')
|
|
83
|
-
assert.ok(properties.includes("this.command({ kind: 'set-placement', placement: select.value as Placement })"), 'in the figure panel')
|
|
84
|
-
assert.ok(!properties.includes('?disabled=${!facts.parts.length}\n .value=${live(current)}'), 'not waiting for a part')
|
|
48
|
+
/* Chosen on the start card and in the figure panel, before any part: the browser check's (test/browser/screen.check.mjs). */
|
|
85
49
|
})
|
|
86
50
|
|
|
87
51
|
/* B.1 and the designer's items (2026-09-27): each target's panel is found as it was left; the fastening form says which it is. */
|
|
88
|
-
test('the panel remembers its scroll and folds per model and part, and forgets them for another model', () => {
|
|
89
|
-
const editor = read('../client/modeller/editor.ts')
|
|
90
|
-
assert.ok(editor.includes("if (panel) this.panelMemory.set(was || '@model', { top: panel.scrollTop, open: this.foldsOf(panel).map(d => d.open) })"))
|
|
91
|
-
assert.ok(editor.includes('panel.scrollTop = kept?.top ?? 0'))
|
|
92
|
-
assert.ok(editor.includes('this.panelMemory.clear()'), 'another model starts clean')
|
|
93
|
-
assert.ok(editor.includes("position: sticky;"), 'the path stays in view whatever the scroll')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
52
|
test('the fastening form says whether it edits the current relation or fastens anew', () => {
|
|
97
|
-
|
|
98
|
-
/* Anew is said; the one it has is said once, by the sentence over the drawing (user 2026-10-01: no third line). */
|
|
99
|
-
assert.ok(links.includes("${current ? nothing : html`<p fasten-mode>${editing}</p>`}"))
|
|
100
|
-
assert.ok(links.includes('<p fasten-sentence>'))
|
|
101
|
-
/* A fastening the part has is changed as soon as something is chosen (2026-10-02): 붙이기 only for a new one. */
|
|
102
|
-
assert.ok(links.includes("${held\n ? nothing\n : html`<button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>"))
|
|
103
|
-
assert.ok(links.includes('if (value) settle()'), 'another target taken at once')
|
|
104
|
-
assert.ok(links.includes("action && !held && this.working === 'fasten' ? this.renderPreviewWords() : nothing"), 'no words for a command no button sends')
|
|
105
|
-
assert.ok(links.includes('const settle = () => this.onFastenRelease(!!held)'))
|
|
53
|
+
/* Anew is said, the one it has changed at once, 붙이기 only for a new one: the browser check's (test/browser/screen.check.mjs). Here, the words. */
|
|
106
54
|
const ko = JSON.parse(read('../translations/ko.json'))
|
|
107
55
|
assert.equal(ko['figure.text.fasten-new'], '새로 붙이기')
|
|
108
56
|
assert.equal(ko['figure.button.fasten-update'], undefined)
|
|
@@ -8,7 +8,7 @@ import { readFileSync } from 'node:fs'
|
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
import { isPlaceholderType, TYPE_LENGTH, TYPE_PATTERN } from '@hatiolab/figure-model'
|
|
10
10
|
|
|
11
|
-
import { suggestTypeOf, releasableType } from '../client/modeller/first-save.js'
|
|
11
|
+
import { offeredTypeOf, suggestTypeOf, releasableType } from '../client/modeller/first-save.js'
|
|
12
12
|
|
|
13
13
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
14
14
|
|
|
@@ -27,14 +27,17 @@ test('an identifier is offered for any name: plain letters as they are, anything
|
|
|
27
27
|
assert.equal(suggestTypeOf('선반', ['fig-000000'], () => draws[n++ % draws.length]!), 'fig-iiiiii')
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
+
/* The question itself -- the name first and focused, a retry on the identifier answered -- is checked in product-page.check.mjs. */
|
|
30
31
|
test('the first save asks the name first; the identifier offered stays through retries, and renaming does not change it', () => {
|
|
31
|
-
|
|
32
|
-
assert.
|
|
33
|
-
|
|
34
|
-
assert.
|
|
35
|
-
assert.
|
|
36
|
-
|
|
37
|
-
assert.
|
|
32
|
+
/* None yet: one is offered, from the name. */
|
|
33
|
+
assert.deepEqual(offeredTypeOf('', 'Lift table', []), { type: 'lift-table', suggested: 'lift-table' })
|
|
34
|
+
/* Offered before and still free, or typed by the author: kept as it is. */
|
|
35
|
+
assert.deepEqual(offeredTypeOf('fig-abc123', 'Lift table', []), { type: 'fig-abc123', suggested: 'fig-abc123' })
|
|
36
|
+
assert.deepEqual(offeredTypeOf('my-lift', 'Other name', []), { type: 'my-lift' })
|
|
37
|
+
/* Offered and found taken: offered again. Typed and taken: kept as typed -- its refusal is said when saving. */
|
|
38
|
+
assert.deepEqual(offeredTypeOf('fig-abc123', 'Lift table', ['fig-abc123']), { type: 'lift-table', suggested: 'lift-table' })
|
|
39
|
+
assert.deepEqual(offeredTypeOf('my-lift', 'Lift table', ['my-lift']), { type: 'my-lift' })
|
|
40
|
+
assert.deepEqual(offeredTypeOf('fig-abc123', '선반', ['fig-abc123'], () => 0), { type: 'fig-000000', suggested: 'fig-000000' })
|
|
38
41
|
for (const lang of ['en', 'ja', 'ko', 'ms', 'zh', 'es']) {
|
|
39
42
|
const t = JSON.parse(read(`../translations/${lang}.json`))
|
|
40
43
|
assert.ok(t['figure.label.figure-identifier'] && t['figure.text.identifier-suggested'], lang)
|
package/test/first-save.test.ts
CHANGED
|
@@ -66,29 +66,7 @@ test('타입을 주지 않으면 싣지 않는다 — 서버가 임시 이름을
|
|
|
66
66
|
assert.equal('type' in requests[0]!, false)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
assert.doesNotMatch(field, /disabled=\$\{!view\.id\}/, '저장 전에도 칸에 적을 수 있다')
|
|
74
|
-
const rename = page.slice(page.indexOf('private renameType'), page.indexOf('private fitToParts'))
|
|
75
|
-
assert.match(rename, /if \(!id\) \{\s*this\.row = \{ \.\.\.this\.row, type: wanted \|\| undefined \}/, '저장 전에는 행 대신 화면이 타입을 쥔다')
|
|
76
|
-
assert.match(page, /<figure-first-save \.type=\$\{this\.row\.type \?\? ''\}/, '물음은 그 타입으로 열린다')
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('처음 저장한 도형은 주소가 제 것이 된다 — 모델러를 다시 열면 새 도형이 열린다 (2026-09-25)', async () => {
|
|
80
|
-
const { readFileSync } = await import('node:fs')
|
|
81
|
-
const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
|
|
82
|
-
const changed = page.slice(page.indexOf('private changed = () => {'), page.indexOf('private async readRow'))
|
|
83
|
-
assert.match(changed, /if \(view\.id && !this\.routeId && !view\.saving\) \{\s*this\.routeId = view\.id\s*navigate\(modellerPathOf\(\{ id: view\.id \}\), true\)/, '저장으로 id 를 얻으면 그 주소로 바꾼다(기록은 덮어쓴다)')
|
|
84
|
-
/* The early return that kept the old figure reads the route id; it now matches only the figure the address names. */
|
|
85
|
-
assert.match(page, /if \(this\.session\.read\(\)\.context && id === this\.routeId && !changes\.active\) return/)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('다른 도형을 열면 행을 먼저 비운다 — 앞 도형의 타입이 칸에 남지 않는다 (C2, 2026-09-25)', async () => {
|
|
89
|
-
const { readFileSync } = await import('node:fs')
|
|
90
|
-
const page = readFileSync(new URL('../client/pages/figure-modeller-page.ts', import.meta.url), 'utf8')
|
|
91
|
-
const updated = page.slice(page.indexOf('async pageUpdated'), page.indexOf('private askFirstSave'))
|
|
92
|
-
const opening = updated.slice(updated.indexOf('if (id) {'), updated.indexOf('this.session.open(id'))
|
|
93
|
-
assert.match(opening, /this\.row = \{ version: 0 \}/, '열기 전에 행을 비운다')
|
|
94
|
-
})
|
|
69
|
+
/*
|
|
70
|
+
On the page -- a type written before the first save carried into its question, the address made the figure's own,
|
|
71
|
+
the row emptied before another figure opens -- see test/browser/product-page.check.mjs.
|
|
72
|
+
*/
|
package/test/gizmo.test.ts
CHANGED
|
@@ -81,15 +81,10 @@ test('크기는 배율만큼 치수를 바꾸고 1mm 아래로 가지 않는다'
|
|
|
81
81
|
test('끈 것은 세션의 한 걸음이다 — 한 번의 되돌리기로 돌아간다', () => {
|
|
82
82
|
const workspace = createFigureEditorWorkspace() as unknown as Record<string, Function>
|
|
83
83
|
assert.ok(typeof workspace.apply === 'function')
|
|
84
|
-
const editor = read('../client/modeller/editor.ts')
|
|
85
|
-
const moved = editor.slice(editor.indexOf(' private transformed('), editor.indexOf(' /** 맞추기'))
|
|
86
|
-
assert.ok(moved.includes('this.run(() => this.session!.apply(changes))'), 'one apply, one step')
|
|
87
|
-
assert.ok(moved.includes("kind: 'nudge-part',"), 'a nudge is one command, one step')
|
|
88
84
|
})
|
|
89
85
|
|
|
90
86
|
test('캔버스 — 기즈모 · W E R · 스냅은 씬의 편집 모드가 한다', () => {
|
|
91
87
|
const canvas = read('../client/modeller/canvas.ts')
|
|
92
|
-
assert.match(canvas, /mode: 1\n/)
|
|
93
88
|
assert.doesNotMatch(canvas, /TransformControls|OrbitControls/)
|
|
94
89
|
})
|
|
95
90
|
|
|
@@ -110,14 +105,6 @@ test('떼어 낸 부품은 다시 옮길 수 있다 — 움직임이 있는 부
|
|
|
110
105
|
assert.equal(gizmoReachOf(slid as never, 'lid').translate, false, 'not its own numbers: a drag on a joint edits the joint (joint-drag)')
|
|
111
106
|
})
|
|
112
107
|
|
|
113
|
-
test('끄는 동안은 모아 두고 놓을 때 한 번 적는다 — 크기 기즈모가 편집기와 싸우지 않는다', () => {
|
|
114
|
-
const canvas = read('../client/modeller/canvas.ts')
|
|
115
|
-
const watch = canvas.slice(canvas.indexOf(' private watchScene()'), canvas.indexOf(' /** Components the gizmo changed'))
|
|
116
|
-
assert.ok(watch.includes('this.pending.set(name, origin!)') && watch.includes('if (!this.pointerDown) this.commitSoon(80)'), '사용자 2026-09-24: 「기즈모 컨트롤 중에 scale은 제대로 작동하지 않는군」')
|
|
117
|
-
assert.ok(!watch.includes("new CustomEvent('part-transformed'"), '움직일 때마다 알리지 않는다')
|
|
118
|
-
assert.ok(canvas.includes('private onPointerUp = () => {') && canvas.includes('this.commitSoon(0)'))
|
|
119
|
-
})
|
|
120
|
-
|
|
121
108
|
|
|
122
109
|
test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로', async () => {
|
|
123
110
|
const { addFigurePrimitive, createFigureAsset } = await import('@hatiolab/figure-model')
|
|
@@ -167,22 +154,10 @@ test('크기 칸과 손잡이 — 배치가 지키는 면을 지킨다, 커널
|
|
|
167
154
|
let hung = addFigureBox(createFigureAsset('Lamp'), { id: 'lamp', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: -50, z: 0 } })
|
|
168
155
|
hung = applyFigureAuthoring(hung, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(hung), placement: 'ceiling' })
|
|
169
156
|
assert.equal(centreY(grow(hung, 'lamp', gizmoReachOf(hung, 'lamp').sizes![1], 300), 'lamp') + 300 / 2, 0, '천장 도형: 윗면이 천장에')
|
|
170
|
-
const editor = read('../client/modeller/editor.ts')
|
|
171
|
-
assert.ok(editor.includes('figureSizeEditChanges(this.shown()!.asset, part, [change])'), '숫자 칸이 부른다')
|
|
172
|
-
assert.ok(editor.includes('figureSizeEditChanges(asset, ownerOf(id), written)'), '손잡이도 부른다')
|
|
173
|
-
assert.ok(!read('../client/modeller/gizmo.ts').includes('keepBottomChanges'), '화면의 제 산수는 없다')
|
|
174
157
|
})
|
|
175
158
|
|
|
176
159
|
/* 손잡이는 키를 몰라도 찾는다 — 도구 막대에 옮기기 · 돌리기 · 크기, 제 숫자로 놓인 부품에만 (09-26). */
|
|
177
160
|
test('도구 막대의 옮기기 · 돌리기 · 크기 — 키와 같은 길, 되쓸 수 있는 것만', () => {
|
|
178
|
-
const editor = read('../client/modeller/editor.ts')
|
|
179
|
-
const bar = editor.slice(editor.indexOf('return html`<ox-canvas-bar'), editor.indexOf('</ox-canvas-bar>'))
|
|
180
|
-
assert.ok(bar.includes("this.renderGizmoTools(!unavailable && this.middle === 'edit')"), '편집 중에만')
|
|
181
|
-
const tools = editor.slice(editor.indexOf(' private renderGizmoTools('), editor.indexOf(" /** The modeller's keys"))
|
|
182
|
-
for (const [mode, key, can] of [['translate', 'W', 'translate'], ['rotate', 'E', 'rotate'], ['scale', 'R', 'scale']])
|
|
183
|
-
assert.ok(tools.includes(`mode: '${mode}'`) && tools.includes(`key: '${key}'`) && tools.includes(`can: !!reach?.${can}`), mode)
|
|
184
|
-
assert.ok(tools.includes('@click=${() => this.useGizmo(tool.mode)}'))
|
|
185
|
-
assert.match(editor, /action === 'scale'\) canvas && this\.useGizmo\(action\)/, '키도 같은 길로 — 눌린 단추가 맞다')
|
|
186
161
|
const ko = JSON.parse(read('../translations/ko.json'))
|
|
187
162
|
assert.deepEqual(['translate', 'rotate', 'scale'].map(m => ko['figure.button.gizmo-' + m]), ['옮기기', '돌리기', '크기 바꾸기'])
|
|
188
163
|
})
|
package/test/joint-fan.test.ts
CHANGED
|
@@ -16,8 +16,6 @@ import { revisionOf } from './revision-of.ts'
|
|
|
16
16
|
import { liveOverSession } from '../client/modeller/live.ts'
|
|
17
17
|
import { moving } from './joints.ts'
|
|
18
18
|
|
|
19
|
-
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
20
|
-
|
|
21
19
|
function arm(): FigureAsset {
|
|
22
20
|
let asset = createFigureAsset('Probe')
|
|
23
21
|
asset = addFigureBox(asset, { id: 'base', size: { x: 200, y: 100, z: 200 }, position: { x: 0, y: 50, z: 0 } })
|
|
@@ -84,14 +82,6 @@ test('no fan for a part that does not turn, nor for a slide -- a fan in a guesse
|
|
|
84
82
|
assert.equal(jointFanOf(revisionOf(slider), 'block'), null)
|
|
85
83
|
})
|
|
86
84
|
|
|
87
|
-
test('the editor draws it for the selected part; the canvas never lets it be picked', () => {
|
|
88
|
-
const editor = read('../client/modeller/editor.ts')
|
|
89
|
-
assert.ok(editor.includes('.fan=${this.fanOf(shown, live)}'))
|
|
90
|
-
const canvas = read('../client/modeller/canvas.ts')
|
|
91
|
-
/* The fan, and what a command would do, are shown and never picked (ADR-0097 §5.5). */
|
|
92
|
-
assert.ok(canvas.includes('.map(c => ({ ...c, capturable: false, locked: true }))'))
|
|
93
|
-
})
|
|
94
|
-
|
|
95
85
|
/*
|
|
96
86
|
* The fan stands where the joint is drawn: the pose set with the controls (the version's inputs), and while a control is
|
|
97
87
|
* dragged, the pose in hand -- the same as a version where that pose was set (user 2026-09-24: the arm jumped on release).
|
package/test/keys.test.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { test } from 'node:test'
|
|
5
5
|
import assert from 'node:assert/strict'
|
|
6
|
-
import { readFileSync } from 'node:fs'
|
|
7
|
-
import { fileURLToPath } from 'node:url'
|
|
8
6
|
|
|
9
7
|
import { keyActionOf } from '../client/modeller/keys.ts'
|
|
10
8
|
|
|
@@ -30,11 +28,6 @@ test('칸에 치는 키는 칸의 것 — 이름 칸의 Backspace 는 글자를
|
|
|
30
28
|
assert.equal(keyActionOf({ key: 'Delete', target: { tagName: 'CANVAS' } }, 'arm'), 'remove')
|
|
31
29
|
})
|
|
32
30
|
|
|
33
|
-
test('편집기가 창의 키를 듣고, 떼어지면 놓는다', () => {
|
|
34
|
-
const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
|
|
35
|
-
assert.ok(editor.includes("window.addEventListener('keydown', this.onKey, true)") && editor.includes("window.removeEventListener('keydown', this.onKey, true)"))
|
|
36
|
-
})
|
|
37
|
-
|
|
38
31
|
test('W · E · R 은 gizmo 의 모드 — 부품 목록에서 골라 포커스가 캔버스 밖에 있어도', () => {
|
|
39
32
|
assert.equal(keyActionOf({ key: 'w' }, 'arm'), 'translate')
|
|
40
33
|
assert.equal(keyActionOf({ key: 'E' }, 'arm'), 'rotate')
|
package/test/layout.test.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* **모델러의 구성 — 세 칸과 오른쪽 탭** (사용자 2026-09-24).
|
|
3
3
|
*
|
|
4
4
|
* 부품 목록 · 캔버스 · 오른쪽 칸(아이콘 탭 여섯). 전에는 부품 속성이 캔버스 아래 좁은 칸에 끼어 있었다.
|
|
5
|
-
*
|
|
5
|
+
* 그 구성은 브라우저 검사(`test/browser/screen.check.mjs`)가 확인하고, 여기서는 화면의 말을 본다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
8
8
|
import assert from 'node:assert/strict'
|
|
@@ -13,57 +13,8 @@ const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.m
|
|
|
13
13
|
const editor = read('../client/modeller/editor.ts')
|
|
14
14
|
const renderBody = editor.slice(editor.indexOf(' render() {'), editor.indexOf(' private renderField('))
|
|
15
15
|
|
|
16
|
-
test('세 칸 — 부품 목록 · 캔버스 · 오른쪽 칸, 캔버스 아래에는 아무것도 없다', () => {
|
|
17
|
-
/* The three panes are ox-three-pane's (ADR-0060 decision 8): parts in start, the canvas in the middle, the side in end. */
|
|
18
|
-
const main = renderBody.slice(renderBody.indexOf('<ox-three-pane'), renderBody.indexOf('</ox-three-pane>'))
|
|
19
|
-
const order = ['<figure-parts', '<figure-canvas', '<aside slot="end">'].map(tag => main.indexOf(tag))
|
|
20
|
-
assert.match(main, /<figure-parts\s+slot="start"/, '부품 목록은 왼쪽 칸')
|
|
21
|
-
assert.ok(order.every(i => i >= 0) && order[0]! < order[1]! && order[1]! < order[2]!, '부품 · 캔버스 · 오른쪽 칸 순서')
|
|
22
|
-
assert.equal((main.match(/<aside/g) ?? []).length, 1, '오른쪽 칸은 하나 — 페이지가 따로 칸을 두지 않는다')
|
|
23
|
-
assert.doesNotMatch(read('../client/pages/figure-modeller-page.ts'), /<aside/)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('오른쪽 칸의 탭 여섯: 속성 · 능력 · 움직임 · 판정 · 환경 · 이력, 판정의 개수는 탭에 남는다', () => {
|
|
27
|
-
const tabs = [...renderBody.matchAll(/this\.renderTab\('([a-z]+)'/g)].map(m => m[1])
|
|
28
|
-
assert.deepEqual(tabs, ['properties', 'capability', 'motion', 'verdict', 'view', 'history'])
|
|
29
|
-
for (const tab of tabs) assert.ok(renderBody.includes(`?off=\${tab !== '${tab}'}`), `${tab} 는 감추되 떼지 않는다`)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('부품을 고르면 — 도형 전체의 탭(판정 · 이력)은 그 부품의 속성으로 넘어가고, 부품 탭은 새 부품을 보여 준다', () => {
|
|
33
|
-
const choose = editor.slice(editor.indexOf(' private choose('), editor.indexOf(' private asset()'))
|
|
34
|
-
assert.match(choose, /this\.selected = id/)
|
|
35
|
-
assert.match(choose, /if \(id && \(this\.tab === 'verdict' \|\| this\.tab === 'view' \|\| this\.tab === 'history'\)\) this\.tab = 'properties'/)
|
|
36
|
-
for (const from of ['@select-part=${(e: CustomEvent) => this.choose(e.detail.id)}', '@part-selected=${(e: CustomEvent<string>) => this.choose(e.detail)}'])
|
|
37
|
-
assert.ok(renderBody.includes(from), from)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
/* B.1 (designer 2026-09-27), amended by the user 2026-10-01: no path -- the list and the canvas show the choice; the list's root goes back. */
|
|
41
|
-
test('패널 머리에는 경로가 없고 부품의 세 줄만 — 모델 설정은 목록 루트의 한 동작으로 연다', () => {
|
|
42
|
-
assert.ok((renderBody.match(/\$\{this\.renderSubject\(\)\}/g) ?? []).length >= 3)
|
|
43
|
-
const subject = editor.slice(editor.indexOf(' private renderSubject()'), editor.indexOf(' private showModelSettings()'))
|
|
44
|
-
assert.ok(!subject.includes('<nav path'), 'no path')
|
|
45
|
-
assert.ok(subject.includes('if (!summary) return nothing'), 'nothing chosen: no head')
|
|
46
|
-
const show = editor.slice(editor.indexOf(' private showModelSettings()'), editor.indexOf(' render() {'))
|
|
47
|
-
assert.ok(show.includes("this.selected = ''") && show.includes("this.tab = 'properties'"), 'the model settings, on the properties tab')
|
|
48
|
-
assert.ok(!/session!?\.(command|apply|edit|undo)/.test(show), 'moving changes no data and no history')
|
|
49
|
-
assert.ok(editor.includes('@show-model-settings=${() => this.showModelSettings()}'), 'the list root, the same move')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('탭마다 제자리 — 속성엔 부품의 모양 · 재질, 자리와 능력엔 사다리와 자리, 움직임엔 움직임 · 조작값', () => {
|
|
53
|
-
const panel = (tab: string) => {
|
|
54
|
-
const start = renderBody.indexOf(`?off=\${tab !== '${tab}'}`)
|
|
55
|
-
return renderBody.slice(start, renderBody.indexOf('<div panel', start + 1))
|
|
56
|
-
}
|
|
57
|
-
assert.ok(panel('properties').includes("this.renderLinks(unavailable, 'shape')") && panel('properties').includes('<figure-material-panel'))
|
|
58
|
-
assert.ok(panel('capability').includes("this.renderLinks(unavailable, 'places')") && panel('capability').includes('<figure-capability-panel'))
|
|
59
|
-
assert.ok(panel('motion').includes("this.renderLinks(unavailable, 'motion')") && panel('motion').includes('this.renderControls('))
|
|
60
|
-
})
|
|
61
|
-
|
|
62
16
|
test('띠 — 칸 접기 · 되돌리기 · 전체 보기 · 저장 상태 · 저장, 말은 번역에서', () => {
|
|
63
|
-
/* The bar
|
|
64
|
-
const bar = renderBody.slice(renderBody.indexOf('<ox-canvas-bar'), renderBody.indexOf('</ox-canvas-bar>'))
|
|
65
|
-
for (const piece of ['.pane=${this.panes}', 'history', 'fit', 'undo-shortcut="Ctrl+Z"', 'redo-shortcut="Ctrl+Y"', 'session!.undo()', 'session!.redo()', '.fit()', 'role="status"', '@click=${this.save}'])
|
|
66
|
-
assert.ok(bar.includes(piece), piece)
|
|
17
|
+
/* The bar itself -- its folds, undo · redo · fit, the status and save -- is the browser check's (test/browser/screen.check.mjs). */
|
|
67
18
|
assert.doesNotMatch(renderBody, /[가-힣]/, '편집기의 화면 말에 한국어가 박혀 있지 않다')
|
|
68
19
|
const keys = [...editor.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
69
20
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
|
|
@@ -72,24 +23,9 @@ test('띠 — 칸 접기 · 되돌리기 · 전체 보기 · 저장 상태 ·
|
|
|
72
23
|
}
|
|
73
24
|
})
|
|
74
25
|
|
|
75
|
-
test('탭 아이콘은 오른쪽 칸 맨 위 — 타입 칸은 속성 탭 안의 첫 칸', () => {
|
|
76
|
-
const aside = renderBody.slice(renderBody.indexOf('<aside slot="end">'), renderBody.indexOf('</aside>'))
|
|
77
|
-
assert.match(aside, /^<aside slot="end">\s*<div tabs>/, '탭이 첫째')
|
|
78
|
-
const properties = aside.slice(aside.indexOf("?off=${tab !== 'properties'}"), aside.indexOf("?off=${tab !== 'capability'}"))
|
|
79
|
-
assert.ok(properties.includes('<slot name="figure"></slot>'))
|
|
80
|
-
const figure = read('../client/modeller/figure-properties.ts')
|
|
81
|
-
assert.match(figure, /<h3>\$\{i18next\.t\('figure\.label\.group-figure'\)\}<\/h3>\s*<slot name="type"><\/slot>/, '타입은 「도형」 절의 첫 칸')
|
|
82
|
-
const page = read('../client/pages/figure-modeller-page.ts')
|
|
83
|
-
assert.ok(page.includes('<label slot="type" type-field>'))
|
|
84
|
-
})
|
|
85
|
-
|
|
86
26
|
/* User 2026-10-01: 「맞추기 아이콘 … 무엇을 하는 것인지 모르겠다」 -- words, and what moves the part apart from what makes one. */
|
|
87
27
|
test('맞추기는 글자 단추로 — 옮기는 넷(가운데 X·Z, 면에 대기, 눈금)과 새 부품을 만드는 대칭 복사를 다른 줄에 둔다', () => {
|
|
88
|
-
|
|
89
|
-
assert.ok(!tools.includes('<md-icon>'), 'no icon-only buttons')
|
|
90
|
-
assert.ok(tools.includes("i18next.t('figure.label.tools')") && tools.includes("i18next.t('figure.label.mirror-copy')"), 'two rows, each named')
|
|
91
|
-
const plane = editor.slice(editor.indexOf(' private renderPlaneAlign('), editor.indexOf(' private alignToPlane()'))
|
|
92
|
-
assert.ok(!plane.includes('<md-icon>') && plane.includes('figure.button.align-to-${placement}'))
|
|
28
|
+
/* The two rows and their buttons in words are the browser check's (test/browser/screen.check.mjs); here, the words. */
|
|
93
29
|
const ko = JSON.parse(readFileSync(fileURLToPath(new URL('../translations/ko.json', import.meta.url)), 'utf8')) as Record<string, string>
|
|
94
30
|
assert.deepEqual(
|
|
95
31
|
['center-x', 'center-z', 'align-to-floor', 'snap', 'mirror-x'].map(k => ko[`figure.button.${k}`]),
|
|
@@ -99,12 +35,7 @@ test('맞추기는 글자 단추로 — 옮기는 넷(가운데 X·Z, 면에 대
|
|
|
99
35
|
})
|
|
100
36
|
|
|
101
37
|
test('불러오는 동안 빈 무대에 「도형을 추가해…」가 깜빡이지 않고, 좁은 화면 알림은 이 브라우저에서 한 번', () => {
|
|
102
|
-
|
|
103
|
-
/* A new figure's start card says how to begin; an empty stage otherwise is one loading, or with every part hidden. */
|
|
104
|
-
assert.ok(canvas.includes('return html`<div stage></div>`'))
|
|
105
|
-
assert.ok(!canvas.includes('add-a-shape-to-start') && !editor.includes('startShown'))
|
|
38
|
+
/* A new figure's start card says how to begin; an empty stage otherwise is one loading, or with every part hidden. The bare stage and the notice said once are the browser check's (test/browser/screen.check.mjs). */
|
|
106
39
|
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es'])
|
|
107
40
|
assert.equal((JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>)['figure.text.add-a-shape-to-start'], undefined, lang)
|
|
108
|
-
assert.ok(editor.includes("if (!foldSaid()) notify({ level: 'info', message: i18next.t('figure.text.parts-panel-folded-narrow') })"))
|
|
109
|
-
assert.ok(/function foldSaid\(\): boolean \{\s*try \{\s*if \(localStorage\.getItem\('figure-fold-said'\)\) return true\s*localStorage\.setItem\('figure-fold-said', '1'\)\s*\} catch/.test(editor), 'storage refused: said, as before')
|
|
110
41
|
})
|