@things-factory/figure-service 10.1.66 → 10.1.67
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/README.md +5 -8
- package/dist-server/service/figure/figure-format.d.ts +49 -31
- package/dist-server/service/figure/figure-format.js +47 -90
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-history.js +1 -1
- package/dist-server/service/figure/figure-history.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.js +5 -5
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-propose-v3.d.ts +1 -1
- package/dist-server/service/figure/figure-propose-v3.js +1 -1
- package/dist-server/service/figure/figure-propose-v3.js.map +1 -1
- package/dist-server/service/figure/figure-query.d.ts +6 -11
- package/dist-server/service/figure/figure-query.js +12 -40
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-rename.js +2 -11
- package/dist-server/service/figure/figure-rename.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +32 -112
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.d.ts +0 -17
- package/dist-server/service/figure/figure-type.js +4 -71
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure-v3-write.d.ts +1 -1
- package/dist-server/service/figure/figure-v3-write.js +2 -3
- package/dist-server/service/figure/figure-v3-write.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +5 -10
- package/dist-server/service/figure/figure.js +6 -11
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/index.js +1 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/service/figure/figure-blueprint-query.test.ts +29 -48
- package/server/service/figure/figure-format.test.ts +54 -110
- package/server/service/figure/figure-format.ts +77 -87
- package/server/service/figure/figure-history.ts +1 -1
- package/server/service/figure/figure-inspect-db.test.ts +1 -11
- package/server/service/figure/figure-mutation.ts +7 -7
- package/server/service/figure/figure-propose-v3.test.ts +2 -2
- package/server/service/figure/figure-propose-v3.ts +1 -1
- package/server/service/figure/figure-query.ts +14 -36
- package/server/service/figure/figure-rename-db.test.ts +1 -1
- package/server/service/figure/figure-rename.test.ts +4 -18
- package/server/service/figure/figure-rename.ts +2 -12
- package/server/service/figure/figure-tools.test.ts +17 -80
- package/server/service/figure/figure-tools.ts +30 -125
- package/server/service/figure/figure-type.ts +3 -52
- package/server/service/figure/figure-v3-write.ts +3 -4
- package/server/service/figure/figure.ts +7 -12
- package/server/service/index.ts +1 -1
- package/dist-server/service/figure/figure-gate.d.ts +0 -93
- package/dist-server/service/figure/figure-gate.js +0 -302
- package/dist-server/service/figure/figure-gate.js.map +0 -1
- package/dist-server/service/figure/figure-source.d.ts +0 -45
- package/dist-server/service/figure/figure-source.js +0 -59
- package/dist-server/service/figure/figure-source.js.map +0 -1
- package/server/service/figure/figure-gate.test.ts +0 -155
- package/server/service/figure/figure-gate.ts +0 -392
- package/server/service/figure/figure-source.test.ts +0 -149
- package/server/service/figure/figure-source.ts +0 -94
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.67",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@hatiolab/figure-model": "^0.1.
|
|
26
|
+
"@hatiolab/figure-model": "^0.1.78",
|
|
27
27
|
"@operato/typeorm-history": "^10.0.0",
|
|
28
28
|
"@things-factory/ai-client-base": "^10.1.53",
|
|
29
29
|
"@things-factory/auth-base": "^10.1.47",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"graphql-upload": "^15.0.2",
|
|
34
34
|
"koa-router": "^7.4.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "cd3f81fbf667f513dfcfb90906cb97ba935373f5"
|
|
37
37
|
}
|
|
@@ -26,25 +26,19 @@ function loadCompiled(modulePath: string): any {
|
|
|
26
26
|
return require(resolved)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
version: 2,
|
|
31
|
-
type: 'TEST_BOX',
|
|
32
|
-
base: { x: 100, y: 100, z: 100 },
|
|
33
|
-
parts: [
|
|
34
|
-
{
|
|
35
|
-
name: 'box-1',
|
|
36
|
-
primitive: 'cube',
|
|
37
|
-
transform: { position: { x: 0, y: 50, z: 0 }, size: { x: 100, y: 100, z: 100 } },
|
|
38
|
-
material: { token: 'palette.primary' }
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
})
|
|
29
|
+
const model = require('@hatiolab/figure-model')
|
|
42
30
|
|
|
43
|
-
/**
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
/** A released figure's source: one box, named `part`, `size` mm on each side. */
|
|
32
|
+
const box = (part = 'box-1', size = 100) =>
|
|
33
|
+
model.serializeV3Asset(model.addV3Box(model.createV3Asset('Test box'), { id: part, size: { x: size, y: size, z: size }, position: { x: 0, y: size / 2, z: 0 } }))
|
|
34
|
+
const sampleSource = box()
|
|
35
|
+
|
|
36
|
+
/** The same box under a different part name, so a test can tell the sources apart. */
|
|
37
|
+
const boxNamed = (_type: string, part: string) => box(part)
|
|
38
|
+
|
|
39
|
+
/** What a consumer was handed: the part ids of the asset, and a part's width. */
|
|
40
|
+
const partsOf = (payload: any): string[] => payload.asset.document.model.nodes.filter((n: any) => n.op === 'place@1').map((n: any) => n.id)
|
|
41
|
+
const widthOf = (payload: any, part: string): number => payload.asset.designInputs[`${part}.size.x`]
|
|
48
42
|
|
|
49
43
|
beforeAll(async () => {
|
|
50
44
|
Figure = loadCompiled('service/figure/figure').Figure
|
|
@@ -91,7 +85,7 @@ function makeContext() {
|
|
|
91
85
|
} as any
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)
|
|
88
|
+
describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)에 발행된 자산 전달', () => {
|
|
95
89
|
it('존재하지 않는 타입은 null을 반환한다', async () => {
|
|
96
90
|
const query = new FigureQuery()
|
|
97
91
|
const result = await query.figureBlueprint('NON_EXISTENT', makeContext())
|
|
@@ -114,7 +108,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
114
108
|
expect(result).toBeNull()
|
|
115
109
|
})
|
|
116
110
|
|
|
117
|
-
it('발행된(released) Figure는
|
|
111
|
+
it('발행된(released) Figure는 자산을 format · type 과 함께 반환한다', async () => {
|
|
118
112
|
const repo = dataSource!.getRepository(Figure)
|
|
119
113
|
await repo.save({
|
|
120
114
|
domain: testDomain,
|
|
@@ -128,37 +122,25 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
128
122
|
const query = new FigureQuery()
|
|
129
123
|
const result = await query.figureBlueprint('RELEASED_FIGURE', makeContext())
|
|
130
124
|
expect(result).not.toBeNull()
|
|
131
|
-
expect(result.
|
|
132
|
-
expect(result.
|
|
133
|
-
expect(result.
|
|
134
|
-
expect(result
|
|
125
|
+
expect(result.format).toBe('v3')
|
|
126
|
+
expect(result.type).toBe('RELEASED_FIGURE')
|
|
127
|
+
expect(result.asset.version).toBe('figure-v3-asset-1')
|
|
128
|
+
expect(partsOf(result)).toEqual(['box-1'])
|
|
135
129
|
})
|
|
136
130
|
|
|
137
|
-
it('현재 저작 중(draft)이더라도 과거 발행판(FigureHistory)이 있으면 최신 발행판을
|
|
131
|
+
it('현재 저작 중(draft)이더라도 과거 발행판(FigureHistory)이 있으면 최신 발행판을 반환한다', async () => {
|
|
138
132
|
const repo = dataSource!.getRepository(Figure)
|
|
139
133
|
const historyRepo = dataSource!.getRepository(FigureHistory)
|
|
140
134
|
|
|
141
135
|
const v1Source = sampleSource
|
|
142
|
-
const
|
|
143
|
-
version: 2,
|
|
144
|
-
type: 'UPDATING_FIGURE',
|
|
145
|
-
base: { x: 200, y: 200, z: 200 },
|
|
146
|
-
parts: [
|
|
147
|
-
{
|
|
148
|
-
name: 'box-modified',
|
|
149
|
-
primitive: 'cube',
|
|
150
|
-
transform: { position: { x: 0, y: 100, z: 0 }, size: { x: 200, y: 200, z: 200 } },
|
|
151
|
-
material: { token: 'palette.primary' }
|
|
152
|
-
}
|
|
153
|
-
]
|
|
154
|
-
})
|
|
136
|
+
const draftSource = box('box-modified', 200)
|
|
155
137
|
|
|
156
138
|
const figure = await repo.save({
|
|
157
139
|
domain: testDomain,
|
|
158
140
|
type: 'UPDATING_FIGURE',
|
|
159
141
|
name: '수정 중인 도형',
|
|
160
142
|
state: 'draft',
|
|
161
|
-
source:
|
|
143
|
+
source: draftSource,
|
|
162
144
|
version: 1
|
|
163
145
|
})
|
|
164
146
|
|
|
@@ -178,8 +160,8 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
178
160
|
|
|
179
161
|
expect(result).not.toBeNull()
|
|
180
162
|
// 소비처는 수정 중인 초안(box-modified, size 200)이 아니라 기발행된 판(box-1, size 100)을 본다!
|
|
181
|
-
expect(result
|
|
182
|
-
expect(result
|
|
163
|
+
expect(partsOf(result)).toEqual(['box-1'])
|
|
164
|
+
expect(widthOf(result, 'box-1')).toBe(100)
|
|
183
165
|
})
|
|
184
166
|
|
|
185
167
|
/*
|
|
@@ -217,7 +199,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
217
199
|
const result = await new FigureQuery().figureBlueprint('MULTI', makeContext())
|
|
218
200
|
|
|
219
201
|
expect(result).not.toBeNull()
|
|
220
|
-
expect(result
|
|
202
|
+
expect(partsOf(result)).toEqual(['v3'])
|
|
221
203
|
})
|
|
222
204
|
|
|
223
205
|
/*
|
|
@@ -261,7 +243,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
261
243
|
const result = await new FigureQuery().figureBlueprint('WIP', makeContext())
|
|
262
244
|
|
|
263
245
|
expect(result).not.toBeNull()
|
|
264
|
-
expect(result
|
|
246
|
+
expect(partsOf(result)).toEqual(['released-v2'])
|
|
265
247
|
})
|
|
266
248
|
|
|
267
249
|
/*
|
|
@@ -393,8 +375,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
393
375
|
})
|
|
394
376
|
|
|
395
377
|
describe('a child domain shadows its parent only with a released version', () => {
|
|
396
|
-
const boxOf = (
|
|
397
|
-
JSON.stringify({ ...JSON.parse(sampleSource), type, parts: [{ ...JSON.parse(sampleSource).parts[0], name: part }] })
|
|
378
|
+
const boxOf = (_type: string, part: string) => box(part)
|
|
398
379
|
|
|
399
380
|
let childSeq = 0
|
|
400
381
|
|
|
@@ -419,7 +400,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
419
400
|
const result = await new FigureQuery().figureBlueprint('CONVEYOR', contextIn(child))
|
|
420
401
|
|
|
421
402
|
expect(result).not.toBeNull()
|
|
422
|
-
expect(result
|
|
403
|
+
expect(partsOf(result)).toEqual(['standard'])
|
|
423
404
|
})
|
|
424
405
|
|
|
425
406
|
it('uses the child release when the child has one', async () => {
|
|
@@ -431,7 +412,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
431
412
|
|
|
432
413
|
const result = await new FigureQuery().figureBlueprint('CONVEYOR', contextIn(child))
|
|
433
414
|
|
|
434
|
-
expect(result
|
|
415
|
+
expect(partsOf(result)).toEqual(['tenant'])
|
|
435
416
|
})
|
|
436
417
|
|
|
437
418
|
/*
|
|
@@ -462,7 +443,7 @@ describe('figureBlueprint 질의 — 소비처(board, twin, things-scene)를 위
|
|
|
462
443
|
const result = await new FigureQuery().figureBlueprint('CONVEYOR', contextIn(child))
|
|
463
444
|
|
|
464
445
|
expect(result).not.toBeNull()
|
|
465
|
-
expect(result
|
|
446
|
+
expect(partsOf(result)).toEqual(['tenant-v1'])
|
|
466
447
|
})
|
|
467
448
|
|
|
468
449
|
/*
|
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* figure-format —
|
|
2
|
+
* figure-format — the doors a stored source goes through: save, release, hand to a board.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - 판은 자산이 적은 `version` 으로 안다. 생김새로 짐작하지 않는다.
|
|
9
|
-
* - 판을 모르는 정본은 거절한다 — 알아보지 못하는 것을 저장하면 안 된다.
|
|
10
|
-
* - V3 자산도 저장·발행·되돌리기·소비처를 지난다. 이 넷은 전에 V2 만 지날 수 있었다.
|
|
11
|
-
* - 예산 칸(`triangles`·`groups`)은 두 판에서 같은 뜻이다.
|
|
4
|
+
* What this file holds:
|
|
5
|
+
* - the format is known from the `version` the asset carries, never guessed from its shape;
|
|
6
|
+
* - a source that does not name the format is refused -- what cannot be identified is not stored;
|
|
7
|
+
* - the budget columns (`triangles`, `groups`) and the score are the kernel's.
|
|
12
8
|
*/
|
|
13
9
|
import { createV3Asset, addV3Box, addV3Primitive, serializeV3Asset, applyV3Authoring, proposeV3Occupancy, placeholderType, v3ScoreOf } from '@hatiolab/figure-model'
|
|
14
10
|
import type { V3Asset } from '@hatiolab/figure-model'
|
|
15
11
|
|
|
16
|
-
import {
|
|
17
|
-
import { FigureSourceError } from './figure-source'
|
|
18
|
-
|
|
19
|
-
/** 온전한 V2 정본 하나. 바닥에 선 상자 한 부품. */
|
|
20
|
-
const v2Source = (over: Record<string, unknown> = {}) =>
|
|
21
|
-
JSON.stringify({
|
|
22
|
-
version: 2,
|
|
23
|
-
type: 'MIXER',
|
|
24
|
-
base: { x: 300, y: 800, z: 300 },
|
|
25
|
-
detailLevel: 'M',
|
|
26
|
-
parts: [
|
|
27
|
-
{
|
|
28
|
-
name: 'body',
|
|
29
|
-
primitive: 'cube',
|
|
30
|
-
transform: { position: { x: 0, y: 50, z: 0 }, size: { x: 100, y: 100, z: 100 } },
|
|
31
|
-
material: { token: 'palette.primary' }
|
|
32
|
-
}
|
|
33
|
-
],
|
|
34
|
-
...over
|
|
35
|
-
})
|
|
12
|
+
import { FigureSourceError, consumablePayloadOf, inspectSource, parseSource } from './figure-format'
|
|
36
13
|
|
|
37
14
|
/** 온전한 V3 자산 하나: 바닥에 선 상자와, 부품에서 제안받아 확정한 점유 부피. */
|
|
38
15
|
function v3Asset(): V3Asset {
|
|
@@ -50,68 +27,56 @@ const v3Source = () => serializeV3Asset(v3Asset())
|
|
|
50
27
|
|
|
51
28
|
describe('figure-format — 판을 알아보는 문', () => {
|
|
52
29
|
describe('판은 자산이 적는다', () => {
|
|
53
|
-
it('숫자 version 은 V2, figure-v3-asset-1 은 V3 다', () => {
|
|
54
|
-
expect(formatOf(v2Source())).toBe('v2')
|
|
55
|
-
expect(formatOf(v3Source())).toBe('v3')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
30
|
it('판을 안 적은 정본은 거절한다 — 생김새로 짐작하지 않는다', () => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
31
|
+
for (const text of [JSON.stringify({ type: 'MIXER', parts: [] }), JSON.stringify({ version: 2, type: 'MIXER', parts: [] })]) {
|
|
32
|
+
expect(() => parseSource(text)).toThrow(FigureSourceError)
|
|
33
|
+
try {
|
|
34
|
+
parseSource(text)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
expect((e as FigureSourceError).findings[0].code).toBe('source-format-unknown')
|
|
37
|
+
}
|
|
64
38
|
}
|
|
65
39
|
})
|
|
66
40
|
|
|
67
41
|
it('JSON 이 아니면 그 말로 거절한다', () => {
|
|
68
42
|
try {
|
|
69
|
-
|
|
43
|
+
parseSource('{')
|
|
70
44
|
} catch (e) {
|
|
71
45
|
expect((e as FigureSourceError).findings[0].code).toBe('source-not-json')
|
|
72
46
|
}
|
|
73
47
|
})
|
|
74
48
|
})
|
|
75
49
|
|
|
76
|
-
describe('저장
|
|
77
|
-
it('
|
|
78
|
-
const parsed =
|
|
79
|
-
expect(parsed.format).toBe('v2')
|
|
80
|
-
expect(parsed.derived.triangles).toBe(12)
|
|
81
|
-
expect(parsed.derived.groups).toBeGreaterThan(0)
|
|
82
|
-
expect(typeof parsed.derived.score).toBe('number')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('V3 자산은 같은 예산 칸을 받는다 — 상자 하나는 두 판에서 다 삼각형 12 다', () => {
|
|
86
|
-
const parsed = parseAnySource(v3Source())
|
|
87
|
-
expect(parsed.format).toBe('v3')
|
|
50
|
+
describe('저장', () => {
|
|
51
|
+
it('예산 칸을 커널이 잰다 — 상자 하나는 삼각형 12, 묶음 1', () => {
|
|
52
|
+
const parsed = parseSource(v3Source())
|
|
88
53
|
expect(parsed.derived.triangles).toBe(12)
|
|
89
54
|
expect(parsed.derived.groups).toBe(1)
|
|
90
55
|
})
|
|
91
56
|
|
|
92
|
-
it('
|
|
93
|
-
const score =
|
|
57
|
+
it('점수 칸은 v3ScoreOf 다 — 저작면의 성숙도 탭과 같은 수(수석 아키텍트 판정 09-24)', () => {
|
|
58
|
+
const score = parseSource(v3Source()).derived.score
|
|
94
59
|
expect(score).toBe(v3ScoreOf(v3Asset())!.score)
|
|
95
60
|
expect(score).toBeGreaterThan(0)
|
|
96
61
|
})
|
|
97
62
|
|
|
98
|
-
it('형식을 어긴
|
|
63
|
+
it('형식을 어긴 자산은 거절한다', () => {
|
|
99
64
|
const broken = JSON.parse(v3Source())
|
|
100
65
|
broken.appearance = []
|
|
101
|
-
expect(() =>
|
|
66
|
+
expect(() => parseSource(JSON.stringify(broken))).toThrow(FigureSourceError)
|
|
102
67
|
})
|
|
103
68
|
})
|
|
104
69
|
|
|
105
|
-
describe('발행 판정
|
|
70
|
+
describe('발행 판정', () => {
|
|
106
71
|
it('제 부피 안에 든 V3 자산은 막지 않는다', () => {
|
|
107
|
-
const inspection =
|
|
72
|
+
const inspection = inspectSource(v3Source())
|
|
108
73
|
expect(inspection.blocked).toBe(false)
|
|
109
74
|
})
|
|
110
75
|
|
|
111
76
|
it('선언한 부피 밖으로 나간 부품은 이름과 함께 막는다', () => {
|
|
112
77
|
let asset = v3Asset()
|
|
113
78
|
asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 100, z: 100 }, position: { x: 900, y: 50, z: 0 } })
|
|
114
|
-
const inspection =
|
|
79
|
+
const inspection = inspectSource(serializeV3Asset(asset))
|
|
115
80
|
expect(inspection.blocked).toBe(true)
|
|
116
81
|
expect(inspection.findings.some(f => f.code === 'part-outside-occupancy' && f.at === 'arm')).toBe(true)
|
|
117
82
|
})
|
|
@@ -119,7 +84,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
119
84
|
it('부피를 선언하지 않은 V3 자산은 막는다', () => {
|
|
120
85
|
let asset = createV3Asset('Bare')
|
|
121
86
|
asset = addV3Box(asset, { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
122
|
-
const inspection =
|
|
87
|
+
const inspection = inspectSource(serializeV3Asset(asset))
|
|
123
88
|
expect(inspection.blocked).toBe(true)
|
|
124
89
|
expect(inspection.findings.some(f => f.code === 'occupancy-undeclared')).toBe(true)
|
|
125
90
|
})
|
|
@@ -149,7 +114,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
149
114
|
placement: 'floor'
|
|
150
115
|
})
|
|
151
116
|
|
|
152
|
-
const inspection =
|
|
117
|
+
const inspection = inspectSource(serializeV3Asset(asset))
|
|
153
118
|
const sampled = inspection.findings.find(f => f.code === 'state-range-sampled')
|
|
154
119
|
expect(sampled).toBeDefined()
|
|
155
120
|
expect(sampled!.blocking).toBe(false)
|
|
@@ -159,11 +124,11 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
159
124
|
})
|
|
160
125
|
|
|
161
126
|
it('정본이 없으면 전과 같이 그 말로 막는다', () => {
|
|
162
|
-
expect(
|
|
127
|
+
expect(inspectSource(null).findings[0].code).toBe('no-source')
|
|
163
128
|
})
|
|
164
129
|
|
|
165
130
|
it('바뀐 것 없는 V3 재발행은 아무 말도 없이 지난다', () => {
|
|
166
|
-
const again =
|
|
131
|
+
const again = inspectSource(v3Source(), v3Source())
|
|
167
132
|
expect(again.blocked).toBe(false)
|
|
168
133
|
expect(again.findings.filter(f => f.code !== 'state-range-sampled')).toEqual([])
|
|
169
134
|
})
|
|
@@ -171,7 +136,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
171
136
|
it('놓인 인스턴스가 기대던 것을 없앤 V3 재발행은 막는다 — 없어진 능력을 이름으로 댄다', () => {
|
|
172
137
|
const before = v3Asset()
|
|
173
138
|
before.document.capabilities.names = ['Holdable']
|
|
174
|
-
const inspection =
|
|
139
|
+
const inspection = inspectSource(v3Source(), serializeV3Asset(before))
|
|
175
140
|
const gone = inspection.findings.find(f => f.code === 'capability-gone')
|
|
176
141
|
expect(gone).toBeDefined()
|
|
177
142
|
expect(gone!.blocking).toBe(true)
|
|
@@ -180,24 +145,11 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
180
145
|
expect(gone!.how).toBeTruthy()
|
|
181
146
|
expect(inspection.blocked).toBe(true)
|
|
182
147
|
})
|
|
183
|
-
|
|
184
|
-
it('직전 발행판이 V2 면 견주지 못했다고 말한다 — 막지는 않는다', () => {
|
|
185
|
-
/* 전환한 도형의 직전 발행판은 모두 V2 다. 두 판을 견주는 길은 없으니 그 사실을 적는다. */
|
|
186
|
-
const converted = inspectAnySource(v3Source(), v2Source())
|
|
187
|
-
const warning = converted.findings.find(f => f.code === 'released-unreadable')
|
|
188
|
-
expect(warning).toBeDefined()
|
|
189
|
-
expect(warning!.blocking).toBe(false)
|
|
190
|
-
expect(converted.blocked).toBe(false)
|
|
191
|
-
|
|
192
|
-
/* 첫 발행에는 견줄 것이 없으니 그 말도 없다. */
|
|
193
|
-
expect(inspectAnySource(v3Source()).findings.some(f => f.code === 'released-unreadable')).toBe(false)
|
|
194
|
-
})
|
|
195
148
|
})
|
|
196
149
|
|
|
197
150
|
/*
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
그 모양이다. 문장은 요청의 언어를 아는 서버가 코드와 값에서 만든다(ADR-0054).
|
|
151
|
+
이 판정은 한때 발행 패널에 영어 문장(`detail`)을 그대로 냈고 왜 · 어떻게가 없었다 — 사용자가 2026-09-19 에
|
|
152
|
+
「가장 큰 문제」로 짚은 모양이다. 문장은 요청의 언어를 아는 서버가 코드와 값에서 만든다(ADR-0054).
|
|
201
153
|
*/
|
|
202
154
|
describe('V3 판정의 문장 — 발행 패널의 말로', () => {
|
|
203
155
|
const KO = require('../../../translations/ko.json') as Record<string, string>
|
|
@@ -223,7 +175,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
223
175
|
}
|
|
224
176
|
|
|
225
177
|
it('넘은 부품은 이름 · 축 · 넘은 양을 화면 말투로 말하고, 왜와 어떻게를 함께 낸다', () => {
|
|
226
|
-
const finding =
|
|
178
|
+
const finding = inspectSource(outside(), null, translatorOf(KO)).findings.find(f => f.code === 'part-outside-occupancy')!
|
|
227
179
|
|
|
228
180
|
expect(finding.message).toMatch(/^'arm' 부품이 공간 선언을 X 로 [\d.]+mm 넘습니다\.$/)
|
|
229
181
|
expect(finding.why).toMatch(/니다\.$/)
|
|
@@ -234,7 +186,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
234
186
|
})
|
|
235
187
|
|
|
236
188
|
it('공간을 선언하지 않은 도형은 무엇을 누르면 되는지 말한다', () => {
|
|
237
|
-
const finding =
|
|
189
|
+
const finding = inspectSource(bare(), null, translatorOf(KO)).findings.find(f => f.code === 'occupancy-undeclared')!
|
|
238
190
|
|
|
239
191
|
expect(finding.message).toBe('이 도형은 차지하는 공간을 아직 선언하지 않았습니다.')
|
|
240
192
|
expect(finding.how).toContain('부품에 맞추기')
|
|
@@ -242,14 +194,14 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
242
194
|
|
|
243
195
|
it('부품이 없는 새 도형에는 도형을 먼저 추가하라고 한다 — 화면은 그때 맞추기 단추를 내지 않는다', () => {
|
|
244
196
|
const empty = serializeV3Asset(createV3Asset('Empty'))
|
|
245
|
-
const finding =
|
|
197
|
+
const finding = inspectSource(empty, null, translatorOf(KO)).findings.find(f => f.code === 'occupancy-undeclared')!
|
|
246
198
|
|
|
247
199
|
expect(finding.how).toBe('먼저 도형을 추가한 뒤, 「부품에 맞추기」로 공간을 선언하세요.')
|
|
248
|
-
expect(
|
|
200
|
+
expect(inspectSource(empty).findings.find(f => f.code === 'occupancy-undeclared')!.how).toMatch(/^Add a shape first/)
|
|
249
201
|
})
|
|
250
202
|
|
|
251
203
|
/*
|
|
252
|
-
|
|
204
|
+
(2026-09-24) 드라이룸 램프처럼 — 붙어 있던 부품이 도형을 키우면 떨어진다.
|
|
253
205
|
여기서는 손으로 만든다: 램프의 높이가 도형 높이(size.y)를 따라 올라가고, 몸통은 그대로다.
|
|
254
206
|
*/
|
|
255
207
|
const splitting = () => {
|
|
@@ -266,7 +218,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
266
218
|
}
|
|
267
219
|
|
|
268
220
|
it('크기를 바꾸면 떨어지는 부품을 막고, 어느 부품이 어느 축에서인지 · 왜 · 어떻게를 말한다', () => {
|
|
269
|
-
const finding =
|
|
221
|
+
const finding = inspectSource(splitting(), null, translatorOf(KO)).findings.find(f => f.code === 'parts-split-on-resize')
|
|
270
222
|
|
|
271
223
|
expect(finding?.blocking).toBe(true)
|
|
272
224
|
expect(finding!.message).toBe("크기를 Y 로 2배로 하면 'lamp' 부품이 나머지에서 떨어집니다.")
|
|
@@ -285,7 +237,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
285
237
|
asset.designInputs[`size.${axis}`] = 100
|
|
286
238
|
}
|
|
287
239
|
asset = applyV3Authoring(asset, { kind: 'scale-uniformly' } as never)
|
|
288
|
-
const finding =
|
|
240
|
+
const finding = inspectSource(serializeV3Asset(asset), null, translatorOf(KO)).findings.find(f => f.code === 'sizing-not-uniform')
|
|
289
241
|
|
|
290
242
|
expect(finding?.blocking).toBe(true)
|
|
291
243
|
expect(finding!.message).toBe("이 도형은 한 비율로 커지는데, 크기를 X 로 2배로 하면 'body' 부품은 같이 커지지 않습니다.")
|
|
@@ -303,7 +255,7 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
303
255
|
interpolation: { prefix: '{', suffix: '}' },
|
|
304
256
|
keySeparator: false
|
|
305
257
|
})
|
|
306
|
-
const finding =
|
|
258
|
+
const finding = inspectSource(splitting(), null, i18next.t.bind(i18next)).findings.find(f => f.code === 'parts-split-on-resize')!
|
|
307
259
|
|
|
308
260
|
expect(finding.message).toBe("크기를 Y 로 2배로 하면 'lamp' 부품이 나머지에서 떨어집니다.")
|
|
309
261
|
expect(`${finding.message}${finding.how}`).not.toContain(''')
|
|
@@ -318,36 +270,36 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
318
270
|
})
|
|
319
271
|
|
|
320
272
|
it('번역이 없으면 영어로 답한다 — 키를 찍지 않는다', () => {
|
|
321
|
-
const finding =
|
|
273
|
+
const finding = inspectSource(outside()).findings.find(f => f.code === 'part-outside-occupancy')!
|
|
322
274
|
|
|
323
275
|
expect(finding.message).toMatch(/^'arm' goes [\d.]+ mm past the declared space on X\.$/)
|
|
324
276
|
expect(finding.message).not.toContain('figure.gate.')
|
|
325
277
|
})
|
|
326
278
|
|
|
327
279
|
/*
|
|
328
|
-
|
|
329
|
-
|
|
280
|
+
자산에는 type 이 없어 임시 이름 검사가 돌지 않았고, 새 도형이 `FIGURE_<uuid>` 로 발행됐다(:3300, 2026-09-24).
|
|
281
|
+
발행하면 이름이 굳으므로 행의 type 으로 거절한다.
|
|
330
282
|
*/
|
|
331
|
-
it('임시 이름인
|
|
332
|
-
/* The name a new
|
|
283
|
+
it('임시 이름인 도형은 발행을 막는다 — 이름은 행에 있다', () => {
|
|
284
|
+
/* The name a new figure gets — the format's rule, the one the gate recognises. */
|
|
333
285
|
const placeholder = placeholderType()
|
|
334
|
-
const finding =
|
|
286
|
+
const finding = inspectSource(v3Source(), null, translatorOf(KO), placeholder).findings.find(f => f.code === 'type-is-placeholder')
|
|
335
287
|
|
|
336
288
|
expect(finding?.blocking).toBe(true)
|
|
337
289
|
expect(finding!.message).toBe(`'${placeholder}' 타입은 처음 저장할 때 붙은 임시 타입입니다.`)
|
|
338
290
|
/* 화면의 칸 이름은 「타입」이다(`figure.label.figure-type`). */
|
|
339
291
|
expect(finding!.how).toContain('타입 칸')
|
|
340
|
-
expect(
|
|
292
|
+
expect(inspectSource(v3Source(), null, translatorOf(KO), 'ROLLER_CONVEYOR').blocked).toBe(false)
|
|
341
293
|
})
|
|
342
294
|
|
|
343
295
|
/* 코드가 묻는 키가 두 언어에 다 있어야 한다 — 없으면 그 언어의 화면에 영어가 섞인다. */
|
|
344
296
|
it('판정이 묻는 키는 ko · en 에 다 있다', () => {
|
|
345
297
|
const asked: string[] = []
|
|
346
298
|
const t = translatorOf(KO, asked)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
299
|
+
inspectSource(outside(), null, t)
|
|
300
|
+
inspectSource(bare(), null, t)
|
|
301
|
+
inspectSource(v3Source(), v3Source(), t)
|
|
302
|
+
inspectSource(v3Source(), null, t, 'FIGURE_ABCDEF12')
|
|
351
303
|
|
|
352
304
|
const keys = [...new Set(asked)]
|
|
353
305
|
expect(keys.length).toBeGreaterThan(5)
|
|
@@ -356,21 +308,13 @@ describe('figure-format — 판을 알아보는 문', () => {
|
|
|
356
308
|
})
|
|
357
309
|
})
|
|
358
310
|
|
|
359
|
-
describe('소비처
|
|
360
|
-
it('
|
|
361
|
-
const payload = consumablePayloadOf(v2Source())
|
|
362
|
-
expect(payload.format).toBe('v2')
|
|
363
|
-
expect((payload.blueprint as { groups?: unknown[] }).groups!.length).toBeGreaterThan(0)
|
|
364
|
-
expect((payload.blueprint as Record<string, unknown>).format).toBeUndefined()
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
it('V3 는 자산을 그대로 받는다 — 그리는 쪽이 커널로 컴파일한다', () => {
|
|
311
|
+
describe('소비처', () => {
|
|
312
|
+
it('자산을 그대로 받는다 — 그리는 쪽이 커널로 컴파일한다', () => {
|
|
368
313
|
const payload = consumablePayloadOf(v3Source())
|
|
369
|
-
expect(payload.
|
|
370
|
-
expect((payload.asset as V3Asset).version).toBe('figure-v3-asset-1')
|
|
314
|
+
expect(payload.asset.version).toBe('figure-v3-asset-1')
|
|
371
315
|
})
|
|
372
316
|
|
|
373
|
-
it('컴파일되지 않는
|
|
317
|
+
it('컴파일되지 않는 자산은 소비처에 넘기지 않고 던진다', () => {
|
|
374
318
|
const broken = JSON.parse(v3Source())
|
|
375
319
|
broken.designInputs = {}
|
|
376
320
|
expect(() => consumablePayloadOf(JSON.stringify(broken))).toThrow()
|