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/row-names.test.ts
DELETED
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The row-name shape (N7–N9): one vocabulary, enforced instead of trusted.
|
|
3
|
-
*
|
|
4
|
-
* A row name is what a user reads where a setting line's name goes — an entry provider's
|
|
5
|
-
* `choice.setting`, or the label of the row that provider, a section or a card style contributes.
|
|
6
|
-
* The host names its own rows in one to three plain words (`Card style`, `stats`); the example read
|
|
7
|
-
* `Bash card → Bash rendering`, a breadcrumb with an arrow and a repeated qualifier. Third instance
|
|
8
|
-
* of one class of defect, so the rule lives in one place and BOTH boundaries apply it: a
|
|
9
|
-
* registration (section, card style) and a menu build (provider rows).
|
|
10
|
-
*
|
|
11
|
-
* These tests pin the rule itself, the rejection everywhere it can happen, the derived names that
|
|
12
|
-
* can therefore never violate it, and the example's corrected row. They also pin that nothing is
|
|
13
|
-
* rewritten: the offending string is reported verbatim and the row is simply absent.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import test from "node:test";
|
|
21
|
-
|
|
22
|
-
import { publishBridgeApi, resetBridgeApiForTests } from "../src/api.ts";
|
|
23
|
-
import { armBridge } from "../src/arm.ts";
|
|
24
|
-
import {
|
|
25
|
-
registerCardStyle,
|
|
26
|
-
registerRows,
|
|
27
|
-
registerSection,
|
|
28
|
-
resetBusForTests,
|
|
29
|
-
resetCardStylesForTests,
|
|
30
|
-
resetSectionsForTests,
|
|
31
|
-
snapshotCardStyles,
|
|
32
|
-
snapshotSections,
|
|
33
|
-
} from "../src/bus.ts";
|
|
34
|
-
import { installCardStyleRows } from "../src/card-style-rows.ts";
|
|
35
|
-
import { HOST_CARD_STYLE_SETTING } from "../src/card-style-line.ts";
|
|
36
|
-
import { disableCaptureBackend, resetCaptureStateForTests } from "../src/capture.ts";
|
|
37
|
-
import { resetHookStateForTests } from "../src/hook.ts";
|
|
38
|
-
import { resetNoticesForTests } from "../src/notices.ts";
|
|
39
|
-
import {
|
|
40
|
-
cardStyleRowLabelText,
|
|
41
|
-
sectionRowLabelText,
|
|
42
|
-
sectionRowLabels,
|
|
43
|
-
} from "../src/row-labels.ts";
|
|
44
|
-
import {
|
|
45
|
-
MAX_ROW_NAME_WORDS,
|
|
46
|
-
ROW_NAME_MARKS,
|
|
47
|
-
getRowNameRejections,
|
|
48
|
-
resetRowNameRejectionsForTests,
|
|
49
|
-
rowNameProblem,
|
|
50
|
-
} from "../src/row-names.ts";
|
|
51
|
-
import { getLastRowBuild, resetRowBuildStateForTests } from "../src/rows.ts";
|
|
52
|
-
import { installSectionRows } from "../src/sections-rows.ts";
|
|
53
|
-
import { collectStatus, formatStatus } from "../src/status.ts";
|
|
54
|
-
import { resetStoreStateForTests } from "../src/store.ts";
|
|
55
|
-
import { registerToolDesign, resetToolDesignsForTests } from "../src/tool-designs.ts";
|
|
56
|
-
import {
|
|
57
|
-
installToolDesignRows,
|
|
58
|
-
resetToolDesignRowStateForTests,
|
|
59
|
-
} from "../src/tool-design-rows.ts";
|
|
60
|
-
import type { CardFrame, CustomizeRow, SidebarSection } from "../src/types.ts";
|
|
61
|
-
import { createFakeHost, type FakeHost } from "./fake-host.ts";
|
|
62
|
-
|
|
63
|
-
const MARK_RULE =
|
|
64
|
-
'a row name is one to three plain words with no arrow, colon, dot, pipe, slash or bracket';
|
|
65
|
-
const WORD_RULE = "a row name is one to three plain words";
|
|
66
|
-
|
|
67
|
-
let configHome: string;
|
|
68
|
-
let previousConfigHome: string | undefined;
|
|
69
|
-
|
|
70
|
-
test.before(() => {
|
|
71
|
-
configHome = mkdtempSync(join(tmpdir(), "gsb-row-names-"));
|
|
72
|
-
previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
|
|
73
|
-
process.env.GENTLE_PI_CONFIG_HOME = configHome;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test.after(() => {
|
|
77
|
-
if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
|
|
78
|
-
else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
|
|
79
|
-
rmSync(configHome, { recursive: true, force: true });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test.beforeEach(() => {
|
|
83
|
-
rmSync(join(configHome, "gentle-shell-bridge"), { recursive: true, force: true });
|
|
84
|
-
resetBridgeApiForTests();
|
|
85
|
-
resetBusForTests();
|
|
86
|
-
resetSectionsForTests();
|
|
87
|
-
resetCardStylesForTests();
|
|
88
|
-
resetRowNameRejectionsForTests();
|
|
89
|
-
resetCaptureStateForTests();
|
|
90
|
-
resetHookStateForTests();
|
|
91
|
-
resetRowBuildStateForTests();
|
|
92
|
-
resetNoticesForTests();
|
|
93
|
-
resetStoreStateForTests();
|
|
94
|
-
resetToolDesignsForTests();
|
|
95
|
-
resetToolDesignRowStateForTests();
|
|
96
|
-
disableCaptureBackend();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const frame = (): CardFrame => ({
|
|
100
|
-
top: (ctx) => "▛".repeat(ctx.width),
|
|
101
|
-
line: (ctx) => ctx.text,
|
|
102
|
-
bottom: (ctx) => "▙".repeat(ctx.width),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const section = (id: string, extra: Partial<SidebarSection> = {}): SidebarSection => ({
|
|
106
|
-
id,
|
|
107
|
-
title: id.toUpperCase(),
|
|
108
|
-
lines: () => [`body ${id}`],
|
|
109
|
-
...extra,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const menuHost = (): FakeHost => {
|
|
113
|
-
const host = createFakeHost();
|
|
114
|
-
armBridge(host.ui);
|
|
115
|
-
return host;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const sectionsHost = (): FakeHost => {
|
|
119
|
-
installSectionRows();
|
|
120
|
-
return menuHost();
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const rowsOfLastBuild = (providerId: string): CustomizeRow[] =>
|
|
124
|
-
getLastRowBuild()?.find((record) => record.providerId === providerId)?.rows ?? [];
|
|
125
|
-
|
|
126
|
-
/** A provider's toggle: two consecutive rows sharing the line name. */
|
|
127
|
-
const toggle = (setting: string, label: string, values: [string, string]): CustomizeRow[] =>
|
|
128
|
-
values.map((value) => ({
|
|
129
|
-
category: "Cards",
|
|
130
|
-
label,
|
|
131
|
-
choice: { setting, value, current: () => value === values[0] },
|
|
132
|
-
action: () => {},
|
|
133
|
-
}));
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------------------------
|
|
136
|
-
// The rule: each mark, and the word limit (N7)
|
|
137
|
-
// ---------------------------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
test("every structural mark is rejected, named in the reason with the rule it broke", () => {
|
|
140
|
-
// The list is the documented one: an arrow, a colon, a dot, a pipe, a slash, a bracket. Each is
|
|
141
|
-
// checked inside an otherwise valid two-word name, so the mark is the only reason.
|
|
142
|
-
const expected = new Map<string, string>();
|
|
143
|
-
for (const mark of [
|
|
144
|
-
"→", "←", "↑", "↓",
|
|
145
|
-
":", ".", "|", "/", "\\",
|
|
146
|
-
"[", "]", "(", ")", "{", "}",
|
|
147
|
-
]) {
|
|
148
|
-
expected.set(mark, `it contains ${JSON.stringify(mark)}; ${MARK_RULE}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
for (const [mark, reason] of expected) {
|
|
152
|
-
assert.ok(ROW_NAME_MARKS.includes(mark), `${mark} is part of the documented mark list`);
|
|
153
|
-
assert.deepEqual(
|
|
154
|
-
rowNameProblem(`Bash ${mark} rendering`),
|
|
155
|
-
{ rule: "mark", reason },
|
|
156
|
-
`a name carrying ${mark} is rejected`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// The observed bad names, verbatim.
|
|
161
|
-
assert.deepEqual(rowNameProblem("Bash card → Bash rendering"), {
|
|
162
|
-
rule: "mark",
|
|
163
|
-
reason: `it contains "→"; ${MARK_RULE}`,
|
|
164
|
-
});
|
|
165
|
-
assert.deepEqual(rowNameProblem("Bash card: rendering"), {
|
|
166
|
-
rule: "mark",
|
|
167
|
-
reason: `it contains ":"; ${MARK_RULE}`,
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("a name is one to three plain words, and the limit is exact", () => {
|
|
172
|
-
assert.equal(rowNameProblem("Bash rendering"), undefined);
|
|
173
|
-
assert.equal(rowNameProblem("Bash"), undefined, "one word is a row name");
|
|
174
|
-
assert.equal(rowNameProblem("Bash card rendering"), undefined, "three words are the limit");
|
|
175
|
-
assert.equal(rowNameProblem(" Bash rendering "), undefined, "runs of spaces are not words");
|
|
176
|
-
|
|
177
|
-
assert.deepEqual(rowNameProblem("Bash card rendering extra"), {
|
|
178
|
-
rule: "words",
|
|
179
|
-
reason: `it has 4 words; ${WORD_RULE}`,
|
|
180
|
-
});
|
|
181
|
-
assert.deepEqual(rowNameProblem(""), {
|
|
182
|
-
rule: "words",
|
|
183
|
-
reason: `it has 0 words; ${WORD_RULE}`,
|
|
184
|
-
});
|
|
185
|
-
assert.deepEqual(rowNameProblem(" "), {
|
|
186
|
-
rule: "words",
|
|
187
|
-
reason: `it has 0 words; ${WORD_RULE}`,
|
|
188
|
-
});
|
|
189
|
-
assert.equal(MAX_ROW_NAME_WORDS, 3, "the documented limit");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// ---------------------------------------------------------------------------------------------
|
|
193
|
-
// An entry provider's line name and row label (N8)
|
|
194
|
-
// ---------------------------------------------------------------------------------------------
|
|
195
|
-
|
|
196
|
-
test("a provider's line name with an arrow is rejected: absent from the rows and from the build", () => {
|
|
197
|
-
const providerId = "loon.banner-source";
|
|
198
|
-
registerRows({
|
|
199
|
-
id: providerId,
|
|
200
|
-
category: "Cards",
|
|
201
|
-
rows: () => toggle("Loon → Banner source", "Loon banner source", ["activity", "output"]),
|
|
202
|
-
});
|
|
203
|
-
const host = menuHost();
|
|
204
|
-
|
|
205
|
-
host.buildMenu();
|
|
206
|
-
|
|
207
|
-
assert.deepEqual(host.toggleLines(), [], "no line reached the renderer");
|
|
208
|
-
assert.equal(
|
|
209
|
-
host.liveRows().some((row) => row.choice?.setting === "Loon → Banner source"),
|
|
210
|
-
false,
|
|
211
|
-
"the rejected rows were not injected into the host's array",
|
|
212
|
-
);
|
|
213
|
-
assert.deepEqual(rowsOfLastBuild(providerId), [], "and the build's own record has no row");
|
|
214
|
-
assert.deepEqual(getRowNameRejections(), [
|
|
215
|
-
{
|
|
216
|
-
surface: "rows",
|
|
217
|
-
id: providerId,
|
|
218
|
-
field: "line name",
|
|
219
|
-
value: "Loon → Banner source",
|
|
220
|
-
rule: "mark",
|
|
221
|
-
reason: `it contains "→"; ${MARK_RULE}`,
|
|
222
|
-
},
|
|
223
|
-
]);
|
|
224
|
-
assert.ok(
|
|
225
|
-
formatStatus(collectStatus()).includes(
|
|
226
|
-
` - rows "${providerId}" line name "Loon → Banner source": it contains "→"; ${MARK_RULE} ` +
|
|
227
|
-
"(rejected, never rewritten)",
|
|
228
|
-
),
|
|
229
|
-
formatStatus(collectStatus()).join("\n"),
|
|
230
|
-
);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("a provider's row label with a colon is rejected, and its siblings still render", () => {
|
|
234
|
-
const providerId = "loon.banner-source";
|
|
235
|
-
registerRows({
|
|
236
|
-
id: providerId,
|
|
237
|
-
category: "Cards",
|
|
238
|
-
rows: () => [
|
|
239
|
-
...toggle("Loon banner source", "Loon: banner source", ["activity", "output"]),
|
|
240
|
-
...toggle("Loon banner count", "Loon banner count", ["one", "two"]),
|
|
241
|
-
],
|
|
242
|
-
});
|
|
243
|
-
const host = menuHost();
|
|
244
|
-
|
|
245
|
-
host.buildMenu();
|
|
246
|
-
|
|
247
|
-
assert.deepEqual(
|
|
248
|
-
host.toggleLines(),
|
|
249
|
-
["Loon banner count ● one ○ two"],
|
|
250
|
-
"only the rejected line is absent; the provider's good rows are untouched",
|
|
251
|
-
);
|
|
252
|
-
assert.equal(
|
|
253
|
-
host.liveRows().some((row) => row.choice?.setting === "Loon banner source"),
|
|
254
|
-
false,
|
|
255
|
-
);
|
|
256
|
-
assert.deepEqual(
|
|
257
|
-
rowsOfLastBuild(providerId).map((row) => row.choice?.setting),
|
|
258
|
-
["Loon banner count", "Loon banner count"],
|
|
259
|
-
"the effective set holds the good rows only",
|
|
260
|
-
);
|
|
261
|
-
assert.deepEqual(getRowNameRejections(), [
|
|
262
|
-
{
|
|
263
|
-
surface: "rows",
|
|
264
|
-
id: providerId,
|
|
265
|
-
field: "row label",
|
|
266
|
-
value: "Loon: banner source",
|
|
267
|
-
rule: "mark",
|
|
268
|
-
reason: `it contains ":"; ${MARK_RULE}`,
|
|
269
|
-
},
|
|
270
|
-
]);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("a provider line name over the word limit is rejected with the count", () => {
|
|
274
|
-
const providerId = "loon.verbose";
|
|
275
|
-
registerRows({
|
|
276
|
-
id: providerId,
|
|
277
|
-
category: "Cards",
|
|
278
|
-
rows: () => toggle("Loon banner source activity", "Loon banner", ["on", "off"]),
|
|
279
|
-
});
|
|
280
|
-
const host = menuHost();
|
|
281
|
-
|
|
282
|
-
host.buildMenu();
|
|
283
|
-
|
|
284
|
-
assert.deepEqual(host.toggleLines(), []);
|
|
285
|
-
assert.deepEqual(getRowNameRejections(), [
|
|
286
|
-
{
|
|
287
|
-
surface: "rows",
|
|
288
|
-
id: providerId,
|
|
289
|
-
field: "line name",
|
|
290
|
-
value: "Loon banner source activity",
|
|
291
|
-
rule: "words",
|
|
292
|
-
reason: `it has 4 words; ${WORD_RULE}`,
|
|
293
|
-
},
|
|
294
|
-
]);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// ---------------------------------------------------------------------------------------------
|
|
298
|
-
// Sections and card styles (N8)
|
|
299
|
-
// ---------------------------------------------------------------------------------------------
|
|
300
|
-
|
|
301
|
-
test("a section whose rowLabel breaks the shape is not stored, and /bridge-status says why", () => {
|
|
302
|
-
registerSection(section("loon.music", { title: "MUSIC", rowLabel: "Music: now playing" }));
|
|
303
|
-
|
|
304
|
-
assert.deepEqual(snapshotSections(), [], "a rejected registration is never stored");
|
|
305
|
-
const host = sectionsHost();
|
|
306
|
-
host.buildMenu();
|
|
307
|
-
assert.deepEqual(host.toggleLines(), [], "and it never reaches a row");
|
|
308
|
-
assert.deepEqual(getRowNameRejections(), [
|
|
309
|
-
{
|
|
310
|
-
surface: "section",
|
|
311
|
-
id: "loon.music",
|
|
312
|
-
field: "row label",
|
|
313
|
-
value: "Music: now playing",
|
|
314
|
-
rule: "mark",
|
|
315
|
-
reason: `it contains ":"; ${MARK_RULE}`,
|
|
316
|
-
},
|
|
317
|
-
]);
|
|
318
|
-
assert.ok(
|
|
319
|
-
formatStatus(collectStatus()).includes(
|
|
320
|
-
' - section "loon.music" row label "Music: now playing": it contains ":"; ' +
|
|
321
|
-
`${MARK_RULE} (rejected, never rewritten)`,
|
|
322
|
-
),
|
|
323
|
-
formatStatus(collectStatus()).join("\n"),
|
|
324
|
-
);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test("a card style whose rowLabel breaks the shape is not registered and never reaches the line", () => {
|
|
328
|
-
registerCardStyle({
|
|
329
|
-
id: "loon.cute",
|
|
330
|
-
label: "cute",
|
|
331
|
-
rowLabel: "Cute → card",
|
|
332
|
-
frame: frame(),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
assert.deepEqual(snapshotCardStyles(), [], "a rejected style is never stored");
|
|
336
|
-
installCardStyleRows();
|
|
337
|
-
const host = createFakeHost({
|
|
338
|
-
rows: [
|
|
339
|
-
{ category: "Cards", label: "Card bash: builtin", action: () => {} },
|
|
340
|
-
{ category: "Cards", label: "Card output: builtin", action: () => {} },
|
|
341
|
-
{
|
|
342
|
-
category: "Cards",
|
|
343
|
-
label: () => "Card style: neon",
|
|
344
|
-
choice: { setting: HOST_CARD_STYLE_SETTING, value: "neon", current: () => false },
|
|
345
|
-
action: async () => {},
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
category: "Cards",
|
|
349
|
-
label: () => "Card style: float",
|
|
350
|
-
choice: { setting: HOST_CARD_STYLE_SETTING, value: "float", current: () => true },
|
|
351
|
-
action: async () => {},
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
});
|
|
355
|
-
armBridge(host.ui);
|
|
356
|
-
host.buildMenu();
|
|
357
|
-
|
|
358
|
-
assert.deepEqual(host.toggleLines(), ["Card style ○ neon ● float"], "the host's line is unchanged");
|
|
359
|
-
assert.deepEqual(getRowNameRejections(), [
|
|
360
|
-
{
|
|
361
|
-
surface: "card style",
|
|
362
|
-
id: "loon.cute",
|
|
363
|
-
field: "row label",
|
|
364
|
-
value: "Cute → card",
|
|
365
|
-
rule: "mark",
|
|
366
|
-
reason: `it contains "→"; ${MARK_RULE}`,
|
|
367
|
-
},
|
|
368
|
-
]);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// ---------------------------------------------------------------------------------------------
|
|
372
|
-
// Derivation can never produce a rejected value (N8)
|
|
373
|
-
// ---------------------------------------------------------------------------------------------
|
|
374
|
-
|
|
375
|
-
test("a derived row label always conforms: marks become word breaks, long titles are clamped", () => {
|
|
376
|
-
const derived = [
|
|
377
|
-
sectionRowLabelText({ title: "BASH CARD → STATUS" }),
|
|
378
|
-
sectionRowLabelText({ title: "MUSIC PLAYER NOW PLAYING TODAY" }),
|
|
379
|
-
sectionRowLabelText({ title: "···" }),
|
|
380
|
-
cardStyleRowLabelText({ label: "Cute → card | frame" }),
|
|
381
|
-
cardStyleRowLabelText({ label: "a very long style label with many words" }),
|
|
382
|
-
cardStyleRowLabelText({ label: "···" }),
|
|
383
|
-
];
|
|
384
|
-
|
|
385
|
-
for (const name of derived) {
|
|
386
|
-
assert.equal(rowNameProblem(name), undefined, `${JSON.stringify(name)} must conform`);
|
|
387
|
-
}
|
|
388
|
-
assert.deepEqual(derived, [
|
|
389
|
-
"bash card status",
|
|
390
|
-
"music player now",
|
|
391
|
-
"section",
|
|
392
|
-
"cute card frame",
|
|
393
|
-
"a very long",
|
|
394
|
-
"card style",
|
|
395
|
-
]);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
test("a collision suffix keeps a derived name inside the word limit", () => {
|
|
399
|
-
const labels = sectionRowLabels([
|
|
400
|
-
section("loon.one", { title: "MUSIC PLAYER NOW PLAYING" }),
|
|
401
|
-
section("loon.two", { title: "MUSIC PLAYER NOW PLAYING AGAIN" }),
|
|
402
|
-
]);
|
|
403
|
-
|
|
404
|
-
assert.deepEqual(
|
|
405
|
-
labels.map((entry) => [entry.label, entry.collision]),
|
|
406
|
-
[
|
|
407
|
-
["music player now", undefined],
|
|
408
|
-
["music player 2", "music player now"],
|
|
409
|
-
],
|
|
410
|
-
"the ordinal replaces the base's last word instead of growing the name",
|
|
411
|
-
);
|
|
412
|
-
for (const entry of labels) {
|
|
413
|
-
assert.equal(rowNameProblem(entry.label), undefined, `${entry.label} must conform`);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("PRESERVE: a conforming provider row renders exactly as before", () => {
|
|
418
|
-
registerRows({
|
|
419
|
-
id: "loon.banner-source",
|
|
420
|
-
category: "Cards",
|
|
421
|
-
rows: () => toggle("Loon banner source", "Loon banner source", ["activity", "output"]),
|
|
422
|
-
});
|
|
423
|
-
const host = menuHost();
|
|
424
|
-
|
|
425
|
-
host.buildMenu();
|
|
426
|
-
|
|
427
|
-
assert.deepEqual(host.toggleLines(), ["Loon banner source ● activity ○ output"]);
|
|
428
|
-
assert.equal(
|
|
429
|
-
host.rendered().includes("Loon banner source"),
|
|
430
|
-
true,
|
|
431
|
-
"the row label is carried through unchanged",
|
|
432
|
-
);
|
|
433
|
-
assert.deepEqual(getRowNameRejections(), [], "a conforming row records nothing");
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// ---------------------------------------------------------------------------------------------
|
|
437
|
-
// A tool design's line (N7, N9)
|
|
438
|
-
// ---------------------------------------------------------------------------------------------
|
|
439
|
-
//
|
|
440
|
-
// A design is declared per TOOL name, so its line's name is derived from that name, led by the noun
|
|
441
|
-
// the host's own Cards lines use (`Card style`, `Card content`, so `Card bash`) — never from the
|
|
442
|
-
// plugin's module path and never from the design's id. A design's `label` is a VALUE on the line,
|
|
443
|
-
// not a row name, so the shape rule applies to the line and the tool name is what satisfies it. The
|
|
444
|
-
// worked example used to contribute a `Bash rendering` provider row; it contributes a design now,
|
|
445
|
-
// and this fixture pins the naming rule the bus owns.
|
|
446
|
-
|
|
447
|
-
test("a design's line is named from its tool, and no id reaches a row", () => {
|
|
448
|
-
registerToolDesign({
|
|
449
|
-
tool: "bash",
|
|
450
|
-
id: "loon.bash-heading",
|
|
451
|
-
label: "bash heading",
|
|
452
|
-
apply: (lines) => [...lines],
|
|
453
|
-
});
|
|
454
|
-
installToolDesignRows();
|
|
455
|
-
const host = menuHost();
|
|
456
|
-
|
|
457
|
-
host.buildMenu();
|
|
458
|
-
|
|
459
|
-
assert.ok(
|
|
460
|
-
host.toggleLines().includes("Card bash ● native ○ bash heading"),
|
|
461
|
-
`the design's line is \`Card <tool>\`: ${host.toggleLines().join(" | ")}`,
|
|
462
|
-
);
|
|
463
|
-
for (const line of host.toggleLines()) {
|
|
464
|
-
const setting = line.split(" ")[0] ?? "";
|
|
465
|
-
assert.equal(rowNameProblem(setting), undefined, `${setting} must conform`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const visible = [...host.rendered(), ...host.toggleLines()].join("\n");
|
|
469
|
-
assert.equal(/loon\./u.test(visible), false, "the registration id never reaches a row");
|
|
470
|
-
assert.equal(/bash-card\./u.test(visible), false, "no dotted id reaches a row");
|
|
471
|
-
assert.equal(visible.includes("Bash design →"), false, "no arrow form survives");
|
|
472
|
-
assert.deepEqual(getRowNameRejections(), [], "the derived line name is accepted");
|
|
473
|
-
});
|