@operato/board 10.17.0 → 10.19.0
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/CHANGELOG.md +14 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +191 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +3383 -0
- package/dist/src/board-provider.js +9 -3
- package/dist/src/board-provider.js.map +1 -1
- package/dist/src/figure-provider.d.ts +15 -0
- package/dist/src/figure-provider.js +98 -0
- package/dist/src/figure-provider.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.d.ts +1 -1
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js +10 -4
- package/dist/src/modeller/scene-viewer/ox-scene-viewer.js.map +1 -1
- package/dist/src/ox-board-preview.js +13 -6
- package/dist/src/ox-board-preview.js.map +1 -1
- package/dist/src/ox-board-template-viewer.d.ts +1 -1
- package/dist/src/ox-board-template-viewer.js +12 -6
- package/dist/src/ox-board-template-viewer.js.map +1 -1
- package/dist/src/ox-board-viewer.d.ts +1 -1
- package/dist/src/ox-board-viewer.js +13 -7
- package/dist/src/ox-board-viewer.js.map +1 -1
- package/dist/src/player/ox-board-wrapper.js +12 -6
- package/dist/src/player/ox-board-wrapper.js.map +1 -1
- package/dist/src/resolved-scene.d.ts +10 -0
- package/dist/src/resolved-scene.js +22 -0
- package/dist/src/resolved-scene.js.map +1 -0
- package/dist/test/figure-provider.test.d.ts +1 -0
- package/dist/test/figure-provider.test.js +139 -0
- package/dist/test/figure-provider.test.js.map +1 -0
- package/dist/test/resolved-scene.test.d.ts +1 -0
- package/dist/test/resolved-scene.test.js +88 -0
- package/dist/test/resolved-scene.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/figure-provider.test.ts +163 -0
- package/test/resolved-scene.test.ts +114 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/board",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.19.0",
|
|
4
4
|
"description": "Webcomponent for board following open-wc recommendations",
|
|
5
5
|
"author": "heartyoh",
|
|
6
6
|
"type": "module",
|
|
@@ -120,17 +120,17 @@
|
|
|
120
120
|
"dependencies": {
|
|
121
121
|
"@material/web": "^2.0.0",
|
|
122
122
|
"@open-wc/scoped-elements": "^2.1.3",
|
|
123
|
-
"@operato/app": "^10.
|
|
124
|
-
"@operato/data-grist": "^10.
|
|
125
|
-
"@operato/font": "^10.
|
|
123
|
+
"@operato/app": "^10.19.0",
|
|
124
|
+
"@operato/data-grist": "^10.19.0",
|
|
125
|
+
"@operato/font": "^10.19.0",
|
|
126
126
|
"@operato/graphql": "^10.16.2",
|
|
127
127
|
"@operato/i18n": "^10.16.2",
|
|
128
128
|
"@operato/input": "^10.16.2",
|
|
129
129
|
"@operato/layout": "^10.16.2",
|
|
130
130
|
"@operato/markdown": "^10.16.2",
|
|
131
131
|
"@operato/popup": "^10.16.2",
|
|
132
|
-
"@operato/property-editor": "^10.
|
|
133
|
-
"@operato/property-panel": "^10.
|
|
132
|
+
"@operato/property-editor": "^10.19.0",
|
|
133
|
+
"@operato/property-panel": "^10.19.0",
|
|
134
134
|
"@operato/styles": "^10.16.2",
|
|
135
135
|
"@operato/utils": "^10.16.2",
|
|
136
136
|
"@types/file-saver": "^2.0.4",
|
|
@@ -180,5 +180,5 @@
|
|
|
180
180
|
"prettier --write"
|
|
181
181
|
]
|
|
182
182
|
},
|
|
183
|
-
"gitHead": "
|
|
183
|
+
"gitHead": "58bdb75a1b1f0c28936d44829e1cf7199de3c617"
|
|
184
184
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing'
|
|
2
|
+
|
|
3
|
+
import { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'
|
|
4
|
+
|
|
5
|
+
import { createFigureResolver } from '../src/figure-provider.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The path a published figure takes to a board (ADR-0063 결정 2).
|
|
9
|
+
*
|
|
10
|
+
* things-scene holds the seat and knows nothing about figures; this package fills it. What is
|
|
11
|
+
* checked here is the part that belongs to the board: which names are asked about, what happens to
|
|
12
|
+
* the answer, and how "nobody published this" stays apart from "could not ask".
|
|
13
|
+
*
|
|
14
|
+
* The blueprint below is the shape figure-model emits — `registerFigure` rejects anything it cannot
|
|
15
|
+
* read, so a fixture that drifts fails loudly here instead of drawing nothing at runtime.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const BLUEPRINT_VERSION = 1
|
|
19
|
+
|
|
20
|
+
const blueprintOf = (type: string) => ({
|
|
21
|
+
version: BLUEPRINT_VERSION,
|
|
22
|
+
type,
|
|
23
|
+
base: { x: 100, y: 100, z: 100 },
|
|
24
|
+
groups: [
|
|
25
|
+
{
|
|
26
|
+
materialKey: 'palette.dark|metal|f|-|-|-',
|
|
27
|
+
material: { token: 'palette.dark', preset: 'metal', flatShading: true },
|
|
28
|
+
animated: false,
|
|
29
|
+
members: [
|
|
30
|
+
{
|
|
31
|
+
name: 'body',
|
|
32
|
+
primitive: 'cube',
|
|
33
|
+
geometryKey: 'cube|100x100x100',
|
|
34
|
+
transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 100, y: 100, z: 100 } },
|
|
35
|
+
sizing: 'scale',
|
|
36
|
+
triangles: 12
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** Records what was asked for, and answers from a table. */
|
|
44
|
+
function fetcherOf(published: Record<string, unknown>, failing: string[] = []) {
|
|
45
|
+
const asked: string[] = []
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
asked,
|
|
49
|
+
fetch: async (type: string) => {
|
|
50
|
+
asked.push(type)
|
|
51
|
+
if (failing.includes(type)) {
|
|
52
|
+
throw new Error(`no route to the door (${type})`)
|
|
53
|
+
}
|
|
54
|
+
return published[type] ?? null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
resetFigures()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {
|
|
65
|
+
const { asked, fetch } = fetcherOf({ EQ_MIXER: blueprintOf('EQ_MIXER') })
|
|
66
|
+
|
|
67
|
+
await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])
|
|
68
|
+
|
|
69
|
+
expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01'])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {
|
|
73
|
+
const { asked, fetch } = fetcherOf({ EQ_TAKEN: blueprintOf('EQ_TAKEN') })
|
|
74
|
+
|
|
75
|
+
/* Somebody else already draws this name — a plugin, an app's own component. */
|
|
76
|
+
Component.register('EQ_TAKEN', class Taken extends Component {} as never)
|
|
77
|
+
|
|
78
|
+
await createFigureResolver(fetch)(['EQ_TAKEN'])
|
|
79
|
+
|
|
80
|
+
expect(asked).to.deep.equal([])
|
|
81
|
+
expect(hasFigure('EQ_TAKEN')).to.equal(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('받은 청사진을 등록한다 — 도면은 type 만 적으면 된다', async () => {
|
|
85
|
+
const { fetch } = fetcherOf({ EQ_REGISTERED: blueprintOf('EQ_REGISTERED') })
|
|
86
|
+
|
|
87
|
+
await createFigureResolver(fetch)(['EQ_REGISTERED'])
|
|
88
|
+
|
|
89
|
+
expect(hasFigure('EQ_REGISTERED')).to.equal(true)
|
|
90
|
+
/* The component type is registered with it — the model writes `type` and nothing else. */
|
|
91
|
+
expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {
|
|
95
|
+
const { fetch } = fetcherOf({})
|
|
96
|
+
|
|
97
|
+
let rejected = false
|
|
98
|
+
try {
|
|
99
|
+
await createFigureResolver(fetch)(['EQ_NOT_PUBLISHED'])
|
|
100
|
+
} catch {
|
|
101
|
+
rejected = true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
expect(rejected).to.equal(false)
|
|
105
|
+
expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {
|
|
109
|
+
const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: blueprintOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN'])
|
|
110
|
+
|
|
111
|
+
let message = ''
|
|
112
|
+
try {
|
|
113
|
+
await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN'])
|
|
114
|
+
} catch (error) {
|
|
115
|
+
message = error instanceof Error ? error.message : String(error)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
expect(message).to.contain('EQ_BROKEN')
|
|
119
|
+
expect(message).to.not.contain('EQ_BESIDE_BROKEN')
|
|
120
|
+
expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('읽을 수 없는 청사진은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {
|
|
124
|
+
const { fetch } = fetcherOf({
|
|
125
|
+
EQ_BESIDE_MALFORMED: blueprintOf('EQ_BESIDE_MALFORMED'),
|
|
126
|
+
EQ_MALFORMED: { version: BLUEPRINT_VERSION, type: 'EQ_MALFORMED' }
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
let message = ''
|
|
130
|
+
try {
|
|
131
|
+
await createFigureResolver(fetch)(['EQ_BESIDE_MALFORMED', 'EQ_MALFORMED'])
|
|
132
|
+
} catch (error) {
|
|
133
|
+
message = error instanceof Error ? error.message : String(error)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
expect(message).to.contain('EQ_MALFORMED')
|
|
137
|
+
expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true)
|
|
138
|
+
expect(hasFigure('EQ_MALFORMED')).to.equal(false)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {
|
|
142
|
+
const { fetch } = fetcherOf({ EQ_ON_A_BOARD: blueprintOf('EQ_ON_A_BOARD') })
|
|
143
|
+
const takeBack = registerTypeResolver(createFigureResolver(fetch))
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const model = {
|
|
147
|
+
type: 'container',
|
|
148
|
+
components: [
|
|
149
|
+
{ type: 'EQ_ON_A_BOARD', left: 0, top: 0, width: 100, height: 100 },
|
|
150
|
+
{ type: 'EQ_NEVER_PUBLISHED', left: 0, top: 0, width: 100, height: 100 }
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const resolution = await resolveUnknownTypes(model)
|
|
155
|
+
|
|
156
|
+
expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED'])
|
|
157
|
+
expect(resolution.failed).to.deep.equal([])
|
|
158
|
+
expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true)
|
|
159
|
+
} finally {
|
|
160
|
+
takeBack()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect, fixture, html } from '@open-wc/testing'
|
|
2
|
+
import type { LitElement } from 'lit'
|
|
3
|
+
import { executeServerCommand } from '@web/test-runner-commands'
|
|
4
|
+
|
|
5
|
+
import { registerTypeResolver, resetFigures } from '@hatiolab/things-scene'
|
|
6
|
+
|
|
7
|
+
import { createResolvedScene } from '../src/resolved-scene.js'
|
|
8
|
+
import '../src/ox-board-preview.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The one door a model goes through to become a scene (ADR-0063 결정 2).
|
|
12
|
+
*
|
|
13
|
+
* Three things are counted here, and each of them is something that goes wrong quietly otherwise:
|
|
14
|
+
* a scene built somewhere else never resolves its types, a board of ordinary components pays for a
|
|
15
|
+
* server round trip it does not need, and a scene that was waiting lands on a viewer that has
|
|
16
|
+
* already moved on.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const KNOWN_MODEL = {
|
|
20
|
+
type: 'container',
|
|
21
|
+
components: [{ type: 'rect', left: 0, top: 0, width: 100, height: 100 }]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const modelWith = (types: string[]) => ({
|
|
25
|
+
type: 'container',
|
|
26
|
+
components: types.map((type, index) => ({ type, left: index * 10, top: 0, width: 100, height: 100 }))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('씬을 만드는 문은 하나다', () => {
|
|
30
|
+
it('things-scene 의 create 를 읽는 파일은 resolved-scene.ts 하나뿐이다', async () => {
|
|
31
|
+
/*
|
|
32
|
+
* 세는 범위는 이 꾸러미와 `@operato/process` 다 — 문은 여기 있고 그 꾸러미가 둘째 소비자다.
|
|
33
|
+
* 이 꾸러미만 보던 규칙은 옆에 씬 둘이 서 있는 동안 「문은 하나」라고 답하고 있었다.
|
|
34
|
+
*/
|
|
35
|
+
const files = await executeServerCommand<unknown, string[]>('files-importing-scene-create')
|
|
36
|
+
|
|
37
|
+
expect(files).to.deep.equal(['src/resolved-scene.ts'])
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('모르는 type 이 없으면 아무에게도 묻지 않는다', () => {
|
|
42
|
+
let asked: string[][]
|
|
43
|
+
let takeBack: () => void
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
asked = []
|
|
47
|
+
takeBack = registerTypeResolver(async types => {
|
|
48
|
+
asked.push([...types])
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
takeBack()
|
|
54
|
+
resetFigures()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('씬의 기본 type 만 있는 보드는 공급자를 부르지 않는다', async () => {
|
|
58
|
+
const scene = await createResolvedScene({ model: KNOWN_MODEL })
|
|
59
|
+
|
|
60
|
+
expect(asked).to.deep.equal([])
|
|
61
|
+
expect(scene).to.not.equal(null)
|
|
62
|
+
|
|
63
|
+
scene?.release?.()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('모르는 type 이 있을 때만 부른다', async () => {
|
|
67
|
+
const scene = await createResolvedScene({ model: modelWith(['rect', 'EQ_ASKED_ABOUT']) })
|
|
68
|
+
|
|
69
|
+
expect(asked).to.deep.equal([['EQ_ASKED_ABOUT']])
|
|
70
|
+
|
|
71
|
+
scene?.release?.()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('기다리는 동안 화면이 바뀌면 늦게 온 씬은 들어서지 않는다', () => {
|
|
76
|
+
let takeBack: () => void
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
takeBack?.()
|
|
80
|
+
resetFigures()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('앞의 모델을 풀고 돌아왔을 때 미리보기가 다른 모델을 들고 있으면 그 씬은 버린다', async () => {
|
|
84
|
+
/* The first model's type resolves slowly; the second one has nothing to resolve. */
|
|
85
|
+
let releaseFirst: () => void = () => {}
|
|
86
|
+
const firstResolved = new Promise<void>(resolve => {
|
|
87
|
+
releaseFirst = resolve
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
takeBack = registerTypeResolver(async types => {
|
|
91
|
+
if (types.includes('EQ_SLOW')) {
|
|
92
|
+
await firstResolved
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const element = (await fixture(html`<ox-board-preview></ox-board-preview>`)) as LitElement & {
|
|
97
|
+
boardModel: unknown
|
|
98
|
+
_scene: { root: { components: unknown[] } } | null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
element.boardModel = modelWith(['EQ_SLOW'])
|
|
102
|
+
await element.updateComplete
|
|
103
|
+
|
|
104
|
+
/* The second model arrives while the first one is still waiting on its type. */
|
|
105
|
+
element.boardModel = modelWith(['rect', 'rect'])
|
|
106
|
+
await element.updateComplete
|
|
107
|
+
|
|
108
|
+
releaseFirst()
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
110
|
+
|
|
111
|
+
expect(element._scene).to.not.equal(null)
|
|
112
|
+
expect(element._scene!.root.components.length).to.equal(2)
|
|
113
|
+
})
|
|
114
|
+
})
|