gentle-shell-bridge 0.3.0 → 0.3.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/LICENSE +202 -0
- package/README.md +91 -722
- package/docs/contract.md +92 -35
- 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/button-rows.test.ts
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* T27 / S16: a BUTTON row is a first-class kind, not an accident of a missing field.
|
|
3
|
-
*
|
|
4
|
-
* The host's row type (`lib/visual-customize-view.ts:107-130`) makes `choice` optional and
|
|
5
|
-
* `action(inline)` required, and Enter/Space runs `action` (`:575-577`) exactly as it does for the
|
|
6
|
-
* host's own `Profiles` and `Reset` rows (`extensions/gentle-shell.ts:3541,3550`). So a row with
|
|
7
|
-
* `category`, `label`, `action` and NO `choice` is the second supported kind, and this suite pins
|
|
8
|
-
* everything about it through the real entry (`publishBridgeApi().registerRows`) and the real
|
|
9
|
-
* capture path:
|
|
10
|
-
*
|
|
11
|
-
* - it is accepted, reaches the host's array with its category untouched, and renders as its own
|
|
12
|
-
* row — never a value of another row's setting;
|
|
13
|
-
* - `preview`, `keyhint`, `visible` and a function-valued `label` are reference-identical, so the
|
|
14
|
-
* bridge passes them through instead of re-deriving them;
|
|
15
|
-
* - its label is the WHOLE visible text of the row, so the bridge's plain-word row-name rule applies
|
|
16
|
-
* to it exactly as it applies to a toggle's label: a mark, or more than three words, is refused
|
|
17
|
-
* and reported verbatim, and the row is absent rather than rewritten;
|
|
18
|
-
* - `confirmOnEnter` never touches it: no `choice` appears, no `applyOnMove` is injected, and the
|
|
19
|
-
* provider's own object is not mutated;
|
|
20
|
-
* - a throwing (or rejecting) `action` is contained by the bridge and reported ONCE through the
|
|
21
|
-
* notice channel that `/bridge-status` lists, so the menu and every other row stay usable;
|
|
22
|
-
* - a provider may mix toggles and buttons in one category, and the order it gave is kept.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import assert from "node:assert/strict";
|
|
26
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
27
|
-
import { tmpdir } from "node:os";
|
|
28
|
-
import { join } from "node:path";
|
|
29
|
-
import test from "node:test";
|
|
30
|
-
|
|
31
|
-
import { publishBridgeApi, resetBridgeApiForTests } from "../src/api.ts";
|
|
32
|
-
import { armBridge } from "../src/arm.ts";
|
|
33
|
-
import {
|
|
34
|
-
registerRows,
|
|
35
|
-
resetBusForTests,
|
|
36
|
-
resetCardStylesForTests,
|
|
37
|
-
resetSectionsForTests,
|
|
38
|
-
resetTransformsForTests,
|
|
39
|
-
} from "../src/bus.ts";
|
|
40
|
-
import { disableCaptureBackend, resetCaptureStateForTests } from "../src/capture.ts";
|
|
41
|
-
import { resetHookStateForTests } from "../src/hook.ts";
|
|
42
|
-
import { resetNoticesForTests, snapshotNotices } from "../src/notices.ts";
|
|
43
|
-
import { getRowNameRejections, resetRowNameRejectionsForTests } from "../src/row-names.ts";
|
|
44
|
-
import { getLastRowBuild, resetRowBuildStateForTests } from "../src/rows.ts";
|
|
45
|
-
import { resetStoreStateForTests } from "../src/store.ts";
|
|
46
|
-
import type { CustomizeRow, RowLabel } from "../src/types.ts";
|
|
47
|
-
import { createFakeHost, type FakeHost } from "./fake-host.ts";
|
|
48
|
-
|
|
49
|
-
const MARK_RULE =
|
|
50
|
-
"a row name is one to three plain words with no arrow, colon, dot, pipe, slash or bracket";
|
|
51
|
-
const WORD_RULE = "a row name is one to three plain words";
|
|
52
|
-
|
|
53
|
-
const labelText = (label: RowLabel | undefined): string => {
|
|
54
|
-
if (typeof label === "function") return label();
|
|
55
|
-
return label ?? "";
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const loonLabels = (host: FakeHost): string[] =>
|
|
59
|
-
host
|
|
60
|
-
.liveRows()
|
|
61
|
-
.map((row) => labelText(row.label))
|
|
62
|
-
.filter((label) => label.startsWith("Loon"));
|
|
63
|
-
|
|
64
|
-
const rowsOfLastBuild = (providerId: string): CustomizeRow[] =>
|
|
65
|
-
getLastRowBuild()?.find((record) => record.providerId === providerId)?.rows ?? [];
|
|
66
|
-
|
|
67
|
-
/** The one visible row a provider contributed, as the host holds it right now. */
|
|
68
|
-
const liveRow = (host: FakeHost, label: string): CustomizeRow => {
|
|
69
|
-
const row = host.liveRows().find((candidate) => labelText(candidate.label) === label);
|
|
70
|
-
assert.ok(row !== undefined, `the host holds no row labelled ${JSON.stringify(label)}`);
|
|
71
|
-
return row;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
let configHome: string;
|
|
75
|
-
let previousConfigHome: string | undefined;
|
|
76
|
-
|
|
77
|
-
test.before(() => {
|
|
78
|
-
configHome = mkdtempSync(join(tmpdir(), "gsb-button-rows-"));
|
|
79
|
-
previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
|
|
80
|
-
process.env.GENTLE_PI_CONFIG_HOME = configHome;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test.after(() => {
|
|
84
|
-
if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
|
|
85
|
-
else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
|
|
86
|
-
rmSync(configHome, { recursive: true, force: true });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test.beforeEach(() => {
|
|
90
|
-
rmSync(join(configHome, "gentle-shell-bridge"), { recursive: true, force: true });
|
|
91
|
-
resetBridgeApiForTests();
|
|
92
|
-
resetBusForTests();
|
|
93
|
-
resetSectionsForTests();
|
|
94
|
-
resetCardStylesForTests();
|
|
95
|
-
resetTransformsForTests();
|
|
96
|
-
resetRowNameRejectionsForTests();
|
|
97
|
-
resetCaptureStateForTests();
|
|
98
|
-
resetHookStateForTests();
|
|
99
|
-
resetRowBuildStateForTests();
|
|
100
|
-
resetNoticesForTests();
|
|
101
|
-
resetStoreStateForTests();
|
|
102
|
-
disableCaptureBackend();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const menuHost = (): FakeHost => {
|
|
106
|
-
const host = createFakeHost();
|
|
107
|
-
armBridge(host.ui);
|
|
108
|
-
return host;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/** A toggle line: consecutive rows sharing one `choice.setting`, the existing kind. */
|
|
112
|
-
const toggles = (setting: string, label: string, values: [string, string]): CustomizeRow[] =>
|
|
113
|
-
values.map((value) => ({
|
|
114
|
-
category: "Cards",
|
|
115
|
-
label,
|
|
116
|
-
choice: { setting, value, current: () => value === values[0] },
|
|
117
|
-
action: () => {},
|
|
118
|
-
}));
|
|
119
|
-
|
|
120
|
-
// ---------------------------------------------------------------------------------------------
|
|
121
|
-
// Accepted, rendered, and never a value of somebody else's setting
|
|
122
|
-
// ---------------------------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
test("a button row is accepted in an existing category and Enter runs its action once", async () => {
|
|
125
|
-
const calls: string[] = [];
|
|
126
|
-
publishBridgeApi().registerRows({
|
|
127
|
-
id: "loon.buttons",
|
|
128
|
-
category: "Cards",
|
|
129
|
-
rows: () => [
|
|
130
|
-
...toggles("Bash rendering", "Bash rendering", ["default", "tool"]),
|
|
131
|
-
{ category: "Cards", label: "Loon refresh", action: () => void calls.push("refresh") },
|
|
132
|
-
],
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const host = menuHost();
|
|
136
|
-
host.buildMenu();
|
|
137
|
-
|
|
138
|
-
assert.ok(host.categories().includes("Cards"), "an existing category is reused");
|
|
139
|
-
assert.deepEqual(loonLabels(host), ["Loon refresh"], "the row is drawn as its own line");
|
|
140
|
-
assert.deepEqual(
|
|
141
|
-
host.toggleLines(),
|
|
142
|
-
["Bash rendering ● default ○ tool"],
|
|
143
|
-
"and it is not a value of the toggle line it sits after",
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const row = liveRow(host, "Loon refresh");
|
|
147
|
-
assert.equal(row.choice, undefined, "a button carries no choice");
|
|
148
|
-
assert.equal("choice" in row, false, "and no choice is invented for it");
|
|
149
|
-
|
|
150
|
-
await host.press("Loon refresh");
|
|
151
|
-
assert.deepEqual(calls, ["refresh"], "Enter (or Space) runs the action exactly once");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("a button row in a new category keeps that category untouched", () => {
|
|
155
|
-
publishBridgeApi().registerRows({
|
|
156
|
-
id: "loon.buttons",
|
|
157
|
-
category: "Loon",
|
|
158
|
-
rows: () => [{ label: "Loon refresh", action: () => {} }],
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const host = menuHost();
|
|
162
|
-
host.buildMenu();
|
|
163
|
-
|
|
164
|
-
const row = liveRow(host, "Loon refresh");
|
|
165
|
-
assert.equal(row.category, "Loon", "the provider's own category name is not rewritten");
|
|
166
|
-
assert.ok(
|
|
167
|
-
host.liveRows().some((candidate) => candidate.category === "Loon"),
|
|
168
|
-
"the row is in the array the host derives its categories from (visual-customize-view.ts:387)",
|
|
169
|
-
);
|
|
170
|
-
// Fixed by S18: the bridge appends the providers' rows BEFORE the view's constructor runs
|
|
171
|
-
// `options.rows.map(...)` (`visual-customize-view.ts:387`), so the page the row names exists to be
|
|
172
|
-
// selected. A late push — after the factory returned — could only join a page the list already
|
|
173
|
-
// held, which is why a brand-new category was invisible before (E8).
|
|
174
|
-
assert.equal(
|
|
175
|
-
host.categories().includes("Loon"),
|
|
176
|
-
true,
|
|
177
|
-
"the new page is in the category list the view computed",
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// ---------------------------------------------------------------------------------------------
|
|
182
|
-
// Pass-through: nothing is re-derived
|
|
183
|
-
// ---------------------------------------------------------------------------------------------
|
|
184
|
-
|
|
185
|
-
test("preview, keyhint, visible and a function label pass through untouched", () => {
|
|
186
|
-
const preview = () => ({ title: "Loon · refresh", sample: "redraws the rail" });
|
|
187
|
-
const visible = () => true;
|
|
188
|
-
const label = () => "Loon refresh";
|
|
189
|
-
const action = () => {};
|
|
190
|
-
publishBridgeApi().registerRows({
|
|
191
|
-
id: "loon.buttons",
|
|
192
|
-
category: "Cards",
|
|
193
|
-
rows: () => [{ category: "Cards", label, preview, keyhint: "Enter refresh · Esc close", visible, action }],
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const host = menuHost();
|
|
197
|
-
host.buildMenu();
|
|
198
|
-
const row = liveRow(host, "Loon refresh");
|
|
199
|
-
|
|
200
|
-
assert.equal(row.label, label, "a function label is handed over as the same function");
|
|
201
|
-
assert.equal(row.preview, preview, "preview is the provider's function");
|
|
202
|
-
assert.equal(row.keyhint, "Enter refresh · Esc close", "keyhint is verbatim");
|
|
203
|
-
assert.equal(row.visible, visible, "visible is the provider's function");
|
|
204
|
-
assert.deepEqual(row.preview?.(), { title: "Loon · refresh", sample: "redraws the rail" });
|
|
205
|
-
assert.equal(row.visible?.(), true);
|
|
206
|
-
assert.notEqual(row.action, action, "the action is the ONE thing the bridge wraps (see below)");
|
|
207
|
-
assert.deepEqual(
|
|
208
|
-
Object.keys(row).sort(),
|
|
209
|
-
["action", "category", "keyhint", "label", "preview", "visible"],
|
|
210
|
-
"no field is added, dropped or renamed on the way through",
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// ---------------------------------------------------------------------------------------------
|
|
215
|
-
// The label is the row's whole visible text, so the row-name rule applies to it unchanged
|
|
216
|
-
// ---------------------------------------------------------------------------------------------
|
|
217
|
-
|
|
218
|
-
test("a button label with a structural mark is refused and reported, and its siblings stay", () => {
|
|
219
|
-
publishBridgeApi().registerRows({
|
|
220
|
-
id: "loon.buttons",
|
|
221
|
-
category: "Cards",
|
|
222
|
-
rows: () => [
|
|
223
|
-
{ category: "Cards", label: "Loon: refresh", action: () => {} },
|
|
224
|
-
{ category: "Cards", label: "Loon refresh the rail now", action: () => {} },
|
|
225
|
-
{ category: "Cards", label: () => "Loon: functional label", action: () => {} },
|
|
226
|
-
{ category: "Cards", label: "Loon refresh", action: () => {} },
|
|
227
|
-
],
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const host = menuHost();
|
|
231
|
-
host.buildMenu();
|
|
232
|
-
|
|
233
|
-
assert.deepEqual(loonLabels(host), ["Loon refresh"], "only the conforming row reaches the menu");
|
|
234
|
-
assert.deepEqual(
|
|
235
|
-
getRowNameRejections(),
|
|
236
|
-
[
|
|
237
|
-
{
|
|
238
|
-
surface: "rows",
|
|
239
|
-
id: "loon.buttons",
|
|
240
|
-
field: "row label",
|
|
241
|
-
value: "Loon: refresh",
|
|
242
|
-
rule: "mark",
|
|
243
|
-
reason: `it contains ":"; ${MARK_RULE}`,
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
surface: "rows",
|
|
247
|
-
id: "loon.buttons",
|
|
248
|
-
field: "row label",
|
|
249
|
-
value: "Loon refresh the rail now",
|
|
250
|
-
rule: "words",
|
|
251
|
-
reason: `it has 5 words; ${WORD_RULE}`,
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
surface: "rows",
|
|
255
|
-
id: "loon.buttons",
|
|
256
|
-
field: "row label",
|
|
257
|
-
value: "Loon: functional label",
|
|
258
|
-
rule: "mark",
|
|
259
|
-
reason: `it contains ":"; ${MARK_RULE}`,
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
"the same rule as a toggle's label, and a function label is evaluated to check it",
|
|
263
|
-
);
|
|
264
|
-
assert.deepEqual(
|
|
265
|
-
rowsOfLastBuild("loon.buttons").map((row) => labelText(row.label)),
|
|
266
|
-
["Loon refresh"],
|
|
267
|
-
"the refused rows are absent from the effective set, not merely hidden",
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// ---------------------------------------------------------------------------------------------
|
|
272
|
-
// confirmOnEnter leaves a button row alone
|
|
273
|
-
// ---------------------------------------------------------------------------------------------
|
|
274
|
-
|
|
275
|
-
test("a button row keeps no choice and no applyOnMove, and the provider's object is not mutated", () => {
|
|
276
|
-
const providerRow: CustomizeRow = { category: "Cards", label: "Loon refresh", action: () => {} };
|
|
277
|
-
const toggleRow: CustomizeRow = {
|
|
278
|
-
category: "Cards",
|
|
279
|
-
label: "Loon rendering",
|
|
280
|
-
choice: { setting: "Loon rendering", value: "on", current: () => true },
|
|
281
|
-
action: () => {},
|
|
282
|
-
};
|
|
283
|
-
publishBridgeApi().registerRows({
|
|
284
|
-
id: "loon.buttons",
|
|
285
|
-
category: "Cards",
|
|
286
|
-
rows: () => [providerRow, toggleRow],
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const host = menuHost();
|
|
290
|
-
host.buildMenu();
|
|
291
|
-
|
|
292
|
-
assert.deepEqual(
|
|
293
|
-
Object.keys(providerRow).sort(),
|
|
294
|
-
["action", "category", "label"],
|
|
295
|
-
"the bridge never writes to the row the provider keeps",
|
|
296
|
-
);
|
|
297
|
-
const row = liveRow(host, "Loon refresh");
|
|
298
|
-
assert.equal("choice" in row, false);
|
|
299
|
-
assert.equal("applyOnMove" in row, false);
|
|
300
|
-
|
|
301
|
-
// PRESERVE: the existing kind is unchanged — a choice row still defers its value to Enter.
|
|
302
|
-
const toggle = liveRow(host, "Loon rendering");
|
|
303
|
-
assert.equal(toggle.choice?.applyOnMove, false);
|
|
304
|
-
assert.equal(toggle.action, toggleRow.action, "and its action is handed over untouched");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// ---------------------------------------------------------------------------------------------
|
|
308
|
-
// A failing action is contained and reported once
|
|
309
|
-
// ---------------------------------------------------------------------------------------------
|
|
310
|
-
|
|
311
|
-
const failureNotice = (label: string, message: string): string =>
|
|
312
|
-
`gentle-shell-bridge: the button row ${JSON.stringify(label)} from "loon.buttons" failed: ` +
|
|
313
|
-
`${message}. The menu stays open; fix the row's action.`;
|
|
314
|
-
|
|
315
|
-
test("a synchronously throwing action is contained and reported once", async () => {
|
|
316
|
-
const ran: string[] = [];
|
|
317
|
-
publishBridgeApi().registerRows({
|
|
318
|
-
id: "loon.buttons",
|
|
319
|
-
category: "Cards",
|
|
320
|
-
rows: () => [
|
|
321
|
-
{
|
|
322
|
-
category: "Cards",
|
|
323
|
-
label: "Loon explode",
|
|
324
|
-
action: () => {
|
|
325
|
-
ran.push("explode");
|
|
326
|
-
throw new Error("row action exploded");
|
|
327
|
-
},
|
|
328
|
-
},
|
|
329
|
-
{ category: "Cards", label: "Loon refresh", action: () => void ran.push("refresh") },
|
|
330
|
-
],
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const host = menuHost();
|
|
334
|
-
host.buildMenu();
|
|
335
|
-
assert.deepEqual(loonLabels(host), ["Loon explode", "Loon refresh"]);
|
|
336
|
-
|
|
337
|
-
await host.press("Loon explode");
|
|
338
|
-
await host.press("Loon explode");
|
|
339
|
-
|
|
340
|
-
assert.deepEqual(ran, ["explode", "explode"], "the row ran; the throw did not escape the bridge");
|
|
341
|
-
assert.deepEqual(
|
|
342
|
-
snapshotNotices().entries.map((entry) => `${entry.level}: ${entry.message}`),
|
|
343
|
-
[`error: ${failureNotice("Loon explode", "row action exploded")}`],
|
|
344
|
-
"one report for one repeated failure, not one per press",
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
// The menu is still usable: the other row renders and its action still runs.
|
|
348
|
-
assert.deepEqual(loonLabels(host), ["Loon explode", "Loon refresh"]);
|
|
349
|
-
await host.press("Loon refresh");
|
|
350
|
-
assert.deepEqual(ran, ["explode", "explode", "refresh"]);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test("a rejecting action is contained and reported once", async () => {
|
|
354
|
-
publishBridgeApi().registerRows({
|
|
355
|
-
id: "loon.buttons",
|
|
356
|
-
category: "Cards",
|
|
357
|
-
rows: () => [
|
|
358
|
-
{
|
|
359
|
-
category: "Cards",
|
|
360
|
-
label: "Loon explode",
|
|
361
|
-
action: async () => {
|
|
362
|
-
throw new Error("row action rejected");
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
],
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const host = menuHost();
|
|
369
|
-
host.buildMenu();
|
|
370
|
-
|
|
371
|
-
await host.press("Loon explode");
|
|
372
|
-
await host.press("Loon explode");
|
|
373
|
-
|
|
374
|
-
assert.deepEqual(
|
|
375
|
-
snapshotNotices().entries.map((entry) => entry.message),
|
|
376
|
-
[failureNotice("Loon explode", "row action rejected")],
|
|
377
|
-
"a rejected promise is a failure too, and it is reported once",
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// ---------------------------------------------------------------------------------------------
|
|
382
|
-
// One provider, both kinds, in its own order
|
|
383
|
-
// ---------------------------------------------------------------------------------------------
|
|
384
|
-
|
|
385
|
-
test("toggles and buttons in one category keep the provider's order", () => {
|
|
386
|
-
publishBridgeApi().registerRows({
|
|
387
|
-
id: "loon.buttons",
|
|
388
|
-
category: "Cards",
|
|
389
|
-
rows: () => [
|
|
390
|
-
...toggles("Loon alpha", "Loon alpha", ["on", "off"]),
|
|
391
|
-
{ category: "Cards", label: "Loon refresh", action: () => {} },
|
|
392
|
-
...toggles("Loon beta", "Loon beta", ["low", "high"]),
|
|
393
|
-
],
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
const host = menuHost();
|
|
397
|
-
host.buildMenu();
|
|
398
|
-
|
|
399
|
-
assert.deepEqual(
|
|
400
|
-
rowsOfLastBuild("loon.buttons").map((row) => labelText(row.label)),
|
|
401
|
-
["Loon alpha", "Loon alpha", "Loon refresh", "Loon beta", "Loon beta"],
|
|
402
|
-
"the build keeps the provider's order across both kinds",
|
|
403
|
-
);
|
|
404
|
-
assert.deepEqual(
|
|
405
|
-
host.toggleLines(),
|
|
406
|
-
["Loon alpha ● on ○ off", "Loon beta ● low ○ high"],
|
|
407
|
-
"each toggle line is still one line; the button is its own row between them",
|
|
408
|
-
);
|
|
409
|
-
});
|