@operato/board 10.20.0 → 10.22.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 +22 -0
- package/dist/src/figure-provider.d.ts +17 -2
- package/dist/src/figure-provider.js +13 -4
- package/dist/src/figure-provider.js.map +1 -1
- package/dist/test/figure-provider.test.js +78 -1
- package/dist/test/figure-provider.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/test/figure-provider.test.ts +88 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.22.0](https://github.com/hatiolab/operato/compare/v10.21.0...v10.22.0) (2026-09-24)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @operato/board
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [10.21.0](https://github.com/hatiolab/operato/compare/v10.20.0...v10.21.0) (2026-09-24)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### :rocket: New Features
|
|
18
|
+
|
|
19
|
+
* **board:** register a V3 figure through its own door ([0ff3c30](https://github.com/hatiolab/operato/commit/0ff3c30f10e0f98b2e9d4c51fd396f1d36b3d07f))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### :bug: Bug Fix
|
|
23
|
+
|
|
24
|
+
* **board:** depend on things-scene 10.1.26 ([c44e314](https://github.com/hatiolab/operato/commit/c44e314c3ec4c409badb668b617b5b66b61da71e))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## [10.20.0](https://github.com/hatiolab/operato/compare/v10.19.1...v10.20.0) (2026-09-21)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @operato/board
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What the server hands over for one released type.
|
|
3
|
+
*
|
|
4
|
+
* A V2 figure sends its compiled blueprint, as it always did. A V3 asset sends the asset under a `format`
|
|
5
|
+
* tag, because the thing that draws it takes the asset and compiles it with the kernel (ADR-0087 decision 5).
|
|
6
|
+
* A blueprint carries no `format`, so the two are told apart by what the payload says about itself rather
|
|
7
|
+
* than by its shape.
|
|
8
|
+
*/
|
|
9
|
+
export type FigurePayload = {
|
|
10
|
+
format: 'v3';
|
|
11
|
+
type: string;
|
|
12
|
+
asset: any;
|
|
13
|
+
} | Record<string, any>;
|
|
14
|
+
/** Asks for one type's payload. Null when no released figure carries that name. */
|
|
15
|
+
export type BlueprintFetch = (type: string) => Promise<FigurePayload | null>;
|
|
16
|
+
/** Registers one released figure with the scene, by whichever door its format uses. */
|
|
17
|
+
export declare function registerFigurePayload(type: string, payload: FigurePayload): void;
|
|
3
18
|
export declare const fetchFigureBlueprint: BlueprintFetch;
|
|
4
19
|
/**
|
|
5
20
|
* Builds the resolver that fills things-scene's seat.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import gql from 'graphql-tag';
|
|
5
|
-
import { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene';
|
|
5
|
+
import { Component, registerFigure, registerFigureV3, registerTypeResolver } from '@hatiolab/things-scene';
|
|
6
6
|
import { client, gqlContext } from '@operato/graphql';
|
|
7
7
|
/*
|
|
8
8
|
* Where a board gets the figures somebody authored.
|
|
@@ -28,6 +28,15 @@ import { client, gqlContext } from '@operato/graphql';
|
|
|
28
28
|
*/
|
|
29
29
|
/** The figure namespace: `EQ_MIXER`, `CONVEYOR`, `VEH_AGV_01`. */
|
|
30
30
|
const FIGURE_TYPE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/;
|
|
31
|
+
const isV3 = (payload) => payload.format === 'v3';
|
|
32
|
+
/** Registers one released figure with the scene, by whichever door its format uses. */
|
|
33
|
+
export function registerFigurePayload(type, payload) {
|
|
34
|
+
var _a;
|
|
35
|
+
if (isV3(payload))
|
|
36
|
+
registerFigureV3((_a = payload.type) !== null && _a !== void 0 ? _a : type, payload.asset);
|
|
37
|
+
else
|
|
38
|
+
registerFigure(payload);
|
|
39
|
+
}
|
|
31
40
|
const FETCH_FIGURE_BLUEPRINT = gql `
|
|
32
41
|
query FetchFigureBlueprint($type: String!) {
|
|
33
42
|
figureBlueprint(type: $type)
|
|
@@ -65,12 +74,12 @@ export function createFigureResolver(fetch = fetchFigureBlueprint) {
|
|
|
65
74
|
const failures = [];
|
|
66
75
|
await Promise.all(asking.map(async (type) => {
|
|
67
76
|
try {
|
|
68
|
-
const
|
|
69
|
-
if (!
|
|
77
|
+
const payload = await fetch(type);
|
|
78
|
+
if (!payload) {
|
|
70
79
|
/* Nobody published this type. It stays a stand-in. */
|
|
71
80
|
return;
|
|
72
81
|
}
|
|
73
|
-
|
|
82
|
+
registerFigurePayload(type, payload);
|
|
74
83
|
}
|
|
75
84
|
catch (error) {
|
|
76
85
|
failures.push({ type, error });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC1G,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAErD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,kEAAkE;AAClE,MAAM,WAAW,GAAG,+BAA+B,CAAA;AAenD,MAAM,IAAI,GAAG,CAAC,OAAsB,EAAyD,EAAE,CAC5F,OAAgC,CAAC,MAAM,KAAK,IAAI,CAAA;AAEnD,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,OAAsB;;IACxE,IAAI,IAAI,CAAC,OAAO,CAAC;QAAE,gBAAgB,CAAC,MAAA,OAAO,CAAC,IAAI,mCAAI,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;;QACnE,cAAc,CAAC,OAAgB,CAAC,CAAA;AACvC,CAAC;AAED,MAAM,sBAAsB,GAAG,GAAG,CAAA;;;;CAIjC,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAmB,KAAK,EAAE,IAAY,EAAE,EAAE;;IACzE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAClC,KAAK,EAAE,sBAAsB;QAC7B,SAAS,EAAE,EAAE,IAAI,EAAE;QACnB,OAAO,EAAE,UAAU,EAAE;KACtB,CAAC,CAAA;IAEF;;;;MAIE;IACF,MAAM,MAAM,GAAI,QAAyD,CAAC,MAAM,CAAA;IAChF,IAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAC,OAAA,MAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,mCAAI,MAAM,CAAC,CAAC,CAAC,CAAA,EAAA,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IACtE,CAAC;IAED,OAAO,MAAA,MAAA,QAAQ,CAAC,IAAI,0CAAE,eAAe,mCAAI,IAAI,CAAA;AAC/C,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAwB,oBAAoB;IAC/E,OAAO,KAAK,UAAU,kBAAkB,CAAC,KAAwB;QAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;QACxF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAuC,EAAE,CAAA;QAEvD,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,GAAG,CAAC,KAAK,EAAC,IAAI,EAAC,EAAE;YACtB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAA;gBACjC,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,sDAAsD;oBACtD,OAAM;gBACR,CAAC;gBAED,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YAChC,CAAC;QACH,CAAC,CAAC,CACH,CAAA;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB;;;cAGE;YACF,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ;iBAC/B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;iBAClG,IAAI,CAAC,IAAI,CAAC,EAAE,CAChB,CAAA;QACH,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAwB,oBAAoB;IAChF,OAAO,oBAAoB,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAA;AAC1D,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, registerFigure, registerFigureV3, registerTypeResolver } from '@hatiolab/things-scene'\nimport { client, gqlContext } from '@operato/graphql'\n\n/*\n * Where a board gets the figures somebody authored.\n *\n * ── Why the seat is filled here ─────────────────────────────────────────────\n * things-scene keeps the seat (`registerTypeResolver`) and knows nothing about figures, GraphQL or\n * who published what. The board package is where a saved model already gets its values from the\n * server — `board-provider.ts` reads a board by id the same way — so this is where the types a\n * model names, and nobody has registered, get fetched. Putting it in the authoring package would\n * make every consumer drag the modeller along, and putting it in each app would make every app\n * build the same thing (ADR-0063 결정 2 · ADR-0045).\n *\n * ── What it asks for, and what it does not ──────────────────────────────────\n * Uppercase names are the figure namespace, so those are the only ones asked about: a model that\n * names `rect` or a mistyped `Conveyor` never reaches the server. A type that is already\n * registered is left alone — a figure never replaces a component that is already drawing.\n *\n * ── An answer of \"nobody published this\" is not a failure ───────────────────\n * `figureBlueprint` returns null for a type that has no released figure, and that type stays\n * unregistered on purpose: the scene draws it as a stand-in and saves it back untouched\n * (ADR-0063 결정 1). Being unable to ask at all is a different fact, so a transport or server\n * error rejects — `resolveUnknownTypes` reports those in `failed` and the board still opens.\n */\n\n/** The figure namespace: `EQ_MIXER`, `CONVEYOR`, `VEH_AGV_01`. */\nconst FIGURE_TYPE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/\n\n/**\n * What the server hands over for one released type.\n *\n * A V2 figure sends its compiled blueprint, as it always did. A V3 asset sends the asset under a `format`\n * tag, because the thing that draws it takes the asset and compiles it with the kernel (ADR-0087 decision 5).\n * A blueprint carries no `format`, so the two are told apart by what the payload says about itself rather\n * than by its shape.\n */\nexport type FigurePayload = { format: 'v3'; type: string; asset: any } | Record<string, any>\n\n/** Asks for one type's payload. Null when no released figure carries that name. */\nexport type BlueprintFetch = (type: string) => Promise<FigurePayload | null>\n\nconst isV3 = (payload: FigurePayload): payload is { format: 'v3'; type: string; asset: any } =>\n (payload as { format?: unknown }).format === 'v3'\n\n/** Registers one released figure with the scene, by whichever door its format uses. */\nexport function registerFigurePayload(type: string, payload: FigurePayload): void {\n if (isV3(payload)) registerFigureV3(payload.type ?? type, payload.asset)\n else registerFigure(payload as never)\n}\n\nconst FETCH_FIGURE_BLUEPRINT = gql`\n query FetchFigureBlueprint($type: String!) {\n figureBlueprint(type: $type)\n }\n`\n\nexport const fetchFigureBlueprint: BlueprintFetch = async (type: string) => {\n const response = await client.query({\n query: FETCH_FIGURE_BLUEPRINT,\n variables: { type },\n context: gqlContext()\n })\n\n /*\n The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response\n carrying `errors` and a null field. Without this the caller cannot tell \"no such figure\" from\n \"the door said no\".\n */\n const errors = (response as { errors?: readonly { message?: string }[] }).errors\n if (errors?.length) {\n throw new Error(errors.map(e => e?.message ?? String(e)).join('\\n'))\n }\n\n return response.data?.figureBlueprint ?? null\n}\n\n/**\n * Builds the resolver that fills things-scene's seat.\n *\n * `fetch` is a seam for tests — the default one talks to the server.\n */\nexport function createFigureResolver(fetch: BlueprintFetch = fetchFigureBlueprint) {\n return async function resolveFigureTypes(types: readonly string[]): Promise<void> {\n const asking = types.filter(type => FIGURE_TYPE.test(type) && !Component.register(type))\n if (asking.length === 0) {\n return\n }\n\n const failures: { type: string; error: unknown }[] = []\n\n await Promise.all(\n asking.map(async type => {\n try {\n const payload = await fetch(type)\n if (!payload) {\n /* Nobody published this type. It stays a stand-in. */\n return\n }\n\n registerFigurePayload(type, payload)\n } catch (error) {\n failures.push({ type, error })\n }\n })\n )\n\n if (failures.length > 0) {\n /*\n What did register stays registered — the seat reads the registry back, not what this says.\n The throw is how \"could not ask\" reaches `TypeResolution.failed`.\n */\n throw new Error(\n `Figure 청사진을 가져오지 못했다:\\n${failures\n .map(({ type, error }) => ` - ${type}: ${error instanceof Error ? error.message : String(error)}`)\n .join('\\n')}`\n )\n }\n }\n}\n\n/**\n * Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`\n * gets published figures on its boards without registering anything itself. Returns the function\n * that takes the seat back.\n */\nexport function installFigureResolver(fetch: BlueprintFetch = fetchFigureBlueprint): () => void {\n return registerTypeResolver(createFigureResolver(fetch))\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect } from '@open-wc/testing';
|
|
2
|
-
import { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene';
|
|
2
|
+
import { Component, hasFigure, hasFigureV3, registerTypeResolver, resetFigures, resetFiguresV3, resolveUnknownTypes } from '@hatiolab/things-scene';
|
|
3
3
|
import { createFigureResolver } from '../src/figure-provider.js';
|
|
4
4
|
/**
|
|
5
5
|
* The path a published figure takes to a board (ADR-0063 결정 2).
|
|
@@ -34,6 +34,53 @@ const blueprintOf = (type) => ({
|
|
|
34
34
|
}
|
|
35
35
|
]
|
|
36
36
|
});
|
|
37
|
+
/**
|
|
38
|
+
* The other thing the door can hand over: a V3 asset under a `format` tag (ADR-0087 decision 5).
|
|
39
|
+
*
|
|
40
|
+
* Written out rather than built with figure-model, for the same reason the blueprint above is: this package
|
|
41
|
+
* does not read either payload, it passes each to the scene's own registration, and a fixture that drifts from
|
|
42
|
+
* the format should fail loudly here rather than draw nothing at runtime. One box standing on the floor, with
|
|
43
|
+
* the volume it takes up declared.
|
|
44
|
+
*/
|
|
45
|
+
const v3AssetOf = (type) => ({
|
|
46
|
+
version: 'figure-v3-asset-1',
|
|
47
|
+
name: type,
|
|
48
|
+
document: {
|
|
49
|
+
version: 'figure-v3-prototype-1',
|
|
50
|
+
model: {
|
|
51
|
+
version: 'r3-spatial-1',
|
|
52
|
+
inputs: [],
|
|
53
|
+
constants: [
|
|
54
|
+
{ id: 'w', unit: 'mm', value: 100 },
|
|
55
|
+
{ id: 'half', unit: 'mm', value: 50 },
|
|
56
|
+
{ id: 'minusHalf', unit: 'mm', value: -50 },
|
|
57
|
+
{ id: 'zero', unit: 'mm', value: 0 },
|
|
58
|
+
{ id: 'a0', unit: 'deg', value: 0 }
|
|
59
|
+
],
|
|
60
|
+
nodes: [
|
|
61
|
+
{ id: 'body.shape', op: 'box-shape@1', args: ['w', 'w', 'w'], outputs: { shape: 'body.s' }, params: { frame: 'body.local' } },
|
|
62
|
+
{
|
|
63
|
+
id: 'body.pose',
|
|
64
|
+
op: 'rigid@1',
|
|
65
|
+
args: ['zero', 'half', 'zero', 'a0', 'a0', 'a0'],
|
|
66
|
+
outputs: { pose: 'body.p' },
|
|
67
|
+
params: { from: 'body.local', to: 'asset' }
|
|
68
|
+
},
|
|
69
|
+
{ id: 'body', op: 'place@1', args: ['body.s', 'body.p'], outputs: { placed: 'body.placed' } }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
capabilities: { version: 'r3-capability-3', assetFrame: 'asset', bindings: [] }
|
|
73
|
+
},
|
|
74
|
+
designInputs: {},
|
|
75
|
+
stateDefaults: {},
|
|
76
|
+
palette: { 'palette.primary': '#477b9e' },
|
|
77
|
+
appearance: [{ target: 'body', material: { token: 'palette.primary' } }],
|
|
78
|
+
occupancy: {
|
|
79
|
+
bounds: { x: { min: 'minusHalf', max: 'half' }, y: { min: 'zero', max: 'w' }, z: { min: 'minusHalf', max: 'half' } },
|
|
80
|
+
placement: 'floor'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const v3PayloadOf = (type) => ({ format: 'v3', type, asset: v3AssetOf(type) });
|
|
37
84
|
/** Records what was asked for, and answers from a table. */
|
|
38
85
|
function fetcherOf(published, failing = []) {
|
|
39
86
|
const asked = [];
|
|
@@ -52,6 +99,7 @@ function fetcherOf(published, failing = []) {
|
|
|
52
99
|
describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {
|
|
53
100
|
afterEach(() => {
|
|
54
101
|
resetFigures();
|
|
102
|
+
resetFiguresV3();
|
|
55
103
|
});
|
|
56
104
|
it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {
|
|
57
105
|
const { asked, fetch } = fetcherOf({ EQ_MIXER: blueprintOf('EQ_MIXER') });
|
|
@@ -74,6 +122,35 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
|
|
|
74
122
|
/* The component type is registered with it — the model writes `type` and nothing else. */
|
|
75
123
|
expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined);
|
|
76
124
|
});
|
|
125
|
+
it('V3 자산은 V3 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {
|
|
126
|
+
const { fetch } = fetcherOf({ EQ_V3: v3PayloadOf('EQ_V3') });
|
|
127
|
+
await createFigureResolver(fetch)(['EQ_V3']);
|
|
128
|
+
expect(hasFigureV3('EQ_V3')).to.equal(true);
|
|
129
|
+
expect(hasFigure('EQ_V3')).to.equal(false);
|
|
130
|
+
/* 도면은 판을 모른다 — 두 판 다 type 하나만 적으면 그려진다. */
|
|
131
|
+
expect(Component.register('EQ_V3')).to.not.equal(undefined);
|
|
132
|
+
});
|
|
133
|
+
it('한 도면에 두 판이 섞여 있어도 각자의 문으로 들어간다', async () => {
|
|
134
|
+
const { fetch } = fetcherOf({ EQ_OLD: blueprintOf('EQ_OLD'), EQ_NEW: v3PayloadOf('EQ_NEW') });
|
|
135
|
+
await createFigureResolver(fetch)(['EQ_OLD', 'EQ_NEW']);
|
|
136
|
+
expect(hasFigure('EQ_OLD')).to.equal(true);
|
|
137
|
+
expect(hasFigureV3('EQ_NEW')).to.equal(true);
|
|
138
|
+
});
|
|
139
|
+
it('읽을 수 없는 V3 자산은 그 type 만 실패로 남는다', async () => {
|
|
140
|
+
const broken = v3PayloadOf('EQ_V3_BROKEN');
|
|
141
|
+
broken.asset.appearance = [];
|
|
142
|
+
const { fetch } = fetcherOf({ EQ_BESIDE_V3: blueprintOf('EQ_BESIDE_V3'), EQ_V3_BROKEN: broken });
|
|
143
|
+
let message = '';
|
|
144
|
+
try {
|
|
145
|
+
await createFigureResolver(fetch)(['EQ_BESIDE_V3', 'EQ_V3_BROKEN']);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
}
|
|
150
|
+
expect(message).to.contain('EQ_V3_BROKEN');
|
|
151
|
+
expect(hasFigure('EQ_BESIDE_V3')).to.equal(true);
|
|
152
|
+
expect(hasFigureV3('EQ_V3_BROKEN')).to.equal(false);
|
|
153
|
+
});
|
|
77
154
|
it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {
|
|
78
155
|
const { fetch } = fetcherOf({});
|
|
79
156
|
let rejected = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-provider.test.js","sourceRoot":"","sources":["../../test/figure-provider.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'\n\nimport { createFigureResolver } from '../src/figure-provider.js'\n\n/**\n * The path a published figure takes to a board (ADR-0063 결정 2).\n *\n * things-scene holds the seat and knows nothing about figures; this package fills it. What is\n * checked here is the part that belongs to the board: which names are asked about, what happens to\n * the answer, and how \"nobody published this\" stays apart from \"could not ask\".\n *\n * The blueprint below is the shape figure-model emits — `registerFigure` rejects anything it cannot\n * read, so a fixture that drifts fails loudly here instead of drawing nothing at runtime.\n */\n\nconst BLUEPRINT_VERSION = 1\n\nconst blueprintOf = (type: string) => ({\n version: BLUEPRINT_VERSION,\n type,\n base: { x: 100, y: 100, z: 100 },\n groups: [\n {\n materialKey: 'palette.dark|metal|f|-|-|-',\n material: { token: 'palette.dark', preset: 'metal', flatShading: true },\n animated: false,\n members: [\n {\n name: 'body',\n primitive: 'cube',\n geometryKey: 'cube|100x100x100',\n transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 100, y: 100, z: 100 } },\n sizing: 'scale',\n triangles: 12\n }\n ]\n }\n ]\n})\n\n/** Records what was asked for, and answers from a table. */\nfunction fetcherOf(published: Record<string, unknown>, failing: string[] = []) {\n const asked: string[] = []\n\n return {\n asked,\n fetch: async (type: string) => {\n asked.push(type)\n if (failing.includes(type)) {\n throw new Error(`no route to the door (${type})`)\n }\n return published[type] ?? null\n }\n }\n}\n\ndescribe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {\n afterEach(() => {\n resetFigures()\n })\n\n it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_MIXER: blueprintOf('EQ_MIXER') })\n\n await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])\n\n expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01'])\n })\n\n it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_TAKEN: blueprintOf('EQ_TAKEN') })\n\n /* Somebody else already draws this name — a plugin, an app's own component. */\n Component.register('EQ_TAKEN', class Taken extends Component {} as never)\n\n await createFigureResolver(fetch)(['EQ_TAKEN'])\n\n expect(asked).to.deep.equal([])\n expect(hasFigure('EQ_TAKEN')).to.equal(false)\n })\n\n it('받은 청사진을 등록한다 — 도면은 type 만 적으면 된다', async () => {\n const { fetch } = fetcherOf({ EQ_REGISTERED: blueprintOf('EQ_REGISTERED') })\n\n await createFigureResolver(fetch)(['EQ_REGISTERED'])\n\n expect(hasFigure('EQ_REGISTERED')).to.equal(true)\n /* The component type is registered with it — the model writes `type` and nothing else. */\n expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined)\n })\n\n it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {\n const { fetch } = fetcherOf({})\n\n let rejected = false\n try {\n await createFigureResolver(fetch)(['EQ_NOT_PUBLISHED'])\n } catch {\n rejected = true\n }\n\n expect(rejected).to.equal(false)\n expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false)\n })\n\n it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {\n const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: blueprintOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN'])\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_BROKEN')\n expect(message).to.not.contain('EQ_BESIDE_BROKEN')\n expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true)\n })\n\n it('읽을 수 없는 청사진은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {\n const { fetch } = fetcherOf({\n EQ_BESIDE_MALFORMED: blueprintOf('EQ_BESIDE_MALFORMED'),\n EQ_MALFORMED: { version: BLUEPRINT_VERSION, type: 'EQ_MALFORMED' }\n })\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE_MALFORMED', 'EQ_MALFORMED'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_MALFORMED')\n expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true)\n expect(hasFigure('EQ_MALFORMED')).to.equal(false)\n })\n\n it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {\n const { fetch } = fetcherOf({ EQ_ON_A_BOARD: blueprintOf('EQ_ON_A_BOARD') })\n const takeBack = registerTypeResolver(createFigureResolver(fetch))\n\n try {\n const model = {\n type: 'container',\n components: [\n { type: 'EQ_ON_A_BOARD', left: 0, top: 0, width: 100, height: 100 },\n { type: 'EQ_NEVER_PUBLISHED', left: 0, top: 0, width: 100, height: 100 }\n ]\n }\n\n const resolution = await resolveUnknownTypes(model)\n\n expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED'])\n expect(resolution.failed).to.deep.equal([])\n expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"figure-provider.test.js","sourceRoot":"","sources":["../../test/figure-provider.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { Component, hasFigure, hasFigureV3, registerTypeResolver, resetFigures, resetFiguresV3, resolveUnknownTypes } from '@hatiolab/things-scene'\n\nimport { createFigureResolver } from '../src/figure-provider.js'\n\n/**\n * The path a published figure takes to a board (ADR-0063 결정 2).\n *\n * things-scene holds the seat and knows nothing about figures; this package fills it. What is\n * checked here is the part that belongs to the board: which names are asked about, what happens to\n * the answer, and how \"nobody published this\" stays apart from \"could not ask\".\n *\n * The blueprint below is the shape figure-model emits — `registerFigure` rejects anything it cannot\n * read, so a fixture that drifts fails loudly here instead of drawing nothing at runtime.\n */\n\nconst BLUEPRINT_VERSION = 1\n\nconst blueprintOf = (type: string) => ({\n version: BLUEPRINT_VERSION,\n type,\n base: { x: 100, y: 100, z: 100 },\n groups: [\n {\n materialKey: 'palette.dark|metal|f|-|-|-',\n material: { token: 'palette.dark', preset: 'metal', flatShading: true },\n animated: false,\n members: [\n {\n name: 'body',\n primitive: 'cube',\n geometryKey: 'cube|100x100x100',\n transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 100, y: 100, z: 100 } },\n sizing: 'scale',\n triangles: 12\n }\n ]\n }\n ]\n})\n\n/**\n * The other thing the door can hand over: a V3 asset under a `format` tag (ADR-0087 decision 5).\n *\n * Written out rather than built with figure-model, for the same reason the blueprint above is: this package\n * does not read either payload, it passes each to the scene's own registration, and a fixture that drifts from\n * the format should fail loudly here rather than draw nothing at runtime. One box standing on the floor, with\n * the volume it takes up declared.\n */\nconst v3AssetOf = (type: string) => ({\n version: 'figure-v3-asset-1',\n name: type,\n document: {\n version: 'figure-v3-prototype-1',\n model: {\n version: 'r3-spatial-1',\n inputs: [],\n constants: [\n { id: 'w', unit: 'mm', value: 100 },\n { id: 'half', unit: 'mm', value: 50 },\n { id: 'minusHalf', unit: 'mm', value: -50 },\n { id: 'zero', unit: 'mm', value: 0 },\n { id: 'a0', unit: 'deg', value: 0 }\n ],\n nodes: [\n { id: 'body.shape', op: 'box-shape@1', args: ['w', 'w', 'w'], outputs: { shape: 'body.s' }, params: { frame: 'body.local' } },\n {\n id: 'body.pose',\n op: 'rigid@1',\n args: ['zero', 'half', 'zero', 'a0', 'a0', 'a0'],\n outputs: { pose: 'body.p' },\n params: { from: 'body.local', to: 'asset' }\n },\n { id: 'body', op: 'place@1', args: ['body.s', 'body.p'], outputs: { placed: 'body.placed' } }\n ]\n },\n capabilities: { version: 'r3-capability-3', assetFrame: 'asset', bindings: [] }\n },\n designInputs: {},\n stateDefaults: {},\n palette: { 'palette.primary': '#477b9e' },\n appearance: [{ target: 'body', material: { token: 'palette.primary' } }],\n occupancy: {\n bounds: { x: { min: 'minusHalf', max: 'half' }, y: { min: 'zero', max: 'w' }, z: { min: 'minusHalf', max: 'half' } },\n placement: 'floor'\n }\n})\n\nconst v3PayloadOf = (type: string) => ({ format: 'v3' as const, type, asset: v3AssetOf(type) })\n\n/** Records what was asked for, and answers from a table. */\nfunction fetcherOf(published: Record<string, unknown>, failing: string[] = []) {\n const asked: string[] = []\n\n return {\n asked,\n fetch: async (type: string) => {\n asked.push(type)\n if (failing.includes(type)) {\n throw new Error(`no route to the door (${type})`)\n }\n return published[type] ?? null\n }\n }\n}\n\ndescribe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {\n afterEach(() => {\n resetFigures()\n resetFiguresV3()\n })\n\n it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_MIXER: blueprintOf('EQ_MIXER') })\n\n await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])\n\n expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01'])\n })\n\n it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_TAKEN: blueprintOf('EQ_TAKEN') })\n\n /* Somebody else already draws this name — a plugin, an app's own component. */\n Component.register('EQ_TAKEN', class Taken extends Component {} as never)\n\n await createFigureResolver(fetch)(['EQ_TAKEN'])\n\n expect(asked).to.deep.equal([])\n expect(hasFigure('EQ_TAKEN')).to.equal(false)\n })\n\n it('받은 청사진을 등록한다 — 도면은 type 만 적으면 된다', async () => {\n const { fetch } = fetcherOf({ EQ_REGISTERED: blueprintOf('EQ_REGISTERED') })\n\n await createFigureResolver(fetch)(['EQ_REGISTERED'])\n\n expect(hasFigure('EQ_REGISTERED')).to.equal(true)\n /* The component type is registered with it — the model writes `type` and nothing else. */\n expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined)\n })\n\n it('V3 자산은 V3 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {\n const { fetch } = fetcherOf({ EQ_V3: v3PayloadOf('EQ_V3') })\n\n await createFigureResolver(fetch)(['EQ_V3'])\n\n expect(hasFigureV3('EQ_V3')).to.equal(true)\n expect(hasFigure('EQ_V3')).to.equal(false)\n /* 도면은 판을 모른다 — 두 판 다 type 하나만 적으면 그려진다. */\n expect(Component.register('EQ_V3')).to.not.equal(undefined)\n })\n\n it('한 도면에 두 판이 섞여 있어도 각자의 문으로 들어간다', async () => {\n const { fetch } = fetcherOf({ EQ_OLD: blueprintOf('EQ_OLD'), EQ_NEW: v3PayloadOf('EQ_NEW') })\n\n await createFigureResolver(fetch)(['EQ_OLD', 'EQ_NEW'])\n\n expect(hasFigure('EQ_OLD')).to.equal(true)\n expect(hasFigureV3('EQ_NEW')).to.equal(true)\n })\n\n it('읽을 수 없는 V3 자산은 그 type 만 실패로 남는다', async () => {\n const broken = v3PayloadOf('EQ_V3_BROKEN')\n broken.asset.appearance = []\n const { fetch } = fetcherOf({ EQ_BESIDE_V3: blueprintOf('EQ_BESIDE_V3'), EQ_V3_BROKEN: broken })\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE_V3', 'EQ_V3_BROKEN'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_V3_BROKEN')\n expect(hasFigure('EQ_BESIDE_V3')).to.equal(true)\n expect(hasFigureV3('EQ_V3_BROKEN')).to.equal(false)\n })\n\n it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {\n const { fetch } = fetcherOf({})\n\n let rejected = false\n try {\n await createFigureResolver(fetch)(['EQ_NOT_PUBLISHED'])\n } catch {\n rejected = true\n }\n\n expect(rejected).to.equal(false)\n expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false)\n })\n\n it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {\n const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: blueprintOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN'])\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_BROKEN')\n expect(message).to.not.contain('EQ_BESIDE_BROKEN')\n expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true)\n })\n\n it('읽을 수 없는 청사진은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {\n const { fetch } = fetcherOf({\n EQ_BESIDE_MALFORMED: blueprintOf('EQ_BESIDE_MALFORMED'),\n EQ_MALFORMED: { version: BLUEPRINT_VERSION, type: 'EQ_MALFORMED' }\n })\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE_MALFORMED', 'EQ_MALFORMED'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_MALFORMED')\n expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true)\n expect(hasFigure('EQ_MALFORMED')).to.equal(false)\n })\n\n it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {\n const { fetch } = fetcherOf({ EQ_ON_A_BOARD: blueprintOf('EQ_ON_A_BOARD') })\n const takeBack = registerTypeResolver(createFigureResolver(fetch))\n\n try {\n const model = {\n type: 'container',\n components: [\n { type: 'EQ_ON_A_BOARD', left: 0, top: 0, width: 100, height: 100 },\n { type: 'EQ_NEVER_PUBLISHED', left: 0, top: 0, width: 100, height: 100 }\n ]\n }\n\n const resolution = await resolveUnknownTypes(model)\n\n expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED'])\n expect(resolution.failed).to.deep.equal([])\n expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}
|