@operato/board 10.29.8 → 10.29.9

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.
@@ -1,8 +1,8 @@
1
1
  import { expect } from '@open-wc/testing';
2
2
  import { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene';
3
- import { createFigureResolver } from '../src/figure-provider.js';
3
+ import { createFigureResolver, FIGURE_TYPE_MAP_CHANGED, figureMapReleaseOfPage, resetFigureMapPage, setFigureMapChoice } from '../src/figure-provider.js';
4
4
  /**
5
- * The path a published figure takes to a board (ADR-0063 결정 2).
5
+ * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.
6
6
  *
7
7
  * things-scene holds the seat and knows nothing about figures; this package fills it. What is
8
8
  * checked here is the part that belongs to the board: which names are asked about, what happens to
@@ -51,138 +51,141 @@ const figureAssetOf = (type) => ({
51
51
  placement: 'floor'
52
52
  }
53
53
  });
54
- const figurePayloadOf = (type) => ({ type, asset: figureAssetOf(type) });
55
- /** Records what was asked for, and answers from a table. */
56
- function fetcherOf(published, failing = []) {
54
+ /** A server that answers from a table, from one map release, and records each question. */
55
+ function serverOf(published, release = { version: 1, revision: 1 }, refusals = {}) {
57
56
  const asked = [];
57
+ const state = { release };
58
58
  return {
59
59
  asked,
60
- fetch: async (type) => {
61
- var _a;
62
- asked.push(type);
63
- if (failing.includes(type)) {
64
- throw new Error(`no route to the door (${type})`);
65
- }
66
- return ((_a = published[type]) !== null && _a !== void 0 ? _a : null);
60
+ state,
61
+ fetch: async (types, map) => {
62
+ asked.push({ types: [...types], map });
63
+ const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }));
64
+ const refused = types
65
+ .filter(type => !published[type])
66
+ .map(type => { var _a; return ({ type, code: 'figure-not-in-type-map', message: (_a = refusals[type]) !== null && _a !== void 0 ? _a : `${type} is not in the type map` }); });
67
+ return { release: state.release, items, refused };
67
68
  }
68
69
  };
69
70
  }
70
- describe('figure 공급자 — 보드가 발행된 Figure 를 받는 길', () => {
71
+ const name = (label) => `hatiolab.com/${label}-${Math.random().toString(36).slice(2, 8)}`;
72
+ describe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {
71
73
  afterEach(() => {
72
74
  resetFigures();
75
+ resetFigureMapPage();
73
76
  });
74
- it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {
75
- const { asked, fetch } = fetcherOf({ EQ_MIXER: figurePayloadOf('EQ_MIXER') });
76
- await createFigureResolver(fetch)(['EQ_MIXER', 'rect', 'Conveyor', 'text', 'VEH_AGV_01']);
77
- expect(asked).to.deep.equal(['EQ_MIXER', 'VEH_AGV_01']);
77
+ it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {
78
+ const mixer = name('eq-mixer');
79
+ const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) });
80
+ await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01']);
81
+ expect(asked).to.deep.equal([{ types: [mixer, 'Conveyor', 'VEH_AGV_01'], map: 'ACTIVE' }]);
78
82
  });
79
- it('이미 등록된 type 은 묻지 않는다 — 그리고 있는 컴포넌트를 figure 가 덮지 않는다', async () => {
80
- const { asked, fetch } = fetcherOf({ EQ_TAKEN: figurePayloadOf('EQ_TAKEN') });
81
- /* Somebody else already draws this name — a plugin, an app's own component. */
82
- Component.register('EQ_TAKEN', class Taken extends Component {
83
+ it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {
84
+ const taken = name('taken');
85
+ const { asked, fetch } = serverOf({ [taken]: figureAssetOf(taken) });
86
+ Component.register(taken, class Taken extends Component {
83
87
  });
84
- await createFigureResolver(fetch)(['EQ_TAKEN']);
88
+ await createFigureResolver(fetch)([taken]);
85
89
  expect(asked).to.deep.equal([]);
86
- expect(hasFigure('EQ_TAKEN')).to.equal(false);
90
+ expect(hasFigure(taken)).to.equal(false);
87
91
  });
88
- it('받은 자산을 등록한다 — 도면은 type 만 적으면 된다', async () => {
89
- const { fetch } = fetcherOf({ EQ_REGISTERED: figurePayloadOf('EQ_REGISTERED') });
90
- await createFigureResolver(fetch)(['EQ_REGISTERED']);
91
- expect(hasFigure('EQ_REGISTERED')).to.equal(true);
92
- /* The component type is registered with it — the model writes `type` and nothing else. */
93
- expect(Component.register('EQ_REGISTERED')).to.not.equal(undefined);
92
+ it('받은 자산을 물은 이름으로 등록한다', async () => {
93
+ const conveyor = name('conveyor');
94
+ const { fetch } = serverOf({ [conveyor]: figureAssetOf(conveyor) });
95
+ await createFigureResolver(fetch)([conveyor]);
96
+ expect(hasFigure(conveyor)).to.equal(true);
97
+ expect(Component.register(conveyor)).to.not.equal(undefined);
94
98
  });
95
- it('자산은 씬의 문으로 등록한다 — 보드는 그것을 읽지 않고 씬에 넘긴다', async () => {
96
- const { fetch } = fetcherOf({ EQ: figurePayloadOf('EQ') });
97
- await createFigureResolver(fetch)(['EQ']);
98
- expect(hasFigure('EQ')).to.equal(true);
99
- /* 도면은 type 하나만 적으면 그려진다. */
100
- expect(Component.register('EQ')).to.not.equal(undefined);
99
+ it('★ 그리지 못한 이름은 서버가 준 이유를 그대로 돌려준다 — 읽을 수 없는 자산도 그 이름만 이유를 받는다', async () => {
100
+ const good = name('good');
101
+ const broken = name('broken');
102
+ const missing = name('missing');
103
+ const brokenAsset = figureAssetOf(broken);
104
+ brokenAsset.appearance = [];
105
+ const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { version: 1, revision: 1 }, { [missing]: '타입 맵에 없습니다' });
106
+ const answer = await createFigureResolver(fetch)([good, broken, missing]);
107
+ expect(hasFigure(good)).to.equal(true);
108
+ expect(hasFigure(broken)).to.equal(false);
109
+ expect(answer.refused.map(r => r.type)).to.deep.equal([missing, broken]);
110
+ expect(answer.refused[0].message).to.equal('타입 맵에 없습니다');
101
111
  });
102
- it('자산이 없는 답은 그 type 만 실패로 남는다 — 옛 청사진을 그리지 않는다', async () => {
103
- const { fetch } = fetcherOf({ EQ_NEW: figurePayloadOf('EQ_NEW'), EQ_OLD: { version: 1, type: 'EQ_OLD', groups: [] } });
112
+ it('물을 수 없으면 거절한다 — 씬은 실패로 모으고 보드는 열린다', async () => {
113
+ const fetch = async () => {
114
+ throw new Error('no route to the door');
115
+ };
104
116
  let message = '';
105
117
  try {
106
- await createFigureResolver(fetch)(['EQ_NEW', 'EQ_OLD']);
118
+ await createFigureResolver(fetch)([name('x')]);
107
119
  }
108
120
  catch (error) {
109
121
  message = error instanceof Error ? error.message : String(error);
110
122
  }
111
- expect(message).to.contain('not a figure asset: EQ_OLD');
112
- expect(hasFigure('EQ_NEW')).to.equal(true);
113
- expect(hasFigure('EQ_OLD')).to.equal(false);
123
+ expect(message).to.contain('no route to the door');
114
124
  });
115
- it('읽을 수 없는 자산은 그 type 만 실패로 남는다', async () => {
116
- const broken = figurePayloadOf('EQ_ASSET_BROKEN');
117
- broken.asset.appearance = [];
118
- const { fetch } = fetcherOf({ EQ_BESIDE: figurePayloadOf('EQ_BESIDE'), EQ_ASSET_BROKEN: broken });
119
- let message = '';
125
+ it('★ 페이지는 처음 받은 맵 릴리스를 기억하고, 후보의 revision 이 바뀐 답은 등록하지 않는다', async () => {
126
+ const first = name('first');
127
+ const later = name('later');
128
+ const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) }, { version: 2, revision: 3 });
129
+ const resolver = createFigureResolver(server.fetch);
130
+ setFigureMapChoice('CANDIDATE');
131
+ await resolver([first]);
132
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 });
133
+ let changed = null;
134
+ const listen = (e) => (changed = e.detail);
135
+ window.addEventListener(FIGURE_TYPE_MAP_CHANGED, listen);
120
136
  try {
121
- await createFigureResolver(fetch)(['EQ_BESIDE', 'EQ_ASSET_BROKEN']);
137
+ server.state.release = { version: 2, revision: 4 };
138
+ const answer = await resolver([later]);
139
+ expect(hasFigure(later)).to.equal(false);
140
+ expect(answer.refused.map(r => r.type)).to.deep.equal([later]);
141
+ expect(changed).to.deep.equal({ was: { map: 'CANDIDATE', version: 2, revision: 3 }, now: { map: 'CANDIDATE', version: 2, revision: 4 } });
142
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 });
122
143
  }
123
- catch (error) {
124
- message = error instanceof Error ? error.message : String(error);
144
+ finally {
145
+ window.removeEventListener(FIGURE_TYPE_MAP_CHANGED, listen);
125
146
  }
126
- expect(message).to.contain('EQ_ASSET_BROKEN');
127
- expect(hasFigure('EQ_BESIDE')).to.equal(true);
128
- expect(hasFigure('EQ_ASSET_BROKEN')).to.equal(false);
129
147
  });
130
- it('발행된 것이 없으면 조용히 자리표시로 남는다 — 거절이 아니다', async () => {
131
- const { fetch } = fetcherOf({});
132
- let rejected = false;
133
- try {
134
- await createFigureResolver(fetch)(['EQ_NOT_PUBLISHED']);
135
- }
136
- catch (_a) {
137
- rejected = true;
138
- }
139
- expect(rejected).to.equal(false);
140
- expect(hasFigure('EQ_NOT_PUBLISHED')).to.equal(false);
148
+ it('★ 활성 맵과 후보 맵의 등록을 한 페이지에서 섞지 않는다', async () => {
149
+ const active = name('active');
150
+ const preview = name('preview');
151
+ const server = serverOf({ [active]: figureAssetOf(active), [preview]: figureAssetOf(preview) }, { version: 1, revision: 1 });
152
+ const resolver = createFigureResolver(server.fetch);
153
+ await resolver([active]);
154
+ expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'ACTIVE', version: 1, revision: 1 });
155
+ setFigureMapChoice('CANDIDATE');
156
+ const answer = await resolver([preview]);
157
+ expect(server.asked.map(a => a.map)).to.deep.equal(['ACTIVE', 'CANDIDATE']);
158
+ expect(hasFigure(preview)).to.equal(false);
159
+ expect(answer.refused.map(r => r.type)).to.deep.equal([preview]);
141
160
  });
142
- it('물을 수 없으면 거절한다. 그때도 답을 받은 것은 등록된다', async () => {
143
- const { fetch } = fetcherOf({ EQ_BESIDE_BROKEN: figurePayloadOf('EQ_BESIDE_BROKEN') }, ['EQ_BROKEN']);
144
- let message = '';
145
- try {
146
- await createFigureResolver(fetch)(['EQ_BESIDE_BROKEN', 'EQ_BROKEN']);
147
- }
148
- catch (error) {
149
- message = error instanceof Error ? error.message : String(error);
150
- }
151
- expect(message).to.contain('EQ_BROKEN');
152
- expect(message).to.not.contain('EQ_BESIDE_BROKEN');
153
- expect(hasFigure('EQ_BESIDE_BROKEN')).to.equal(true);
154
- });
155
- it('읽을 수 없는 자산은 그 type 만 실패로 남는다 — 다른 type 을 막지 않는다', async () => {
156
- const { fetch } = fetcherOf({
157
- EQ_BESIDE_MALFORMED: figurePayloadOf('EQ_BESIDE_MALFORMED'),
158
- EQ_MALFORMED: { type: 'EQ_MALFORMED', asset: { version: 'figure-asset-1' } }
159
- });
160
- let message = '';
161
- try {
162
- await createFigureResolver(fetch)(['EQ_BESIDE_MALFORMED', 'EQ_MALFORMED']);
163
- }
164
- catch (error) {
165
- message = error instanceof Error ? error.message : String(error);
166
- }
167
- expect(message).to.contain('EQ_MALFORMED');
168
- expect(hasFigure('EQ_BESIDE_MALFORMED')).to.equal(true);
169
- expect(hasFigure('EQ_MALFORMED')).to.equal(false);
161
+ it('맵 릴리스가 없는 서버(저작 서버의 최신 정책)는 기억하지 않는다', async () => {
162
+ const a = name('a');
163
+ const b = name('b');
164
+ const server = serverOf({ [a]: figureAssetOf(a), [b]: figureAssetOf(b) }, null);
165
+ const resolver = createFigureResolver(server.fetch);
166
+ await resolver([a]);
167
+ await resolver([b]);
168
+ expect(figureMapReleaseOfPage()).to.equal(null);
169
+ expect(hasFigure(a)).to.equal(true);
170
+ expect(hasFigure(b)).to.equal(true);
170
171
  });
171
- it('씬의 자리에 앉아 동작한다 — 모델의 모르는 type 이 해석 뒤에 남지 않는다', async () => {
172
- const { fetch } = fetcherOf({ EQ_ON_A_BOARD: figurePayloadOf('EQ_ON_A_BOARD') });
172
+ it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {
173
+ const onBoard = name('on-board');
174
+ const never = name('never');
175
+ const { fetch } = serverOf({ [onBoard]: figureAssetOf(onBoard) });
173
176
  const takeBack = registerTypeResolver(createFigureResolver(fetch));
174
177
  try {
175
178
  const model = {
176
179
  type: 'container',
177
180
  components: [
178
- { type: 'EQ_ON_A_BOARD', left: 0, top: 0, width: 100, height: 100 },
179
- { type: 'EQ_NEVER_PUBLISHED', left: 0, top: 0, width: 100, height: 100 }
181
+ { type: onBoard, left: 0, top: 0, width: 100, height: 100 },
182
+ { type: never, left: 0, top: 0, width: 100, height: 100 }
180
183
  ]
181
184
  };
182
185
  const resolution = await resolveUnknownTypes(model);
183
- expect(resolution.unresolved).to.deep.equal(['EQ_NEVER_PUBLISHED']);
186
+ expect(resolution.unresolved).to.deep.equal([never]);
184
187
  expect(resolution.failed).to.deep.equal([]);
185
- expect(hasFigure('EQ_ON_A_BOARD')).to.equal(true);
188
+ expect(hasFigure(onBoard)).to.equal(true);
186
189
  }
187
190
  finally {
188
191
  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, 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 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. One box standing on the floor, its room declared. */\nconst figureAssetOf = (type: string) => ({\n version: 'figure-asset-1',\n name: type,\n document: {\n version: 'figure-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 figurePayloadOf = (type: string) => ({ type, asset: figureAssetOf(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 resetFigures()\n })\n\n it('대문자 이름만 서버에 묻는다 — 씬의 기본 type 과 오타는 문을 지나지 않는다', async () => {\n const { asked, fetch } = fetcherOf({ EQ_MIXER: figurePayloadOf('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: figurePayloadOf('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: figurePayloadOf('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({ EQ: figurePayloadOf('EQ') })\n\n await createFigureResolver(fetch)(['EQ'])\n\n expect(hasFigure('EQ')).to.equal(true)\n /* 도면은 type 하나만 적으면 그려진다. */\n expect(Component.register('EQ')).to.not.equal(undefined)\n })\n\n it('자산이 없는 답은 그 type 만 실패로 남는다 — 옛 청사진을 그리지 않는다', async () => {\n const { fetch } = fetcherOf({ EQ_NEW: figurePayloadOf('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(hasFigure('EQ_NEW')).to.equal(true)\n expect(hasFigure('EQ_OLD')).to.equal(false)\n })\n\n it('읽을 수 없는 자산은 그 type 만 실패로 남는다', async () => {\n const broken = figurePayloadOf('EQ_ASSET_BROKEN')\n broken.asset.appearance = []\n const { fetch } = fetcherOf({ EQ_BESIDE: figurePayloadOf('EQ_BESIDE'), EQ_ASSET_BROKEN: broken })\n\n let message = ''\n try {\n await createFigureResolver(fetch)(['EQ_BESIDE', 'EQ_ASSET_BROKEN'])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n\n expect(message).to.contain('EQ_ASSET_BROKEN')\n expect(hasFigure('EQ_BESIDE')).to.equal(true)\n expect(hasFigure('EQ_ASSET_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: figurePayloadOf('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: figurePayloadOf('EQ_BESIDE_MALFORMED'),\n EQ_MALFORMED: { type: 'EQ_MALFORMED', asset: { version: 'figure-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(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: figurePayloadOf('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":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAEtH,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,2BAA2B,CAAA;AAElC;;;;;;;;;;GAUG;AAEH,kGAAkG;AAClG,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO,EAAE,gBAAgB;IACzB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE;QACR,OAAO,EAAE,oBAAoB;QAC7B,KAAK,EAAE;YACL,OAAO,EAAE,cAAc;YACvB,MAAM,EAAE,EAAE;YACV,SAAS,EAAE;gBACT,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;gBACnC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;gBACrC,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;gBAC3C,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE;gBACpC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;aACpC;YACD,KAAK,EAAE;gBACL,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE;gBAC7H;oBACE,EAAE,EAAE,WAAW;oBACf,EAAE,EAAE,SAAS;oBACb,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;oBAChD,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBAC3B,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE;iBAC5C;gBACD,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE;aAC9F;SACF;QACD,YAAY,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;KAChF;IACD,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,EAAE;IACjB,OAAO,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE;IACzC,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,CAAC;IACxE,SAAS,EAAE;QACT,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;QACpH,SAAS,EAAE,OAAO;KACnB;CACF,CAAC,CAAA;AAEF,2FAA2F;AAC3F,SAAS,QAAQ,CACf,SAAkC,EAClC,UAAwD,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EACnF,WAAmC,EAAE;IAErC,MAAM,KAAK,GAAuC,EAAE,CAAA;IACpD,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,CAAA;IACzB,OAAO;QACL,KAAK;QACL,KAAK;QACL,KAAK,EAAE,KAAK,EAAE,KAAwB,EAAE,GAAW,EAAmC,EAAE;YACtF,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;YACtC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;YACrH,MAAM,OAAO,GAAG,KAAK;iBAClB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;iBAChC,GAAG,CAAC,IAAI,CAAC,EAAE,WAAC,OAAA,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAA,QAAQ,CAAC,IAAI,CAAC,mCAAI,GAAG,IAAI,yBAAyB,EAAE,CAAC,CAAA,EAAA,CAAC,CAAA;YACvH,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QACnD,CAAC;KACF,CAAA;AACH,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,gBAAgB,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAA;AAEjG,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,CAAA;QACd,kBAAkB,EAAE,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;QAC9B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAEpE,MAAM,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;QAEpF,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;IAC5F,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACpE,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,KAAM,SAAQ,SAAS;SAAY,CAAC,CAAA;QAEpE,MAAM,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;QAE1C,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAC/B,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qBAAqB,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;QACjC,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAEnE,MAAM,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;QAE7C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC1C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAA;QAC/B,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,WAAW,CAAC,UAAU,GAAG,EAAE,CAAA;QAC3B,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC,CAAA;QAE5I,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QAEzE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACtC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACzC,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;QACzE,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;YACvB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACzC,CAAC,CAAA;QACD,IAAI,OAAO,GAAG,EAAE,CAAA;QAChB,IAAI,CAAC;YACH,MAAM,oBAAoB,CAAC,KAAc,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;QACtH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACnD,kBAAkB,CAAC,WAAW,CAAC,CAAA;QAE/B,MAAM,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;QACvB,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;QAE7F,IAAI,OAAO,GAAQ,IAAI,CAAA;QACvB,MAAM,MAAM,GAAG,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,GAAI,CAAiB,CAAC,MAAM,CAAC,CAAA;QAClE,MAAM,CAAC,gBAAgB,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAA;QACxD,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAA;YAClD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YACtC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACxC,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YAC/D,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;YACzI,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/F,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,mBAAmB,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;QAC5H,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAEnD,MAAM,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;QACxB,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;QAE1F,kBAAkB,CAAC,WAAW,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;QAExC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAA;QAC3E,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACnB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACnB,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QAC/E,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAEnD,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACnB,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAEnB,MAAM,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACjE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,oBAAoB,CAAC,KAAK,CAAU,CAAC,CAAA;QAE3E,IAAI,CAAC;YACH,MAAM,KAAK,GAAG;gBACZ,IAAI,EAAE,WAAW;gBACjB,UAAU,EAAE;oBACV,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;oBAC3D,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;iBAC1D;aACF,CAAA;YACD,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAA;YACnD,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YACpD,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAC3C,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC3C,CAAC;gBAAS,CAAC;YACT,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { Component, hasFigure, registerTypeResolver, resetFigures, resolveUnknownTypes } from '@hatiolab/things-scene'\n\nimport {\n createFigureResolver,\n FIGURE_TYPE_MAP_CHANGED,\n figureMapReleaseOfPage,\n resetFigureMapPage,\n setFigureMapChoice,\n type FigureBlueprintsAnswer\n} from '../src/figure-provider.js'\n\n/**\n * The path a figure takes to a board (ADR-0063 결정 2, ADR-0114): one batch question by full name.\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. One box standing on the floor, its room declared. */\nconst figureAssetOf = (type: string) => ({\n version: 'figure-asset-1',\n name: type,\n document: {\n version: 'figure-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\n/** A server that answers from a table, from one map release, and records each question. */\nfunction serverOf(\n published: Record<string, unknown>,\n release: { version: number; revision: number } | null = { version: 1, revision: 1 },\n refusals: Record<string, string> = {}\n) {\n const asked: { types: string[]; map: string }[] = []\n const state = { release }\n return {\n asked,\n state,\n fetch: async (types: readonly string[], map: string): Promise<FigureBlueprintsAnswer> => {\n asked.push({ types: [...types], map })\n const items = types.filter(type => published[type]).map(type => ({ type, ref: `${type}@1`, asset: published[type] }))\n const refused = types\n .filter(type => !published[type])\n .map(type => ({ type, code: 'figure-not-in-type-map', message: refusals[type] ?? `${type} is not in the type map` }))\n return { release: state.release, items, refused }\n }\n }\n}\n\nconst name = (label: string) => `hatiolab.com/${label}-${Math.random().toString(36).slice(2, 8)}`\n\ndescribe('figure 공급자 — 보드가 Figure 를 정식 이름으로 한 번에 묻는다', () => {\n afterEach(() => {\n resetFigures()\n resetFigureMapPage()\n })\n\n it('등록되지 않은 type 을 대소문자와 관계없이 한 번에 묻는다 — 이미 등록된 것은 묻지 않는다', async () => {\n const mixer = name('eq-mixer')\n const { asked, fetch } = serverOf({ [mixer]: figureAssetOf(mixer) })\n\n await createFigureResolver(fetch)([mixer, 'rect', 'Conveyor', 'text', 'VEH_AGV_01'])\n\n expect(asked).to.deep.equal([{ types: [mixer, 'Conveyor', 'VEH_AGV_01'], map: 'ACTIVE' }])\n })\n\n it('이미 등록된 이름은 묻지 않고, figure 가 그 컴포넌트를 덮지 않는다', async () => {\n const taken = name('taken')\n const { asked, fetch } = serverOf({ [taken]: figureAssetOf(taken) })\n Component.register(taken, class Taken extends Component {} as never)\n\n await createFigureResolver(fetch)([taken])\n\n expect(asked).to.deep.equal([])\n expect(hasFigure(taken)).to.equal(false)\n })\n\n it('받은 자산을 물은 이름으로 등록한다', async () => {\n const conveyor = name('conveyor')\n const { fetch } = serverOf({ [conveyor]: figureAssetOf(conveyor) })\n\n await createFigureResolver(fetch)([conveyor])\n\n expect(hasFigure(conveyor)).to.equal(true)\n expect(Component.register(conveyor)).to.not.equal(undefined)\n })\n\n it('★ 그리지 못한 이름은 서버가 준 이유를 그대로 돌려준다 — 읽을 수 없는 자산도 그 이름만 이유를 받는다', async () => {\n const good = name('good')\n const broken = name('broken')\n const missing = name('missing')\n const brokenAsset = figureAssetOf(broken)\n brokenAsset.appearance = []\n const { fetch } = serverOf({ [good]: figureAssetOf(good), [broken]: brokenAsset }, { version: 1, revision: 1 }, { [missing]: '타입 맵에 없습니다' })\n\n const answer = await createFigureResolver(fetch)([good, broken, missing])\n\n expect(hasFigure(good)).to.equal(true)\n expect(hasFigure(broken)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([missing, broken])\n expect(answer!.refused[0].message).to.equal('타입 맵에 없습니다')\n })\n\n it('물을 수 없으면 거절한다 — 씬은 실패로 모으고 보드는 열린다', async () => {\n const fetch = async () => {\n throw new Error('no route to the door')\n }\n let message = ''\n try {\n await createFigureResolver(fetch as never)([name('x')])\n } catch (error) {\n message = error instanceof Error ? error.message : String(error)\n }\n expect(message).to.contain('no route to the door')\n })\n\n it('★ 페이지는 처음 받은 맵 릴리스를 기억하고, 후보의 revision 이 바뀐 답은 등록하지 않는다', async () => {\n const first = name('first')\n const later = name('later')\n const server = serverOf({ [first]: figureAssetOf(first), [later]: figureAssetOf(later) }, { version: 2, revision: 3 })\n const resolver = createFigureResolver(server.fetch)\n setFigureMapChoice('CANDIDATE')\n\n await resolver([first])\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })\n\n let changed: any = null\n const listen = (e: Event) => (changed = (e as CustomEvent).detail)\n window.addEventListener(FIGURE_TYPE_MAP_CHANGED, listen)\n try {\n server.state.release = { version: 2, revision: 4 }\n const answer = await resolver([later])\n expect(hasFigure(later)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([later])\n expect(changed).to.deep.equal({ was: { map: 'CANDIDATE', version: 2, revision: 3 }, now: { map: 'CANDIDATE', version: 2, revision: 4 } })\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'CANDIDATE', version: 2, revision: 3 })\n } finally {\n window.removeEventListener(FIGURE_TYPE_MAP_CHANGED, listen)\n }\n })\n\n it('★ 활성 맵과 후보 맵의 등록을 한 페이지에서 섞지 않는다', async () => {\n const active = name('active')\n const preview = name('preview')\n const server = serverOf({ [active]: figureAssetOf(active), [preview]: figureAssetOf(preview) }, { version: 1, revision: 1 })\n const resolver = createFigureResolver(server.fetch)\n\n await resolver([active])\n expect(figureMapReleaseOfPage()).to.deep.equal({ map: 'ACTIVE', version: 1, revision: 1 })\n\n setFigureMapChoice('CANDIDATE')\n const answer = await resolver([preview])\n\n expect(server.asked.map(a => a.map)).to.deep.equal(['ACTIVE', 'CANDIDATE'])\n expect(hasFigure(preview)).to.equal(false)\n expect(answer!.refused.map(r => r.type)).to.deep.equal([preview])\n })\n\n it('맵 릴리스가 없는 서버(저작 서버의 최신 정책)는 기억하지 않는다', async () => {\n const a = name('a')\n const b = name('b')\n const server = serverOf({ [a]: figureAssetOf(a), [b]: figureAssetOf(b) }, null)\n const resolver = createFigureResolver(server.fetch)\n\n await resolver([a])\n await resolver([b])\n\n expect(figureMapReleaseOfPage()).to.equal(null)\n expect(hasFigure(a)).to.equal(true)\n expect(hasFigure(b)).to.equal(true)\n })\n\n it('씬의 자리에 앉아 동작한다 — 받은 것은 그려지고 나머지는 자리표시로 남는다', async () => {\n const onBoard = name('on-board')\n const never = name('never')\n const { fetch } = serverOf({ [onBoard]: figureAssetOf(onBoard) })\n const takeBack = registerTypeResolver(createFigureResolver(fetch) as never)\n\n try {\n const model = {\n type: 'container',\n components: [\n { type: onBoard, left: 0, top: 0, width: 100, height: 100 },\n { type: never, left: 0, top: 0, width: 100, height: 100 }\n ]\n }\n const resolution = await resolveUnknownTypes(model)\n expect(resolution.unresolved).to.deep.equal([never])\n expect(resolution.failed).to.deep.equal([])\n expect(hasFigure(onBoard)).to.equal(true)\n } finally {\n takeBack()\n }\n })\n})\n"]}