gentle-shell-bridge 0.3.0 → 0.3.1
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/LICENSE +202 -0
- package/docs/contract.md +1 -1
- package/package.json +13 -1
- package/src/api.ts +1 -1
- package/.codegraph/.gitignore +0 -5
- package/PRD.md +0 -144
- package/examples/bash-card.ts +0 -1694
- package/gentle-shell-bridge-0.3.0.tgz +0 -0
- package/odd/tasks/palette-sections.md +0 -30
- package/odd/tasks/plugin-keymap.md +0 -32
- package/odd/tasks/plugin-surface-contract.md +0 -55
- package/test/api.test.ts +0 -395
- package/test/apply-on-move.test.ts +0 -477
- package/test/arm.test.ts +0 -220
- package/test/bridge-ready.test.ts +0 -309
- package/test/bus.test.ts +0 -165
- package/test/button-rows.test.ts +0 -409
- package/test/capture.test.ts +0 -356
- package/test/card-style-rows.test.ts +0 -510
- package/test/card-style-transform.test.ts +0 -379
- package/test/card-style-wiring.test.ts +0 -258
- package/test/card-styles-e2e.test.ts +0 -491
- package/test/card-styles.test.ts +0 -324
- package/test/card-transcript-path.test.ts +0 -178
- package/test/config.test.ts +0 -93
- package/test/e2e.test.ts +0 -443
- package/test/example-bash-identity.test.ts +0 -372
- package/test/example-bash-matrix.test.ts +0 -856
- package/test/example-designs.test.ts +0 -734
- package/test/example-neon-transform.test.ts +0 -283
- package/test/example-plain-style.test.ts +0 -1268
- package/test/extension.test.ts +0 -160
- package/test/fake-events.ts +0 -58
- package/test/fake-host.ts +0 -211
- package/test/fake-sidebar.ts +0 -516
- package/test/fake-transcript.ts +0 -486
- package/test/frame-telemetry.test.ts +0 -417
- package/test/hook.test.ts +0 -196
- package/test/manifest.test.ts +0 -105
- package/test/new-category.test.ts +0 -979
- package/test/notices.test.ts +0 -328
- package/test/plugin-integration-lines.test.ts +0 -979
- package/test/plugin-surface.test.ts +0 -152
- package/test/presence.test.ts +0 -459
- package/test/rail-surfaces.test.ts +0 -457
- package/test/rail-width.test.ts +0 -423
- package/test/regression.test.ts +0 -483
- package/test/repaint-api.test.ts +0 -200
- package/test/reserved-categories.test.ts +0 -361
- package/test/row-labels.test.ts +0 -284
- package/test/row-names.test.ts +0 -473
- package/test/row-origin.test.ts +0 -395
- package/test/section-interaction.test.ts +0 -364
- package/test/section-panel.test.ts +0 -403
- package/test/sections-rows.test.ts +0 -291
- package/test/sidebar-backend.test.ts +0 -471
- package/test/sidebar-e2e.test.ts +0 -331
- package/test/sidebar-hover.test.ts +0 -276
- package/test/sidebar-independent.test.ts +0 -349
- package/test/sidebar-render.test.ts +0 -281
- package/test/sidebar-sections.test.ts +0 -245
- package/test/status-card-styles.test.ts +0 -201
- package/test/status-section-frame.test.ts +0 -181
- package/test/status-sections.test.ts +0 -243
- package/test/status.test.ts +0 -209
- package/test/store.test.ts +0 -192
- package/test/surface-repaint-cost.test.ts +0 -520
- package/test/tool-design-chain.test.ts +0 -483
- package/test/tool-design-cost.test.ts +0 -473
- package/test/tool-design-rows.test.ts +0 -359
- package/test/tool-designs.test.ts +0 -745
- package/test/transform-pipeline.test.ts +0 -465
- package/test/transforms-bus.test.ts +0 -233
- package/test/verifier-width-regressions.test.ts +0 -164
- package/tsconfig.json +0 -18
package/test/capture.test.ts
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import { registerRows, resetBusForTests } from "../src/bus.ts";
|
|
5
|
-
import {
|
|
6
|
-
getCaptureDiagnostics,
|
|
7
|
-
installCaptureBackend,
|
|
8
|
-
resetCaptureStateForTests,
|
|
9
|
-
} from "../src/capture.ts";
|
|
10
|
-
import { getConfigHome } from "../src/config.ts";
|
|
11
|
-
import type { CustomizeRow, RowContext, RowProvider } from "../src/types.ts";
|
|
12
|
-
import { createFakeHost } from "./fake-host.ts";
|
|
13
|
-
|
|
14
|
-
test.beforeEach(() => {
|
|
15
|
-
resetBusForTests();
|
|
16
|
-
resetCaptureStateForTests();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
/** A provider whose rows are plain labelled actions in one category. */
|
|
20
|
-
const labelsProvider = (id: string, category: string, labels: string[]): RowProvider => ({
|
|
21
|
-
id,
|
|
22
|
-
category,
|
|
23
|
-
rows: () => labels.map((label) => ({ label, action: () => {} })),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("installs on the instance and injects provider rows after the host's own rows", () => {
|
|
27
|
-
const host = createFakeHost();
|
|
28
|
-
registerRows(labelsProvider("p1", "Cards", ["Provider one", "Provider two"]));
|
|
29
|
-
|
|
30
|
-
const result = installCaptureBackend(host.ui);
|
|
31
|
-
|
|
32
|
-
assert.equal(result.armed, true);
|
|
33
|
-
assert.equal(result.target, "instance");
|
|
34
|
-
assert.equal(result.outcome, "armed");
|
|
35
|
-
|
|
36
|
-
const component = host.buildMenu();
|
|
37
|
-
|
|
38
|
-
assert.deepEqual(host.rendered(), [
|
|
39
|
-
"Card bash: builtin",
|
|
40
|
-
"Card output: builtin",
|
|
41
|
-
"Banner: builtin",
|
|
42
|
-
"Layout: builtin",
|
|
43
|
-
"Provider one",
|
|
44
|
-
"Provider two",
|
|
45
|
-
]);
|
|
46
|
-
assert.equal(host.invalidateCount(), 1, "invalidate is called once after injecting");
|
|
47
|
-
assert.ok(component !== undefined, "the adapter is returned unchanged");
|
|
48
|
-
assert.equal(
|
|
49
|
-
"rows" in (component as object),
|
|
50
|
-
false,
|
|
51
|
-
"the adapter hides the rows — duck-typing options.rows never matches",
|
|
52
|
-
);
|
|
53
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
54
|
-
kind: "injected",
|
|
55
|
-
injectedRows: 2,
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("installs on the prototype when the instance has no own custom", () => {
|
|
60
|
-
const host = createFakeHost({ customShape: "prototype" });
|
|
61
|
-
registerRows(labelsProvider("p1", "Cards", ["Proto one"]));
|
|
62
|
-
|
|
63
|
-
const result = installCaptureBackend(host.ui);
|
|
64
|
-
|
|
65
|
-
assert.equal(result.armed, true);
|
|
66
|
-
assert.equal(result.target, "prototype");
|
|
67
|
-
assert.equal(
|
|
68
|
-
Object.prototype.hasOwnProperty.call(host.ui, "custom"),
|
|
69
|
-
false,
|
|
70
|
-
"wrapping the prototype must not create an own property on the instance",
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
host.buildMenu();
|
|
74
|
-
|
|
75
|
-
assert.deepEqual(host.rendered().slice(-1), ["Proto one"]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("two providers coexist in one menu, in subscription order", () => {
|
|
79
|
-
const host = createFakeHost();
|
|
80
|
-
registerRows(labelsProvider("first", "Cards", ["First a"]));
|
|
81
|
-
registerRows(labelsProvider("second", "Banner", ["Second b"]));
|
|
82
|
-
installCaptureBackend(host.ui);
|
|
83
|
-
|
|
84
|
-
host.buildMenu();
|
|
85
|
-
const rows = host.liveRows();
|
|
86
|
-
|
|
87
|
-
assert.deepEqual(
|
|
88
|
-
rows.slice(4).map((row) => [row.category, row.label]),
|
|
89
|
-
[
|
|
90
|
-
["Cards", "First a"],
|
|
91
|
-
["Banner", "Second b"],
|
|
92
|
-
],
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("a throwing provider loses only its own rows and is recorded", () => {
|
|
97
|
-
const host = createFakeHost();
|
|
98
|
-
registerRows(labelsProvider("before", "Cards", ["Before ok"]));
|
|
99
|
-
registerRows({
|
|
100
|
-
id: "boom",
|
|
101
|
-
category: "Banner",
|
|
102
|
-
rows: () => {
|
|
103
|
-
throw new Error("provider exploded");
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
registerRows(labelsProvider("after", "Layout", ["After ok"]));
|
|
107
|
-
installCaptureBackend(host.ui);
|
|
108
|
-
|
|
109
|
-
host.buildMenu();
|
|
110
|
-
|
|
111
|
-
assert.deepEqual(host.rendered().slice(4), ["Before ok", "After ok"]);
|
|
112
|
-
assert.deepEqual(getCaptureDiagnostics().lastFailures, [
|
|
113
|
-
{ providerId: "boom", message: "provider exploded", rowsDropped: true },
|
|
114
|
-
]);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("rows from a provider that throws part-way are all dropped, not half-applied", () => {
|
|
118
|
-
const host = createFakeHost();
|
|
119
|
-
registerRows({
|
|
120
|
-
id: "partial",
|
|
121
|
-
category: "Cards",
|
|
122
|
-
rows: () => {
|
|
123
|
-
const rows: CustomizeRow[] = [{ label: "kept?", action: () => {} }];
|
|
124
|
-
// Building the second row throws: the first must not leak into the menu.
|
|
125
|
-
const explode = (): CustomizeRow => {
|
|
126
|
-
throw new Error("second row exploded");
|
|
127
|
-
};
|
|
128
|
-
rows.push(explode());
|
|
129
|
-
return rows;
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
installCaptureBackend(host.ui);
|
|
133
|
-
|
|
134
|
-
host.buildMenu();
|
|
135
|
-
|
|
136
|
-
assert.deepEqual(host.rendered().slice(4), []);
|
|
137
|
-
assert.deepEqual(getCaptureDiagnostics().lastFailures, [
|
|
138
|
-
{ providerId: "partial", message: "second row exploded", rowsDropped: true },
|
|
139
|
-
]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("provider.category fills a row's missing category, and the provider's row is not mutated", () => {
|
|
143
|
-
const host = createFakeHost();
|
|
144
|
-
const ownCategoryRow: CustomizeRow = { category: "Banner", label: "Own category", action: () => {} };
|
|
145
|
-
const inheritedCategoryRow: CustomizeRow = { label: "Inherited category", action: () => {} };
|
|
146
|
-
registerRows({
|
|
147
|
-
id: "categories",
|
|
148
|
-
category: "Layout",
|
|
149
|
-
rows: () => [ownCategoryRow, inheritedCategoryRow],
|
|
150
|
-
});
|
|
151
|
-
installCaptureBackend(host.ui);
|
|
152
|
-
|
|
153
|
-
host.buildMenu();
|
|
154
|
-
const injected = host.liveRows().slice(4);
|
|
155
|
-
|
|
156
|
-
assert.deepEqual(
|
|
157
|
-
injected.map((row) => row.category),
|
|
158
|
-
["Banner", "Layout"],
|
|
159
|
-
);
|
|
160
|
-
assert.equal(
|
|
161
|
-
inheritedCategoryRow.category,
|
|
162
|
-
undefined,
|
|
163
|
-
"the row the provider returned is not mutated",
|
|
164
|
-
);
|
|
165
|
-
assert.notEqual(injected[1], inheritedCategoryRow, "the filled row is a shallow clone");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("providers receive the active theme and the resolved config home", () => {
|
|
169
|
-
const theme = { name: "theme-under-test" };
|
|
170
|
-
const host = createFakeHost({ theme });
|
|
171
|
-
let received: RowContext | undefined;
|
|
172
|
-
registerRows({
|
|
173
|
-
id: "ctx",
|
|
174
|
-
category: "Cards",
|
|
175
|
-
rows: (ctx) => {
|
|
176
|
-
received = ctx;
|
|
177
|
-
return [];
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
installCaptureBackend(host.ui);
|
|
181
|
-
|
|
182
|
-
host.buildMenu();
|
|
183
|
-
|
|
184
|
-
assert.equal(received?.theme, theme, "the factory's second argument is the theme");
|
|
185
|
-
assert.equal(received?.configHome, getConfigHome());
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("Array.prototype.map identity is restored on success", () => {
|
|
189
|
-
const host = createFakeHost();
|
|
190
|
-
registerRows(labelsProvider("p1", "Cards", ["Row"]));
|
|
191
|
-
installCaptureBackend(host.ui);
|
|
192
|
-
const before = Array.prototype.map;
|
|
193
|
-
|
|
194
|
-
host.buildMenu();
|
|
195
|
-
|
|
196
|
-
assert.equal(Array.prototype.map, before);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("Array.prototype.map identity is restored when the factory throws", () => {
|
|
200
|
-
const host = createFakeHost({ throwInsideFactory: true });
|
|
201
|
-
registerRows(labelsProvider("p1", "Cards", ["Row"]));
|
|
202
|
-
installCaptureBackend(host.ui);
|
|
203
|
-
const before = Array.prototype.map;
|
|
204
|
-
|
|
205
|
-
assert.throws(() => host.buildMenu(), /factory exploded/);
|
|
206
|
-
|
|
207
|
-
assert.equal(Array.prototype.map, before);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test("Array.prototype.map identity is restored when the captured array is not a row array", () => {
|
|
211
|
-
const host = createFakeHost({ mapOnlyNonRows: true });
|
|
212
|
-
registerRows(labelsProvider("p1", "Cards", ["Must not appear"]));
|
|
213
|
-
installCaptureBackend(host.ui);
|
|
214
|
-
const before = Array.prototype.map;
|
|
215
|
-
|
|
216
|
-
host.buildMenu();
|
|
217
|
-
|
|
218
|
-
assert.equal(Array.prototype.map, before);
|
|
219
|
-
assert.deepEqual(
|
|
220
|
-
host.rendered().filter((label) => label === "Must not appear"),
|
|
221
|
-
[],
|
|
222
|
-
"a plain array of numbers is never injected into",
|
|
223
|
-
);
|
|
224
|
-
assert.equal(host.invalidateCount(), 0, "nothing was injected, so nothing was invalidated");
|
|
225
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
226
|
-
kind: "uncaptured",
|
|
227
|
-
injectedRows: 0,
|
|
228
|
-
reason: "no-capture",
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("an async factory is returned unchanged and recorded as uncaptured", async () => {
|
|
233
|
-
const host = createFakeHost({ factoryResult: "promise" });
|
|
234
|
-
registerRows(labelsProvider("p1", "Cards", ["Late row"]));
|
|
235
|
-
installCaptureBackend(host.ui);
|
|
236
|
-
const before = Array.prototype.map;
|
|
237
|
-
|
|
238
|
-
const result = host.buildMenu();
|
|
239
|
-
|
|
240
|
-
assert.ok(result instanceof Promise, "the promise is returned unchanged");
|
|
241
|
-
await result;
|
|
242
|
-
assert.equal(Array.prototype.map, before);
|
|
243
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
244
|
-
kind: "uncaptured",
|
|
245
|
-
injectedRows: 0,
|
|
246
|
-
reason: "async-factory",
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("a second install on the same ui does not double-wrap", () => {
|
|
251
|
-
const host = createFakeHost();
|
|
252
|
-
registerRows(labelsProvider("p1", "Cards", ["Once"]));
|
|
253
|
-
|
|
254
|
-
assert.equal(installCaptureBackend(host.ui).outcome, "armed");
|
|
255
|
-
const second = installCaptureBackend(host.ui);
|
|
256
|
-
assert.equal(second.armed, false);
|
|
257
|
-
assert.equal(second.outcome, "already-armed");
|
|
258
|
-
|
|
259
|
-
host.buildMenu();
|
|
260
|
-
|
|
261
|
-
assert.deepEqual(
|
|
262
|
-
host.rendered().filter((label) => label === "Once"),
|
|
263
|
-
["Once"],
|
|
264
|
-
"rows injected exactly once",
|
|
265
|
-
);
|
|
266
|
-
assert.equal(host.invalidateCount(), 1);
|
|
267
|
-
assert.equal(getCaptureDiagnostics().armedTargets, 1);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("the same menu built twice injects once", () => {
|
|
271
|
-
const host = createFakeHost();
|
|
272
|
-
registerRows(labelsProvider("p1", "Cards", ["Once"]));
|
|
273
|
-
installCaptureBackend(host.ui);
|
|
274
|
-
|
|
275
|
-
host.buildMenu();
|
|
276
|
-
host.buildMenu();
|
|
277
|
-
|
|
278
|
-
assert.deepEqual(host.rendered().filter((label) => label === "Once"), ["Once"]);
|
|
279
|
-
assert.equal(host.invalidateCount(), 1, "the second build marks the array and skips injection");
|
|
280
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
281
|
-
kind: "already-injected",
|
|
282
|
-
injectedRows: 0,
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("a throwing nested custom call neither loses the outer patch nor double-installs it", () => {
|
|
287
|
-
const host = createFakeHost();
|
|
288
|
-
registerRows(labelsProvider("p1", "Cards", ["Outer row"]));
|
|
289
|
-
installCaptureBackend(host.ui);
|
|
290
|
-
const before = Array.prototype.map;
|
|
291
|
-
|
|
292
|
-
// The outer factory triggers another wrapped `custom` call whose factory throws, then keeps
|
|
293
|
-
// building its own menu. If the nested call had installed its own patch and restored `map` in
|
|
294
|
-
// its own `finally`, the outer patch would be gone and the outer rows array would never be
|
|
295
|
-
// captured.
|
|
296
|
-
let outerInvalidations = 0;
|
|
297
|
-
const outerRows: CustomizeRow[] = [
|
|
298
|
-
{ category: "Cards", label: "Outer: builtin one", action: () => {} },
|
|
299
|
-
{ category: "Cards", label: "Outer: builtin two", action: () => {} },
|
|
300
|
-
{ category: "Banner", label: "Outer: builtin three", action: () => {} },
|
|
301
|
-
];
|
|
302
|
-
const component = host.ui.custom!((_ui, _theme) => {
|
|
303
|
-
try {
|
|
304
|
-
host.ui.custom!(() => {
|
|
305
|
-
throw new Error("inner exploded");
|
|
306
|
-
});
|
|
307
|
-
} catch {
|
|
308
|
-
// the host survives a nested failure
|
|
309
|
-
}
|
|
310
|
-
outerRows.map((row) => row.category ?? "Settings");
|
|
311
|
-
return {
|
|
312
|
-
render: () => outerRows.map((row) => String(row.label)),
|
|
313
|
-
handleInput: () => {},
|
|
314
|
-
invalidate: () => {
|
|
315
|
-
outerInvalidations += 1;
|
|
316
|
-
},
|
|
317
|
-
dispose: () => {},
|
|
318
|
-
};
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
assert.ok(component !== undefined);
|
|
322
|
-
assert.deepEqual(
|
|
323
|
-
outerRows.map((row) => row.label),
|
|
324
|
-
["Outer: builtin one", "Outer: builtin two", "Outer: builtin three", "Outer row"],
|
|
325
|
-
"the provider's row landed in the outer rows array captured after the nested throw",
|
|
326
|
-
);
|
|
327
|
-
assert.equal(Array.prototype.map, before, "map identity restored after the outermost call");
|
|
328
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
329
|
-
kind: "injected",
|
|
330
|
-
injectedRows: 1,
|
|
331
|
-
});
|
|
332
|
-
assert.equal(outerInvalidations, 1, "the outer component is the one invalidated");
|
|
333
|
-
assert.equal(getCaptureDiagnostics().nestingDepth, 0, "the nesting counter is back to zero");
|
|
334
|
-
assert.equal(getCaptureDiagnostics().armedTargets, 1, "one wrapped target, no second patch");
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("providers registering during a build do not disturb the fan-out", () => {
|
|
338
|
-
const host = createFakeHost();
|
|
339
|
-
registerRows({
|
|
340
|
-
id: "early",
|
|
341
|
-
category: "Cards",
|
|
342
|
-
rows: () => {
|
|
343
|
-
registerRows(labelsProvider("late", "Banner", ["Late: registered during build"]));
|
|
344
|
-
return [{ label: "Early ok", action: () => {} }];
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
installCaptureBackend(host.ui);
|
|
348
|
-
|
|
349
|
-
host.buildMenu();
|
|
350
|
-
|
|
351
|
-
assert.deepEqual(host.rendered().slice(4), ["Early ok"]);
|
|
352
|
-
assert.deepEqual(getCaptureDiagnostics().lastOutcome, {
|
|
353
|
-
kind: "injected",
|
|
354
|
-
injectedRows: 1,
|
|
355
|
-
});
|
|
356
|
-
});
|