@operato/board 10.28.0 → 10.28.2

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 CHANGED
@@ -3,6 +3,23 @@
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.28.2](https://github.com/hatiolab/operato/compare/v10.28.1...v10.28.2) (2026-09-25)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * **board:** published figures are V3 assets only ([8fe502d](https://github.com/hatiolab/operato/commit/8fe502da6b8b8a0c45ece38c347bbd80a3f29f57))
12
+
13
+
14
+
15
+ ### [10.28.1](https://github.com/hatiolab/operato/compare/v10.28.0...v10.28.1) (2026-09-24)
16
+
17
+ **Note:** Version bump only for package @operato/board
18
+
19
+
20
+
21
+
22
+
6
23
  ## [10.28.0](https://github.com/hatiolab/operato/compare/v10.27.1...v10.28.0) (2026-09-24)
7
24
 
8
25
  **Note:** Version bump only for package @operato/board
@@ -1,19 +1,16 @@
1
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.
2
+ * What the server hands over for one released type: the figure asset under a `format` tag. The board does not read
3
+ * the asset; the scene's own registration compiles it with the kernel (ADR-0087 decision 5). V1 and V2 are gone
4
+ * (2026-09-26), so a payload that does not say `format: 'v3'` is not a figure this board can draw.
8
5
  */
9
6
  export type FigurePayload = {
10
7
  format: 'v3';
11
8
  type: string;
12
9
  asset: any;
13
- } | Record<string, any>;
10
+ };
14
11
  /** Asks for one type's payload. Null when no released figure carries that name. */
15
12
  export type BlueprintFetch = (type: string) => Promise<FigurePayload | null>;
16
- /** Registers one released figure with the scene, by whichever door its format uses. */
13
+ /** Registers one released figure with the scene. Anything but a figure asset is refused by name. */
17
14
  export declare function registerFigurePayload(type: string, payload: FigurePayload): void;
18
15
  export declare const fetchFigureBlueprint: BlueprintFetch;
19
16
  /**
@@ -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, registerFigureV3, registerTypeResolver } from '@hatiolab/things-scene';
5
+ import { Component, 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,14 +28,12 @@ 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. */
31
+ /** Registers one released figure with the scene. Anything but a figure asset is refused by name. */
33
32
  export function registerFigurePayload(type, payload) {
34
33
  var _a;
35
- if (isV3(payload))
36
- registerFigureV3((_a = payload.type) !== null && _a !== void 0 ? _a : type, payload.asset);
37
- else
38
- registerFigure(payload);
34
+ if ((payload === null || payload === void 0 ? void 0 : payload.format) !== 'v3')
35
+ throw new Error(`not a figure asset: ${type}`);
36
+ registerFigureV3((_a = payload.type) !== null && _a !== void 0 ? _a : type, payload.asset);
39
37
  }
40
38
  const FETCH_FIGURE_BLUEPRINT = gql `
41
39
  query FetchFigureBlueprint($type: String!) {
@@ -90,7 +88,7 @@ export function createFigureResolver(fetch = fetchFigureBlueprint) {
90
88
  What did register stays registered — the seat reads the registry back, not what this says.
91
89
  The throw is how "could not ask" reaches `TypeResolution.failed`.
92
90
  */
93
- throw new Error(`Figure 청사진을 가져오지 못했다:\n${failures
91
+ throw new Error(`Figure 를 가져오지 못했다:\n${failures
94
92
  .map(({ type, error }) => ` - ${type}: ${error instanceof Error ? error.message : String(error)}`)
95
93
  .join('\n')}`);
96
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"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","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
+ {"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, 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: the figure asset under a `format` tag. The board does not read\n * the asset; the scene's own registration compiles it with the kernel (ADR-0087 decision 5). V1 and V2 are gone\n * (2026-09-26), so a payload that does not say `format: 'v3'` is not a figure this board can draw.\n */\nexport type FigurePayload = { format: 'v3'; type: string; asset: 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\n/** Registers one released figure with the scene. Anything but a figure asset is refused by name. */\nexport function registerFigurePayload(type: string, payload: FigurePayload): void {\n if ((payload as { format?: unknown })?.format !== 'v3') throw new Error(`not a figure asset: ${type}`)\n registerFigureV3(payload.type ?? type, payload.asset)\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, hasFigureV3, registerTypeResolver, resetFigures, resetFiguresV3, resolveUnknownTypes } from '@hatiolab/things-scene';
2
+ import { Component, hasFigureV3, registerTypeResolver, 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).
@@ -8,40 +8,11 @@ import { createFigureResolver } from '../src/figure-provider.js';
8
8
  * checked here is the part that belongs to the board: which names are asked about, what happens to
9
9
  * the answer, and how "nobody published this" stays apart from "could not ask".
10
10
  *
11
- * The blueprint below is the shape figure-model emits — `registerFigure` rejects anything it cannot
12
- * read, so a fixture that drifts fails loudly here instead of drawing nothing at runtime.
13
- */
14
- const BLUEPRINT_VERSION = 1;
15
- const blueprintOf = (type) => ({
16
- version: BLUEPRINT_VERSION,
17
- type,
18
- base: { x: 100, y: 100, z: 100 },
19
- groups: [
20
- {
21
- materialKey: 'palette.dark|metal|f|-|-|-',
22
- material: { token: 'palette.dark', preset: 'metal', flatShading: true },
23
- animated: false,
24
- members: [
25
- {
26
- name: 'body',
27
- primitive: 'cube',
28
- geometryKey: 'cube|100x100x100',
29
- transform: { position: { x: 0, y: 0, z: 0 }, size: { x: 100, y: 100, z: 100 } },
30
- sizing: 'scale',
31
- triangles: 12
32
- }
33
- ]
34
- }
35
- ]
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.
11
+ * The payload below is written out rather than built with figure-model: this package does not read it, it passes it
12
+ * to the scene's own registration, and a fixture that drifts from the format should fail loudly here rather than
13
+ * draw nothing at runtime.
44
14
  */
15
+ /** What the door hands over: a figure asset under a `format` tag. One box standing on the floor, its room declared. */
45
16
  const v3AssetOf = (type) => ({
46
17
  version: 'figure-v3-asset-1',
47
18
  name: type,
@@ -92,54 +63,59 @@ function fetcherOf(published, failing = []) {
92
63
  if (failing.includes(type)) {
93
64
  throw new Error(`no route to the door (${type})`);
94
65
  }
95
- return (_a = published[type]) !== null && _a !== void 0 ? _a : null;
66
+ return ((_a = published[type]) !== null && _a !== void 0 ? _a : null);
96
67
  }
97
68
  };
98
69
  }
99
70
  describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {
100
71
  afterEach(() => {
101
- resetFigures();
102
72
  resetFiguresV3();
103
73
  });
104
74
  it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {
105
- const { asked, fetch } = fetcherOf({ EQ_MIXER: blueprintOf('EQ_MIXER') });
75
+ const { asked, fetch } = fetcherOf({ EQ_MIXER: v3PayloadOf('EQ_MIXER') });
106
76
  await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01']);
107
77
  expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01']);
108
78
  });
109
79
  it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {
110
- const { asked, fetch } = fetcherOf({ EQ_TAKEN: blueprintOf('EQ_TAKEN') });
80
+ const { asked, fetch } = fetcherOf({ EQ_TAKEN: v3PayloadOf('EQ_TAKEN') });
111
81
  /* Somebody else already draws this name — a plugin, an app's own component. */
112
82
  Component.register('EQ_TAKEN', class Taken extends Component {
113
83
  });
114
84
  await createFigureResolver(fetch)(['EQ_TAKEN']);
115
85
  expect(asked).to.deep.equal([]);
116
- expect(hasFigure('EQ_TAKEN')).to.equal(false);
86
+ expect(hasFigureV3('EQ_TAKEN')).to.equal(false);
117
87
  });
118
- it('받은 청사진을 등록한다 — 도면은 type 만 적으면 된다', async () => {
119
- const { fetch } = fetcherOf({ EQ_REGISTERED: blueprintOf('EQ_REGISTERED') });
88
+ it('받은 자산을 등록한다 — 도면은 type 만 적으면 된다', async () => {
89
+ const { fetch } = fetcherOf({ EQ_REGISTERED: v3PayloadOf('EQ_REGISTERED') });
120
90
  await createFigureResolver(fetch)(['EQ_REGISTERED']);
121
- expect(hasFigure('EQ_REGISTERED')).to.equal(true);
91
+ expect(hasFigureV3('EQ_REGISTERED')).to.equal(true);
122
92
  /* The component type is registered with it — the model writes `type` and nothing else. */
123
93
  expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined);
124
94
  });
125
- it('V3 자산은 V3 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {
95
+ it('자산은 씬의 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {
126
96
  const { fetch } = fetcherOf({ EQ_V3: v3PayloadOf('EQ_V3') });
127
97
  await createFigureResolver(fetch)(['EQ_V3']);
128
98
  expect(hasFigureV3('EQ_V3')).to.equal(true);
129
- expect(hasFigure('EQ_V3')).to.equal(false);
130
- /* 도면은 판을 모른다 — 두 판 다 type 하나만 적으면 그려진다. */
99
+ /* 도면은 type 하나만 적으면 그려진다. */
131
100
  expect(Component.register('EQ_V3')).to.not.equal(undefined);
132
101
  });
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);
102
+ it('자산이 아닌 것(format 이 v3 가 아닌 답)은 그 type 만 실패로 남는다 — 옛 청사진을 그리지 않는다', async () => {
103
+ const { fetch } = fetcherOf({ EQ_NEW: v3PayloadOf('EQ_NEW'), EQ_OLD: { version: 1, type: 'EQ_OLD', groups: [] } });
104
+ let message = '';
105
+ try {
106
+ await createFigureResolver(fetch)(['EQ_NEW', 'EQ_OLD']);
107
+ }
108
+ catch (error) {
109
+ message = error instanceof Error ? error.message : String(error);
110
+ }
111
+ expect(message).to.contain('not a figure asset: EQ_OLD');
137
112
  expect(hasFigureV3('EQ_NEW')).to.equal(true);
113
+ expect(hasFigureV3('EQ_OLD')).to.equal(false);
138
114
  });
139
115
  it('읽을 수 없는 V3 자산은 그 type 만 실패로 남는다', async () => {
140
116
  const broken = v3PayloadOf('EQ_V3_BROKEN');
141
117
  broken.asset.appearance = [];
142
- const { fetch } = fetcherOf({ EQ_BESIDE_V3: blueprintOf('EQ_BESIDE_V3'), EQ_V3_BROKEN: broken });
118
+ const { fetch } = fetcherOf({ EQ_BESIDE_V3: v3PayloadOf('EQ_BESIDE_V3'), EQ_V3_BROKEN: broken });
143
119
  let message = '';
144
120
  try {
145
121
  await createFigureResolver(fetch)(['EQ_BESIDE_V3', 'EQ_V3_BROKEN']);
@@ -148,7 +124,7 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
148
124
  message = error instanceof Error ? error.message : String(error);
149
125
  }
150
126
  expect(message).to.contain('EQ_V3_BROKEN');
151
- expect(hasFigure('EQ_BESIDE_V3')).to.equal(true);
127
+ expect(hasFigureV3('EQ_BESIDE_V3')).to.equal(true);
152
128
  expect(hasFigureV3('EQ_V3_BROKEN')).to.equal(false);
153
129
  });
154
130
  it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {
@@ -161,10 +137,10 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
161
137
  rejected = true;
162
138
  }
163
139
  expect(rejected).to.equal(false);
164
- expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false);
140
+ expect(hasFigureV3('EQ_NOT_PUBLISHED')).to.equal(false);
165
141
  });
166
142
  it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {
167
- const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: blueprintOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN']);
143
+ const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: v3PayloadOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN']);
168
144
  let message = '';
169
145
  try {
170
146
  await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN']);
@@ -174,12 +150,12 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
174
150
  }
175
151
  expect(message).to.contain('EQ_BROKEN');
176
152
  expect(message).to.not.contain('EQ_BESIDE_BROKEN');
177
- expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true);
153
+ expect(hasFigureV3('EQ_BESIDE_BROKEN')).to.equal(true);
178
154
  });
179
- it('읽을 수 없는 청사진은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {
155
+ it('읽을 수 없는 자산은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {
180
156
  const { fetch } = fetcherOf({
181
- EQ_BESIDE_MALFORMED: blueprintOf('EQ_BESIDE_MALFORMED'),
182
- EQ_MALFORMED: { version: BLUEPRINT_VERSION, type: 'EQ_MALFORMED' }
157
+ EQ_BESIDE_MALFORMED: v3PayloadOf('EQ_BESIDE_MALFORMED'),
158
+ EQ_MALFORMED: { format: 'v3', type: 'EQ_MALFORMED', asset: { version: 'figure-v3-asset-1' } }
183
159
  });
184
160
  let message = '';
185
161
  try {
@@ -189,11 +165,11 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
189
165
  message = error instanceof Error ? error.message : String(error);
190
166
  }
191
167
  expect(message).to.contain('EQ_MALFORMED');
192
- expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true);
193
- expect(hasFigure('EQ_MALFORMED')).to.equal(false);
168
+ expect(hasFigureV3('EQ_BESIDE_MALFORMED')).to.equal(true);
169
+ expect(hasFigureV3('EQ_MALFORMED')).to.equal(false);
194
170
  });
195
171
  it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {
196
- const { fetch } = fetcherOf({ EQ_ON_A_BOARD: blueprintOf('EQ_ON_A_BOARD') });
172
+ const { fetch } = fetcherOf({ EQ_ON_A_BOARD: v3PayloadOf('EQ_ON_A_BOARD') });
197
173
  const takeBack = registerTypeResolver(createFigureResolver(fetch));
198
174
  try {
199
175
  const model = {
@@ -206,7 +182,7 @@ describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () =>
206
182
  const resolution = await resolveUnknownTypes(model);
207
183
  expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED']);
208
184
  expect(resolution.failed).to.deep.equal([]);
209
- expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true);
185
+ expect(hasFigureV3('EQ_ON_A_BOARD')).to.equal(true);
210
186
  }
211
187
  finally {
212
188
  takeBack();
@@ -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, 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"]}
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, hasFigureV3, registerTypeResolver, 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 payload below is written out rather than built with figure-model: this package does not read it, it passes it\n * to the scene's own registration, and a fixture that drifts from the format should fail loudly here rather than\n * draw nothing at runtime.\n */\n\n/** What the door hands over: a figure asset under a `format` tag. One box standing on the floor, its room declared. */\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) as never\n }\n }\n}\n\ndescribe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {\n afterEach(() => {\n resetFiguresV3()\n })\n\n it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_MIXER: v3PayloadOf('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: v3PayloadOf('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(hasFigureV3('EQ_TAKEN')).to.equal(false)\n })\n\n it('받은 자산을 등록한다 — 도면은 type 만 적으면 된다', async () => {\n const { fetch } = fetcherOf({ EQ_REGISTERED: v3PayloadOf('EQ_REGISTERED') })\n\n await createFigureResolver(fetch)(['EQ_REGISTERED'])\n\n expect(hasFigureV3('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({ EQ_V3: v3PayloadOf('EQ_V3') })\n\n await createFigureResolver(fetch)(['EQ_V3'])\n\n expect(hasFigureV3('EQ_V3')).to.equal(true)\n /* 도면은 type 하나만 적으면 그려진다. */\n expect(Component.register('EQ_V3')).to.not.equal(undefined)\n })\n\n it('자산이 아닌 것(format 이 v3 가 아닌 답)은 그 type 만 실패로 남는다 — 옛 청사진을 그리지 않는다', async () => {\n const { fetch } = fetcherOf({ EQ_NEW: v3PayloadOf('EQ_NEW'), EQ_OLD: { version: 1, type: 'EQ_OLD', groups: [] } })\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_NEW', 'EQ_OLD'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('not a figure asset: EQ_OLD')\n expect(hasFigureV3('EQ_NEW')).to.equal(true)\n expect(hasFigureV3('EQ_OLD')).to.equal(false)\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: v3PayloadOf('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(hasFigureV3('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(hasFigureV3('EQ_NOT_PUBLISHED')).to.equal(false)\n })\n\n it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {\n const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: v3PayloadOf('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(hasFigureV3('EQ_BESIDE_BROKEN')).to.equal(true)\n })\n\n it('읽을 수 없는 자산은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {\n const { fetch } = fetcherOf({\n EQ_BESIDE_MALFORMED: v3PayloadOf('EQ_BESIDE_MALFORMED'),\n EQ_MALFORMED: { format: 'v3', type: 'EQ_MALFORMED', asset: { version: 'figure-v3-asset-1' } }\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(hasFigureV3('EQ_BESIDE_MALFORMED')).to.equal(true)\n expect(hasFigureV3('EQ_MALFORMED')).to.equal(false)\n })\n\n it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {\n const { fetch } = fetcherOf({ EQ_ON_A_BOARD: v3PayloadOf('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(hasFigureV3('EQ_ON_A_BOARD')).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}