@serverkgg/bridge 0.0.2 → 0.0.4

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.
@@ -0,0 +1,450 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { BridgeKind } from "../core";
3
+ import type { BridgeDriver } from "../modules";
4
+ import {
5
+ BridgeControl,
6
+ type BridgeField,
7
+ BridgeFormTarget,
8
+ BridgeLayout,
9
+ type BridgeSectionAction,
10
+ type BridgeVisibleWhen,
11
+ } from "../panel";
12
+ import { validateDriver } from "./bridgeDriver";
13
+
14
+ const text = (value: string) => ({
15
+ ar: value,
16
+ en: value,
17
+ });
18
+
19
+ const driverWith = (visibleWhen: BridgeVisibleWhen): BridgeDriver => ({
20
+ install: {
21
+ kind: BridgeKind.Install,
22
+ run: async () => {
23
+ return;
24
+ },
25
+ },
26
+ lifecycle: {
27
+ kind: BridgeKind.Lifecycle,
28
+ command: async () => [
29
+ "run",
30
+ ],
31
+ },
32
+ panel: {
33
+ tabs: [
34
+ {
35
+ id: "version",
36
+ title: text("Version"),
37
+ sections: [
38
+ {
39
+ layout: BridgeLayout.Form,
40
+ id: "version",
41
+ target: BridgeFormTarget.Variables,
42
+ visibleWhen,
43
+ fields: [
44
+ {
45
+ key: "MC_VERSION",
46
+ control: BridgeControl.Text,
47
+ label: text("Version"),
48
+ },
49
+ ],
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ });
56
+
57
+ const field = (key: string): BridgeField => ({
58
+ key,
59
+ control: BridgeControl.Text,
60
+ label: text(key),
61
+ });
62
+
63
+ const driverWithActions = (actions: BridgeSectionAction[]): BridgeDriver => ({
64
+ install: {
65
+ kind: BridgeKind.Install,
66
+ run: async () => {
67
+ return;
68
+ },
69
+ },
70
+ lifecycle: {
71
+ kind: BridgeKind.Lifecycle,
72
+ command: async () => [
73
+ "run",
74
+ ],
75
+ },
76
+ modules: {
77
+ broadcast: {
78
+ kind: BridgeKind.Actions,
79
+ requiresRunning: true,
80
+ actions: {
81
+ say: async () => {
82
+ return;
83
+ },
84
+ },
85
+ },
86
+ },
87
+ panel: {
88
+ tabs: [
89
+ {
90
+ id: "gameplay",
91
+ title: text("Gameplay"),
92
+ sections: [
93
+ {
94
+ layout: BridgeLayout.Actions,
95
+ id: "broadcast",
96
+ title: text("Broadcast"),
97
+ module: "broadcast",
98
+ actions,
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ },
104
+ });
105
+
106
+ const formSection = (id: string, fields: BridgeField[]) => ({
107
+ layout: BridgeLayout.Form as const,
108
+ id,
109
+ target: BridgeFormTarget.Variables,
110
+ fields,
111
+ });
112
+
113
+ const driverWithTabs = (tabs: number, sections: number): BridgeDriver => ({
114
+ install: {
115
+ kind: BridgeKind.Install,
116
+ run: async () => {
117
+ return;
118
+ },
119
+ },
120
+ lifecycle: {
121
+ kind: BridgeKind.Lifecycle,
122
+ command: async () => [
123
+ "run",
124
+ ],
125
+ },
126
+ panel: {
127
+ tabs: Array.from({
128
+ length: tabs,
129
+ }).map((_tab, tabIndex) => ({
130
+ id: `tab-${tabIndex}`,
131
+ title: text(`Tab ${tabIndex}`),
132
+ sections: Array.from({
133
+ length: sections,
134
+ }).map((_section, sectionIndex) =>
135
+ formSection(`section-${sectionIndex}`, [
136
+ field("MC_VERSION"),
137
+ ]),
138
+ ),
139
+ })),
140
+ },
141
+ });
142
+
143
+ describe("panel caps", () => {
144
+ test("takes the tab and section counts the agent mirror accepts", () => {
145
+ expect(() => validateDriver(driverWithTabs(8, 8))).not.toThrow();
146
+ });
147
+
148
+ test("rejects a ninth tab the agent mirror would drop", () => {
149
+ expect(() => validateDriver(driverWithTabs(9, 1))).toThrow(
150
+ "the panel declares 9 tabs — the platform takes at most 8",
151
+ );
152
+ });
153
+
154
+ test("rejects a ninth section the agent mirror would drop", () => {
155
+ expect(() => validateDriver(driverWithTabs(1, 9))).toThrow(
156
+ 'tab "tab-0" declares 9 sections — the platform takes at most 8',
157
+ );
158
+ });
159
+
160
+ test("rejects a form carrying more fields than the agent mirror accepts", () => {
161
+ const driver = driverWithTabs(1, 1);
162
+ const [tab] = driver.panel?.tabs ?? [];
163
+ const [section] = tab?.sections ?? [];
164
+
165
+ if (section?.layout !== BridgeLayout.Form) {
166
+ throw new Error("fixture is missing its form section");
167
+ }
168
+
169
+ section.fields = Array.from({
170
+ length: 33,
171
+ }).map((_entry, index) => field(`FIELD_${index}`));
172
+
173
+ expect(() => validateDriver(driver)).toThrow("declares 33 fields — the platform takes at most 32");
174
+ });
175
+
176
+ test("rejects a table carrying more columns than the agent mirror accepts", () => {
177
+ const driver = driverWithTabs(1, 1);
178
+ const [tab] = driver.panel?.tabs ?? [];
179
+
180
+ if (!tab) {
181
+ throw new Error("fixture is missing its tab");
182
+ }
183
+
184
+ tab.sections = [
185
+ {
186
+ layout: BridgeLayout.Table,
187
+ id: "players",
188
+ module: "players",
189
+ columns: Array.from({
190
+ length: 9,
191
+ }).map((_entry, index) => ({
192
+ key: `column${index}`,
193
+ label: text("Column"),
194
+ })),
195
+ },
196
+ ];
197
+
198
+ driver.modules = {
199
+ players: {
200
+ kind: BridgeKind.Collection,
201
+ list: async () => [],
202
+ },
203
+ };
204
+
205
+ expect(() => validateDriver(driver)).toThrow("declares 9 columns — the platform takes at most 8");
206
+ });
207
+
208
+ test("rejects a label longer than the agent mirror accepts", () => {
209
+ const driver = driverWithTabs(1, 1);
210
+ const [tab] = driver.panel?.tabs ?? [];
211
+
212
+ if (!tab) {
213
+ throw new Error("fixture is missing its tab");
214
+ }
215
+
216
+ tab.title = text("x".repeat(513));
217
+
218
+ expect(() => validateDriver(driver)).toThrow(
219
+ "title arabic text is 513 characters — the platform takes at most 512",
220
+ );
221
+ });
222
+ });
223
+
224
+ describe("actions sections", () => {
225
+ test("accepts an action the module implements", () => {
226
+ expect(() =>
227
+ validateDriver(
228
+ driverWithActions([
229
+ {
230
+ id: "say",
231
+ label: text("Say"),
232
+ fields: [
233
+ field("message"),
234
+ ],
235
+ },
236
+ ]),
237
+ ),
238
+ ).not.toThrow();
239
+ });
240
+
241
+ test("rejects an action the module does not implement", () => {
242
+ expect(() =>
243
+ validateDriver(
244
+ driverWithActions([
245
+ {
246
+ id: "shout",
247
+ label: text("Shout"),
248
+ },
249
+ ]),
250
+ ),
251
+ ).toThrow('declares action "shout", which module "broadcast" does not implement');
252
+ });
253
+
254
+ test("rejects a section with no actions", () => {
255
+ expect(() => validateDriver(driverWithActions([]))).toThrow("declares no actions");
256
+ });
257
+
258
+ test("rejects an action carrying more fields than one prompt fits", () => {
259
+ expect(() =>
260
+ validateDriver(
261
+ driverWithActions([
262
+ {
263
+ id: "say",
264
+ label: text("Say"),
265
+ fields: [
266
+ field("one"),
267
+ field("two"),
268
+ field("three"),
269
+ field("four"),
270
+ field("five"),
271
+ field("six"),
272
+ ],
273
+ },
274
+ ]),
275
+ ),
276
+ ).toThrow('action "say" declares 6 fields — at most 5 fit one prompt');
277
+ });
278
+
279
+ test("rejects a section pointing at a module that is not an actions module", () => {
280
+ const driver = driverWithActions([
281
+ {
282
+ id: "say",
283
+ label: text("Say"),
284
+ },
285
+ ]);
286
+
287
+ driver.modules = {
288
+ broadcast: {
289
+ kind: BridgeKind.Settings,
290
+ read: async () => ({}),
291
+ },
292
+ };
293
+
294
+ expect(() => validateDriver(driver)).toThrow('references "broadcast", which is not an actions module');
295
+ });
296
+
297
+ test("checks the fields a row action declares", () => {
298
+ const driver = driverWithActions([
299
+ {
300
+ id: "say",
301
+ label: text("Say"),
302
+ },
303
+ ]);
304
+
305
+ driver.panel = {
306
+ tabs: [
307
+ {
308
+ id: "players",
309
+ title: text("Players"),
310
+ sections: [
311
+ {
312
+ layout: BridgeLayout.Table,
313
+ id: "players",
314
+ module: "players",
315
+ columns: [
316
+ {
317
+ key: "name",
318
+ label: text("Name"),
319
+ },
320
+ ],
321
+ actions: [
322
+ {
323
+ id: "xp",
324
+ label: text("XP"),
325
+ fields: [
326
+ {
327
+ ...field("amount"),
328
+ visibleWhen: {
329
+ variable: "MODE",
330
+ },
331
+ },
332
+ ],
333
+ },
334
+ ],
335
+ },
336
+ ],
337
+ },
338
+ ],
339
+ };
340
+
341
+ driver.modules = {
342
+ players: {
343
+ kind: BridgeKind.Collection,
344
+ list: async () => [],
345
+ actions: {
346
+ xp: async () => {
347
+ return;
348
+ },
349
+ },
350
+ },
351
+ };
352
+
353
+ expect(() => validateDriver(driver)).toThrow('action "xp" field "amount" is visible when "MODE" matches nothing');
354
+ });
355
+ });
356
+
357
+ describe("visibleWhen", () => {
358
+ test("accepts a rule that matches on values", () => {
359
+ expect(() =>
360
+ validateDriver(
361
+ driverWith({
362
+ variable: "MODPACK",
363
+ values: [
364
+ "vanilla",
365
+ ],
366
+ }),
367
+ ),
368
+ ).not.toThrow();
369
+ });
370
+
371
+ test("accepts a rule that matches on emptiness", () => {
372
+ expect(() =>
373
+ validateDriver(
374
+ driverWith({
375
+ variable: "MODPACK",
376
+ empty: true,
377
+ }),
378
+ ),
379
+ ).not.toThrow();
380
+
381
+ expect(() =>
382
+ validateDriver(
383
+ driverWith({
384
+ variable: "MODPACK",
385
+ empty: false,
386
+ }),
387
+ ),
388
+ ).not.toThrow();
389
+ });
390
+
391
+ test("accepts a rule that narrows values inside a presence check", () => {
392
+ expect(() =>
393
+ validateDriver(
394
+ driverWith({
395
+ variable: "MODPACK",
396
+ empty: false,
397
+ values: [
398
+ "modrinth:cobblemon",
399
+ ],
400
+ }),
401
+ ),
402
+ ).not.toThrow();
403
+ });
404
+
405
+ test("rejects a rule that declares neither values nor empty", () => {
406
+ expect(() =>
407
+ validateDriver(
408
+ driverWith({
409
+ variable: "MODPACK",
410
+ }),
411
+ ),
412
+ ).toThrow('is visible when "MODPACK" matches nothing');
413
+ });
414
+
415
+ test("rejects an empty values list", () => {
416
+ expect(() =>
417
+ validateDriver(
418
+ driverWith({
419
+ variable: "MODPACK",
420
+ values: [],
421
+ }),
422
+ ),
423
+ ).toThrow('is visible when "MODPACK" matches nothing');
424
+ });
425
+
426
+ test("checks a field rule as well as a section rule", () => {
427
+ const driver = driverWith({
428
+ variable: "MODPACK",
429
+ empty: true,
430
+ });
431
+
432
+ const [section] = driver.panel?.tabs[0]?.sections ?? [];
433
+
434
+ if (!section || section.layout !== BridgeLayout.Form) {
435
+ throw new Error("fixture is missing its form section");
436
+ }
437
+
438
+ const [field] = section.fields;
439
+
440
+ if (!field) {
441
+ throw new Error("fixture is missing its field");
442
+ }
443
+
444
+ field.visibleWhen = {
445
+ variable: "SERVER_TYPE",
446
+ };
447
+
448
+ expect(() => validateDriver(driver)).toThrow('field "MC_VERSION" is visible when "SERVER_TYPE" matches nothing');
449
+ });
450
+ });