@vestra-player/sdk 1.5.30 → 1.5.33

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.
@@ -116,6 +116,16 @@ type Call = {
116
116
  * await host.settle();
117
117
  * assert.equal(picture.props["brightness"], 40);
118
118
  * ```
119
+ *
120
+ * @remarks
121
+ * A record, not a window: no Slint file is compiled and nothing is drawn.
122
+ * `props` holds what the plugin wrote, by Slint name; `emit` fires a
123
+ * callback the way the user's input would; `userClose` closes it the way
124
+ * the title-bar button would.
125
+ *
126
+ * @capability None.
127
+ * @since 1.0.0
128
+ * @category Testing
119
129
  */
120
130
  export interface FakeWindow {
121
131
  /** The `.slint` file the window was created from (package-relative). */
@@ -134,12 +144,48 @@ export interface FakeWindow {
134
144
  * Fire a Slint callback as if the user triggered it in the window. The
135
145
  * name may be kebab-case or camelCase; `args` are the callback arguments.
136
146
  * Follow it with `await host.settle()` before asserting.
147
+ *
148
+ * @capability None.
149
+ *
150
+ * @example Type into a window's search field
151
+ * The test types "cat" into the window's search box and checks the
152
+ * plugin's results. `emit` delivers the Slint callback `search-changed`
153
+ * with its argument to the plugin's `on` handler; `settle()` waits for the
154
+ * properties the handler wrote.
155
+ * ```ts
156
+ * import assert from "node:assert/strict";
157
+ * declare const host: TestHost;
158
+ * // ---cut---
159
+ * const search = host.window("search");
160
+ * search.emit("searchChanged", "cat"); // camelCase or kebab-case
161
+ * await host.settle();
162
+ * assert.ok((search.props["results"] as unknown[]).length > 0);
163
+ * ```
164
+ *
165
+ * @since 1.0.0
137
166
  */
138
167
  emit(callback: string, ...args: Json[]): void;
139
168
  /**
140
169
  * The user closes the window with its title-bar button: the window closes
141
170
  * and the plugin's `window.onClose` listeners run (unlike the plugin's own
142
171
  * `close()`).
172
+ *
173
+ * @capability None.
174
+ *
175
+ * @example Close a window the way the user does
176
+ * The test closes the plugin's window with its title-bar button and checks
177
+ * that the plugin stopped its timer. Unlike the plugin's own `close()`,
178
+ * `userClose` runs the plugin's `onClose` listeners.
179
+ * ```ts
180
+ * import assert from "node:assert/strict";
181
+ * declare const host: TestHost;
182
+ * // ---cut---
183
+ * host.window("clock").userClose();
184
+ * await host.settle();
185
+ * assert.equal(host.window("clock").visible, false);
186
+ * ```
187
+ *
188
+ * @since 1.0.0
143
189
  */
144
190
  userClose(): void;
145
191
  }
@@ -147,6 +193,8 @@ export interface FakeWindow {
147
193
  * A file dialog as the player shows it ({@link TestHost.fileDialogs}).
148
194
  *
149
195
  * @since 1.5.11
196
+ *
197
+ * @category Testing
150
198
  */
151
199
  export interface FileDialogText {
152
200
  /** Which dialog. */
@@ -177,6 +225,16 @@ export interface FileDialogText {
177
225
  * await toggle.run();
178
226
  * assert.equal(host.command("toggle-loop").state.checked, true);
179
227
  * ```
228
+ *
229
+ * @remarks
230
+ * The host's record of one command: what the plugin declared, the state
231
+ * it last sent, and how the menu shows it in {@link TestHost.locale}.
232
+ * Read it again after `settle()` to see changes; `run` executes it as the
233
+ * menu would.
234
+ *
235
+ * @capability None.
236
+ * @since 1.0.0
237
+ * @category Testing
180
238
  */
181
239
  export interface FakeCommand {
182
240
  /**
@@ -227,6 +285,23 @@ export interface FakeCommand {
227
285
  * Execute the command the way the menu would, then wait until the host is
228
286
  * idle. Rejects (with a {@link HostError}) when the command is disabled or
229
287
  * busy, like the player, which does not call `run` then.
288
+ *
289
+ * @capability None.
290
+ * @throws `command` (rejects) A `HostError`: the command is disabled or
291
+ * busy (the message says which).
292
+ *
293
+ * @example Check that a disabled item refuses to run
294
+ * The test checks that Export cannot run while nothing is open. Like the
295
+ * player, the host does not call `run` for a disabled or busy command;
296
+ * the promise rejects with a `HostError`.
297
+ * ```ts
298
+ * import assert from "node:assert/strict";
299
+ * declare const host: TestHost;
300
+ * // ---cut---
301
+ * await assert.rejects(host.command("export").run());
302
+ * ```
303
+ *
304
+ * @since 1.0.0
230
305
  */
231
306
  run(): Promise<void>;
232
307
  }
@@ -235,6 +310,29 @@ export interface FakeCommand {
235
310
  * it: an entry of {@link TestHost.barButtons}.
236
311
  *
237
312
  * @since 1.5.29
313
+ *
314
+ * @remarks
315
+ * A bottom-bar button as the player would show it, derived from the
316
+ * command's declaration and state each time it is read: clickable while
317
+ * the command is enabled and not busy, accent-coloured while checked, with
318
+ * the command's title (and reason) as the tooltip.
319
+ *
320
+ * @capability None.
321
+ *
322
+ * @example Check a bar button's tooltip while it is disabled
323
+ * The test checks that the Export button shows why it cannot be clicked.
324
+ * The tooltip is the command's title; while the command is disabled or
325
+ * busy and has a non-empty `reason`, the reason follows on a second line.
326
+ * ```ts
327
+ * import assert from "node:assert/strict";
328
+ * declare const host: TestHost;
329
+ * // ---cut---
330
+ * const [button] = host.barButtons;
331
+ * assert.equal(button?.enabled, false);
332
+ * assert.match(button?.tooltip ?? "", /\n/); // the command declares a reason
333
+ * ```
334
+ *
335
+ * @category Testing
238
336
  */
239
337
  export interface FakeBarButton {
240
338
  /** The command's local id. */
@@ -258,6 +356,24 @@ export interface FakeBarButton {
258
356
  * Click the button, then wait until the host is idle: runs the command
259
357
  * as its menu item would. Rejects (with a {@link HostError}) while the
260
358
  * button is disabled, and once the command is gone.
359
+ *
360
+ * @capability None.
361
+ * @throws `command` (rejects) A `HostError`: the command is disabled or
362
+ * busy.
363
+ * @throws `missing` (rejects) A `HostError`: the command is gone.
364
+ *
365
+ * @example Click the button and wait for its effect
366
+ * The test clicks the plugin's button and checks the OSD. `click` runs the
367
+ * command like its menu item and resolves once the host is idle.
368
+ * ```ts
369
+ * import assert from "node:assert/strict";
370
+ * declare const host: TestHost;
371
+ * // ---cut---
372
+ * await host.barButtons[0]?.click();
373
+ * assert.ok(host.osd.length > 0);
374
+ * ```
375
+ *
376
+ * @since 1.5.29
261
377
  */
262
378
  click(): Promise<void>;
263
379
  }
@@ -265,6 +381,31 @@ export interface FakeBarButton {
265
381
  * A generated stream (`plugin.player.openStream`) as the test host sees it:
266
382
  * the fake player end of the frame connection. Assertion surface; see
267
383
  * {@link TestHost.producers}.
384
+ *
385
+ * @remarks
386
+ * The player's end of a generated stream's frame connection
387
+ * ({@link TestHost.producers}): how many frames arrived and whether the
388
+ * plugin released it or the player retired it. Tests assert on it; nothing
389
+ * is played.
390
+ *
391
+ * @capability None.
392
+ *
393
+ * @example Check that the stream delivers frames
394
+ * The test starts the plugin's generator and checks that video and audio
395
+ * arrive. The counters grow as the SDK pulls frames and sends them over
396
+ * the fake connection.
397
+ * ```ts
398
+ * import assert from "node:assert/strict";
399
+ * declare const host: TestHost;
400
+ * // ---cut---
401
+ * await host.command("start-generator").run();
402
+ * await host.settle(200); // let a few frames through
403
+ * const [producer] = host.producers.values();
404
+ * assert.ok((producer?.videoFrames ?? 0) > 0 && (producer?.receivedFrames ?? 0) > 0);
405
+ * ```
406
+ *
407
+ * @since 1.0.0
408
+ * @category Testing
268
409
  */
269
410
  export interface FakeProducer {
270
411
  /** The producer id the host assigned (`1`, `2`, … per host). */
@@ -289,7 +430,12 @@ export interface FakeProducer {
289
430
  */
290
431
  droppedWhileSelected: boolean;
291
432
  }
292
- /** A Markdown document as {@link TestHost.markdown} reads it back. */
433
+ /**
434
+ * A Markdown document as {@link TestHost.markdown} reads it back.
435
+ *
436
+ * @since 1.1.0
437
+ * @category Testing
438
+ */
293
439
  export interface MarkdownWire {
294
440
  /** The Markdown text. */
295
441
  text: string;
@@ -362,6 +508,19 @@ export interface MarkdownWire {
362
508
  * await stop();
363
509
  * });
364
510
  * ```
511
+ *
512
+ * @remarks
513
+ * An in-memory player that speaks the real wire protocol, so the SDK's own
514
+ * logic runs as it does in the player; only the player side is simulated
515
+ * (no decoding, no Slint, no windows). Make one per test, preset it if the
516
+ * plugin reads something during activation, start the plugin with
517
+ * {@link startPlugin}, drive it, `settle()`, and assert on what it
518
+ * recorded.
519
+ *
520
+ * @capability None.
521
+ * @see [Test a plugin](../../docs/how-to/testing.md)
522
+ * @since 1.0.0
523
+ * @category Testing
365
524
  */
366
525
  export declare class TestHost implements Transport {
367
526
  #private;
@@ -479,6 +638,20 @@ export declare class TestHost implements Transport {
479
638
  *
480
639
  * @param directory - The package directory (holding `locales/`).
481
640
  * @since 1.5.9
641
+ *
642
+ * @capability None.
643
+ *
644
+ * @example Load the translations after changing the language
645
+ * The test switches the host to German before starting the plugin and
646
+ * loads that catalog itself (`startPlugin` does the same). Texts are then
647
+ * shown in German.
648
+ * ```ts
649
+ * declare const host: TestHost;
650
+ * declare const directory: string;
651
+ * // ---cut---
652
+ * host.locale = "de";
653
+ * host.loadCatalog(directory); // locales/de.po (or de.json)
654
+ * ```
482
655
  */
483
656
  loadCatalog(directory: string): void;
484
657
  /**
@@ -502,6 +675,8 @@ export declare class TestHost implements Transport {
502
675
  * ```
503
676
  *
504
677
  * @since 1.5.9
678
+ *
679
+ * @capability None.
505
680
  */
506
681
  translate(text: TextValue, args?: TemplateArgs): string;
507
682
  /**
@@ -517,6 +692,24 @@ export declare class TestHost implements Transport {
517
692
  *
518
693
  * @param language - `"system"` or a locale.
519
694
  * @since 1.5.9
695
+ *
696
+ * @capability None.
697
+ *
698
+ * @example Simulate the user choosing French for the next start
699
+ * The plugin hears that the saved interface language changed. The host
700
+ * pushes `appearance.changed`, which a plugin with `events` sees in
701
+ * `appearance.language` once it has read it and subscribed; this run's
702
+ * language does not change.
703
+ * ```ts
704
+ * import assert from "node:assert/strict";
705
+ * declare const host: TestHost;
706
+ * // ---cut---
707
+ * void plugin.appearance.language; // read once: the SDK then follows it
708
+ * plugin.appearance.onChange(() => {}); // subscribes to appearance events
709
+ * host.setLanguage("fr");
710
+ * await host.settle();
711
+ * assert.equal(plugin.appearance.language, "fr");
712
+ * ```
520
713
  */
521
714
  setLanguage(language: LanguagePreference): void;
522
715
  /**
@@ -726,6 +919,9 @@ export declare class TestHost implements Transport {
726
919
  * await host.settle();
727
920
  * assert.equal(plugin.timeline.marks.length, 2);
728
921
  * ```
922
+ *
923
+ * @capability None.
924
+ * @since 1.0.0
729
925
  */
730
926
  setTimelineLayer(pluginId: string, marks: {
731
927
  time: number;
@@ -766,6 +962,9 @@ export declare class TestHost implements Transport {
766
962
  * join(plugin.dataDirectory, "cover.png"),
767
963
  * );
768
964
  * ```
965
+ *
966
+ * @capability None.
967
+ * @since 1.0.0
769
968
  */
770
969
  imagePath(value: Json | undefined): string | null;
771
970
  /** `drops.provide` registrations of this plugin, in order (`id` is the SDK's). */
@@ -806,6 +1005,10 @@ export declare class TestHost implements Transport {
806
1005
  * modifiers: { alt: true },
807
1006
  * });
808
1007
  * ```
1008
+ *
1009
+ * @capability None.
1010
+ * @throws Never: files nobody takes are ignored, as in the player.
1011
+ * @since 1.0.0
809
1012
  */
810
1013
  drop(paths: string[], options?: {
811
1014
  target?: "video" | "panel";
@@ -824,6 +1027,24 @@ export declare class TestHost implements Transport {
824
1027
  /**
825
1028
  * Simulate another plugin switching the palette: updates
826
1029
  * {@link TestHost.appearance} and pushes `appearance.changed`.
1030
+ *
1031
+ * @capability None.
1032
+ *
1033
+ * @example Simulate another plugin switching to the light palette
1034
+ * The plugin hears the palette change. The host updates `appearance` and
1035
+ * pushes `appearance.changed` to a plugin that listens with `events`.
1036
+ * ```ts
1037
+ * import assert from "node:assert/strict";
1038
+ * declare const host: TestHost;
1039
+ * // ---cut---
1040
+ * const themes: ThemeId[] = [];
1041
+ * plugin.appearance.onChange((theme) => themes.push(theme)); // subscribes (`events`)
1042
+ * host.setAppearance("vestra-light");
1043
+ * await host.settle();
1044
+ * assert.deepEqual(themes, ["vestra-light"]);
1045
+ * ```
1046
+ *
1047
+ * @since 1.0.0
827
1048
  */
828
1049
  setAppearance(theme: ThemeId): void;
829
1050
  /**
@@ -874,6 +1095,9 @@ export declare class TestHost implements Transport {
874
1095
  * await host.settle();
875
1096
  * assert.equal(page.props["step"], 10);
876
1097
  * ```
1098
+ *
1099
+ * @capability None.
1100
+ * @since 1.0.0
877
1101
  */
878
1102
  settingsPage(id: string): {
879
1103
  /** The page's settings category. */
@@ -903,6 +1127,23 @@ export declare class TestHost implements Transport {
903
1127
  /**
904
1128
  * Simulate the backdrop's `effective` changing (`false`: a video frame
905
1129
  * appeared; `true`: there is none again); pushed to the plugin.
1130
+ *
1131
+ * @capability None.
1132
+ *
1133
+ * @example Make a video picture replace the backdrop
1134
+ * The plugin's backdrop hears that a video frame now covers it. `false`
1135
+ * means a picture appeared; the change is pushed like the player's
1136
+ * `window.state`.
1137
+ * ```ts
1138
+ * import assert from "node:assert/strict";
1139
+ * declare const host: TestHost;
1140
+ * // ---cut---
1141
+ * host.setBackdropEffective(false);
1142
+ * await host.settle();
1143
+ * assert.equal(plugin.panels.backdrop.state?.effective, false);
1144
+ * ```
1145
+ *
1146
+ * @since 1.0.0
906
1147
  */
907
1148
  setBackdropEffective(effective: boolean): void;
908
1149
  /**
@@ -935,6 +1176,8 @@ export declare class TestHost implements Transport {
935
1176
  * ```
936
1177
  *
937
1178
  * @since 1.5.2
1179
+ *
1180
+ * @capability None.
938
1181
  */
939
1182
  mountOther(slot: "bottom" | "sidebar" | "backdrop", plugin: string, options?: {
940
1183
  title?: string;
@@ -944,6 +1187,21 @@ export declare class TestHost implements Transport {
944
1187
  * when its tab was current, the next tab (else the previous one) is.
945
1188
  *
946
1189
  * @since 1.5.2
1190
+ *
1191
+ * @capability None.
1192
+ *
1193
+ * @example Let the playlist's tab go away
1194
+ * The other plugin's sidebar tab disappears and the plugin's own tab
1195
+ * becomes current. When the removed tab was current, the next tab (else
1196
+ * the previous one) becomes current, as in the player.
1197
+ * ```ts
1198
+ * import assert from "node:assert/strict";
1199
+ * declare const host: TestHost;
1200
+ * // ---cut---
1201
+ * host.unmountOther("vestra.playlist");
1202
+ * await host.settle();
1203
+ * assert.equal(host.layout.sidebar?.panels?.length, 1);
1204
+ * ```
947
1205
  */
948
1206
  unmountOther(plugin: string): void;
949
1207
  /**
@@ -952,6 +1210,20 @@ export declare class TestHost implements Transport {
952
1210
  * becomes the current tab. Pushed as a `window.state` change.
953
1211
  *
954
1212
  * @since 1.5.2
1213
+ *
1214
+ * @capability None.
1215
+ *
1216
+ * @example Click another plugin's sidebar tab
1217
+ * The user switches the sidebar to the playlist; the plugin's panel is in
1218
+ * a background tab now. The change is pushed as `window.state`.
1219
+ * ```ts
1220
+ * import assert from "node:assert/strict";
1221
+ * declare const host: TestHost;
1222
+ * // ---cut---
1223
+ * host.selectTab("vestra.playlist");
1224
+ * await host.settle();
1225
+ * assert.notEqual(host.layout.sidebar?.owner, 1); // the plugin under test is owner 1
1226
+ * ```
955
1227
  */
956
1228
  selectTab(plugin: string): void;
957
1229
  /**
@@ -968,6 +1240,24 @@ export declare class TestHost implements Transport {
968
1240
  * Simulate another plugin taking the display layer first (the plugin's
969
1241
  * `video.view()` then fails with `conflict`), or, with `null`, releasing
970
1242
  * it (values reset to the defaults).
1243
+ *
1244
+ * @capability None.
1245
+ *
1246
+ * @example Let another plugin own the display layer
1247
+ * The plugin's `video.view()` fails with `conflict` because another plugin
1248
+ * took the display layer first. `null` releases it again.
1249
+ * ```ts
1250
+ * import assert from "node:assert/strict";
1251
+ * declare const host: TestHost;
1252
+ * // ---cut---
1253
+ * host.setVideoViewOwner("someone.zoom");
1254
+ * const view = plugin.video.view();
1255
+ * view.set({ zoom: 2 });
1256
+ * await host.settle();
1257
+ * assert.equal(view.status.state, "failed");
1258
+ * ```
1259
+ *
1260
+ * @since 1.0.0
971
1261
  */
972
1262
  setVideoViewOwner(pluginId: string | null): void;
973
1263
  /**
@@ -987,6 +1277,24 @@ export declare class TestHost implements Transport {
987
1277
  * Simulate another plugin owning the render options (the plugin's
988
1278
  * `video.render()` then fails with `conflict`), or, with `null`, releasing
989
1279
  * them (values reset to the defaults).
1280
+ *
1281
+ * @capability None.
1282
+ *
1283
+ * @example Let another plugin own the render options
1284
+ * The plugin's render options report `conflict` while another plugin owns
1285
+ * them; `null` releases them and resets the values to the defaults.
1286
+ * ```ts
1287
+ * import assert from "node:assert/strict";
1288
+ * declare const host: TestHost;
1289
+ * // ---cut---
1290
+ * host.setVideoRenderOwner("someone.hdr");
1291
+ * const render = plugin.video.render();
1292
+ * render.set({ toneMapping: "bt.2390" });
1293
+ * await host.settle();
1294
+ * assert.equal(render.status.state, "failed");
1295
+ * ```
1296
+ *
1297
+ * @since 1.0.0
990
1298
  */
991
1299
  setVideoRenderOwner(pluginId: string | null): void;
992
1300
  /**
@@ -1010,6 +1318,9 @@ export declare class TestHost implements Transport {
1010
1318
  * await host.settle();
1011
1319
  * assert.equal(render.status.state, "failed");
1012
1320
  * ```
1321
+ *
1322
+ * @capability None.
1323
+ * @since 1.0.0
1013
1324
  */
1014
1325
  failVideoRender(code: "failed" | "unsupported", message: string): void;
1015
1326
  /**
@@ -1145,6 +1456,21 @@ export declare class TestHost implements Transport {
1145
1456
  * What `plugin.resolvers.fileTypes` reports: the extensions of this
1146
1457
  * plugin's file resolvers and of {@link TestHost.otherResolvers}, sorted,
1147
1458
  * each once.
1459
+ *
1460
+ * @capability None.
1461
+ *
1462
+ * @example Check the file types a resolver adds
1463
+ * The test checks the list the plugin will read as
1464
+ * `resolvers.fileTypes`: the extensions of its own file resolvers and of
1465
+ * `otherResolvers`, sorted, each once.
1466
+ * ```ts
1467
+ * import assert from "node:assert/strict";
1468
+ * declare const host: TestHost;
1469
+ * // ---cut---
1470
+ * assert.deepEqual(host.fileTypes(), ["ch8"]);
1471
+ * ```
1472
+ *
1473
+ * @since 1.5.8
1148
1474
  */
1149
1475
  fileTypes(): string[];
1150
1476
  /**
@@ -1167,6 +1493,9 @@ export declare class TestHost implements Transport {
1167
1493
  * await host.settle();
1168
1494
  * assert.deepEqual(plugin.resolvers.fileTypes, ["ch8"]);
1169
1495
  * ```
1496
+ *
1497
+ * @capability None.
1498
+ * @since 1.5.8
1170
1499
  */
1171
1500
  updateFileTypes(): void;
1172
1501
  /**
@@ -1305,6 +1634,11 @@ export declare class TestHost implements Transport {
1305
1634
  * `"test.plugin"` by default).
1306
1635
  */
1307
1636
  manifestId: string;
1637
+ /**
1638
+ * A fresh in-memory player: nothing open, no plugin, the player's core
1639
+ * actions registered. Preset what the plugin reads during activation, then
1640
+ * pass it to {@link startPlugin} as `host`.
1641
+ */
1308
1642
  constructor();
1309
1643
  /** The `player.setStepSeek` value in effect (default 5). */
1310
1644
  stepSeek: number;
@@ -1373,8 +1707,10 @@ export declare class TestHost implements Transport {
1373
1707
  /**
1374
1708
  * One of the 14 core actions (`play-pause`, `stop`, `seek-forward`, ...)
1375
1709
  * that the test host registers like the player, as `host/<id>`. Throws for
1376
- * an unknown id. `run()` executes it with the player's checks and writes
1377
- * its on-screen text to {@link TestHost.osd}.
1710
+ * an unknown id. `run()` executes it like its menu item: a disabled
1711
+ * action rejects with `command`, as in the player (`plugin.host.run`
1712
+ * rejects with `unavailable` instead); otherwise it runs with the
1713
+ * player's checks and writes its on-screen text to {@link TestHost.osd}.
1378
1714
  *
1379
1715
  * @example Pause through the player's own action
1380
1716
  * The test pauses playback the way the Space key does and checks that the
@@ -1390,6 +1726,9 @@ export declare class TestHost implements Transport {
1390
1726
  * assert.equal(host.source.paused, true);
1391
1727
  * assert.deepEqual(host.coreRuns, ["play-pause"]);
1392
1728
  * ```
1729
+ *
1730
+ * @capability None.
1731
+ * @since 1.0.0
1393
1732
  */
1394
1733
  coreAction(id: string): FakeCommand;
1395
1734
  /**
@@ -1431,9 +1770,34 @@ export declare class TestHost implements Transport {
1431
1770
  * assert.deepEqual(host.osd, ["Folder not found"]);
1432
1771
  * host.reset("system.openPath");
1433
1772
  * ```
1773
+ *
1774
+ * @capability None.
1775
+ * @since 1.0.0
1434
1776
  */
1435
1777
  handle(method: string, handler: Handler): void;
1436
- /** Restore the default behaviour of a method replaced with {@link TestHost.handle}. */
1778
+ /**
1779
+ * Restore the default behaviour of a method replaced with {@link TestHost.handle}.
1780
+ *
1781
+ * @capability None.
1782
+ *
1783
+ * @example Restore the default answer after a simulated failure
1784
+ * The first write fails, the second succeeds. `reset` removes the handler
1785
+ * that `handle` installed, so the method is answered by the test host
1786
+ * again.
1787
+ * ```ts
1788
+ * import assert from "node:assert/strict";
1789
+ * declare const host: TestHost;
1790
+ * // ---cut---
1791
+ * host.handle("clipboard.writeText", () => {
1792
+ * throw new HostError("failed", "denied");
1793
+ * });
1794
+ * await assert.rejects(plugin.clipboard.writeText("x"));
1795
+ * host.reset("clipboard.writeText");
1796
+ * await plugin.clipboard.writeText("x"); // the default handler again
1797
+ * ```
1798
+ *
1799
+ * @since 1.0.0
1800
+ */
1437
1801
  reset(method: string): void;
1438
1802
  /**
1439
1803
  * Deliver a host event to the plugin. Like the player, subscription-based
@@ -1444,6 +1808,22 @@ export declare class TestHost implements Transport {
1444
1808
  *
1445
1809
  * @param event - The wire event name.
1446
1810
  * @param data - The event payload (wire-level shape).
1811
+ *
1812
+ * @capability None.
1813
+ *
1814
+ * @example Deliver a raw event the helpers do not cover
1815
+ * The test pushes a `window.state` event after changing `layout` by hand.
1816
+ * Like the player, the host delivers it only when the plugin subscribed;
1817
+ * most tests use `setSource`, `setMainWindow` and the other helpers
1818
+ * instead.
1819
+ * ```ts
1820
+ * declare const host: TestHost;
1821
+ * // ---cut---
1822
+ * host.emit("window.state", {}); // no facts: the SDK reads window.state again
1823
+ * await host.settle();
1824
+ * ```
1825
+ *
1826
+ * @since 1.0.0
1447
1827
  */
1448
1828
  emit(event: string, data: unknown): void;
1449
1829
  /**
@@ -1471,6 +1851,9 @@ export declare class TestHost implements Transport {
1471
1851
  * host.setSource({ phase: "ended", terminalReason: "natural" });
1472
1852
  * await host.settle();
1473
1853
  * ```
1854
+ *
1855
+ * @capability None.
1856
+ * @since 1.0.0
1474
1857
  */
1475
1858
  setSource(patch: Partial<SourceState>): void;
1476
1859
  /**
@@ -1495,6 +1878,9 @@ export declare class TestHost implements Transport {
1495
1878
  * assert.equal(plugin.window.state.fullscreen, true);
1496
1879
  * host.setMainWindow({ pictureInPicture: { supported: true, active: false } });
1497
1880
  * ```
1881
+ *
1882
+ * @capability None.
1883
+ * @since 1.0.0
1498
1884
  */
1499
1885
  setMainWindow(patch: Partial<{
1500
1886
  fullscreen: boolean;
@@ -1511,6 +1897,22 @@ export declare class TestHost implements Transport {
1511
1897
  * The pointer reached the right edge in fullscreen (`true`) or left the
1512
1898
  * popped-out sidebar (`false`): the attached sidebar pops out or collapses
1513
1899
  * (reference §6.3). No effect outside fullscreen, when hidden or floating.
1900
+ *
1901
+ * @capability None.
1902
+ *
1903
+ * @example Pop the sidebar out in fullscreen
1904
+ * The pointer reaches the right edge in fullscreen and the attached
1905
+ * sidebar pops out over the video. Outside fullscreen, or with the
1906
+ * sidebar hidden or floating, nothing happens.
1907
+ * ```ts
1908
+ * declare const host: TestHost;
1909
+ * // ---cut---
1910
+ * host.setMainWindow({ fullscreen: true });
1911
+ * host.popSidebar(true);
1912
+ * await host.settle();
1913
+ * ```
1914
+ *
1915
+ * @since 1.0.0
1514
1916
  */
1515
1917
  popSidebar(popped: boolean): void;
1516
1918
  /**
@@ -1536,6 +1938,9 @@ export declare class TestHost implements Transport {
1536
1938
  * await host.settle();
1537
1939
  * assert.equal(plugin.player.isCurrent(before), false);
1538
1940
  * ```
1941
+ *
1942
+ * @capability None.
1943
+ * @since 1.0.0
1539
1944
  */
1540
1945
  openElsewhere(source: string, phase?: SourceState["phase"]): void;
1541
1946
  /**
@@ -1558,6 +1963,9 @@ export declare class TestHost implements Transport {
1558
1963
  * const [producer] = host.producers.values();
1559
1964
  * host.failStream(producer!.id, "producer transport closed");
1560
1965
  * ```
1966
+ *
1967
+ * @capability None.
1968
+ * @since 1.5.7
1561
1969
  */
1562
1970
  failStream(producerId: number, message: string): void;
1563
1971
  /**
@@ -1567,11 +1975,12 @@ export declare class TestHost implements Transport {
1567
1975
  * the conflict rule orders them. A disabled command lets the key pass to
1568
1976
  * the next command bound to it (SDK 1.5.12, as player 0.4.3): the one it
1569
1977
  * shadowed, then the global scope. The first enabled one runs; `press`
1570
- * resolves after it ran (`"ran"`), or rejects when it is busy. When only
1571
- * disabled commands hold the key, the first one with a `reason` shows the
1572
- * reason on the OSD and eats the key (`"reason"`, see `host.osd`);
1573
- * without a reason the key goes on to the focused control (`"passed"`,
1574
- * nothing runs). Rejects when nothing is bound. Bindings use the
1978
+ * resolves after it ran (`"ran"`). When only disabled commands hold the
1979
+ * key, the first of them decides, as in the player: with a `reason` it
1980
+ * shows the reason on the OSD and eats the key (`"reason"`, see
1981
+ * `host.osd`); without one the key goes on to the focused control
1982
+ * (`"passed"`, nothing runs). Unlike the player, which only logs a key
1983
+ * that reaches a busy command, `press` then rejects, so a test notices. Rejects when nothing is bound. Bindings use the
1575
1984
  * reference syntax (`"Space"`, `"Ctrl+O"`, `"Mouse:WheelUp"`), and user
1576
1985
  * overrides in `bindings.overlay` apply.
1577
1986
  *
@@ -1588,6 +1997,12 @@ export declare class TestHost implements Transport {
1588
1997
  * await host.press("Ctrl+B"); // a global binding
1589
1998
  * await host.press("Delete", { panel: true }); // while the panel has focus
1590
1999
  * ```
2000
+ *
2001
+ * @capability None.
2002
+ * @throws `command` (rejects) A `HostError`: the command that would run is
2003
+ * busy (the player only logs this).
2004
+ * @throws `Error` (rejects) Nothing is bound to the key.
2005
+ * @since 1.0.0
1591
2006
  */
1592
2007
  press(binding: string, options?: {
1593
2008
  panel?: boolean;
@@ -1705,6 +2120,9 @@ export declare class TestHost implements Transport {
1705
2120
  * const rows = host.window("chat").props["messages"] as { body: Json }[];
1706
2121
  * assert.equal(host.markdown(rows[0]!.body)?.text, "**Hello**");
1707
2122
  * ```
2123
+ *
2124
+ * @capability None.
2125
+ * @since 1.1.0
1708
2126
  */
1709
2127
  markdown(value: Json | undefined): MarkdownWire | null;
1710
2128
  /**
@@ -1720,17 +2138,68 @@ export declare class TestHost implements Transport {
1720
2138
  /**
1721
2139
  * Replace the plugin list and push `packages.changed` (another plugin
1722
2140
  * started, stopped, was installed or removed).
2141
+ *
2142
+ * @capability None.
2143
+ *
2144
+ * @example Simulate another plugin starting
2145
+ * The plugin's store window hears that a plugin is running now. The list
2146
+ * replaces `packages` and is pushed as `packages.changed`, which the
2147
+ * plugin's `packages.onChange` listeners receive.
2148
+ * ```ts
2149
+ * declare const host: TestHost;
2150
+ * // ---cut---
2151
+ * host.setPackages([...host.packages]); // edit a copy, then push it
2152
+ * await host.settle();
2153
+ * ```
2154
+ *
2155
+ * @since 1.0.0
1723
2156
  */
1724
2157
  setPackages(list: TestHost["packages"]): void;
1725
2158
  /**
1726
2159
  * Simulate another plugin calling `player.next()` / `previous()`: the host
1727
2160
  * asks this plugin's sequence provider. Rejects with `unavailable` when
1728
2161
  * there is no provider or its flag is false.
2162
+ *
2163
+ * @capability None.
2164
+ * @throws `unavailable` (rejects) A `HostError`: there is no sequence
2165
+ * provider, or its flag is false.
2166
+ *
2167
+ * @example Ask the plugin's provider for the next item
2168
+ * Another plugin calls `player.next()`; the host asks the plugin under
2169
+ * test, which is the sequence provider, and the test checks what it
2170
+ * opened.
2171
+ * ```ts
2172
+ * import assert from "node:assert/strict";
2173
+ * declare const host: TestHost;
2174
+ * // ---cut---
2175
+ * await host.step("next");
2176
+ * await host.settle();
2177
+ * assert.equal(host.source.source, "/media/b.mp4");
2178
+ * ```
2179
+ *
2180
+ * @since 1.0.0
1729
2181
  */
1730
2182
  step(direction: "next" | "previous"): Promise<unknown>;
1731
2183
  /**
1732
2184
  * The plugin's command with this local id. Throws when it is not
1733
2185
  * registered (never added, or disposed).
2186
+ *
2187
+ * @capability None.
2188
+ *
2189
+ * @example Read a command's state after a change
2190
+ * The test checks that Export becomes enabled once a file is ready. The
2191
+ * record is updated as the plugin sends new state; read it after
2192
+ * `settle()`.
2193
+ * ```ts
2194
+ * import assert from "node:assert/strict";
2195
+ * declare const host: TestHost;
2196
+ * // ---cut---
2197
+ * host.setSource({ phase: "ready", duration: 60 });
2198
+ * await host.settle();
2199
+ * assert.equal(host.command("export").state.enabled, true); // throws if not registered
2200
+ * ```
2201
+ *
2202
+ * @since 1.0.0
1734
2203
  */
1735
2204
  command(id: string): FakeCommand;
1736
2205
  /**
@@ -1777,6 +2246,9 @@ export declare class TestHost implements Transport {
1777
2246
  * await host.settle();
1778
2247
  * assert.equal(host.window("picture").props["brightness"], 0);
1779
2248
  * ```
2249
+ *
2250
+ * @capability None.
2251
+ * @since 1.0.0
1780
2252
  */
1781
2253
  window(id: string): FakeWindow;
1782
2254
  /**
@@ -1797,6 +2269,9 @@ export declare class TestHost implements Transport {
1797
2269
  * panel.emit("select", 2);
1798
2270
  * await host.settle();
1799
2271
  * ```
2272
+ *
2273
+ * @capability None.
2274
+ * @since 1.0.0
1800
2275
  */
1801
2276
  mounted(): Omit<FakeWindow, "callbacks" | "userClose"> & {
1802
2277
  /** `"bottom"`, `"sidebar"` or `"backdrop"`. */
@@ -1819,6 +2294,9 @@ export declare class TestHost implements Transport {
1819
2294
  * // ---cut---
1820
2295
  * assert.deepEqual(host.sent("osd.show").at(-1), { text: "Saved" });
1821
2296
  * ```
2297
+ *
2298
+ * @capability None.
2299
+ * @since 1.0.0
1822
2300
  */
1823
2301
  sent(method: string): Record<string, Json>[];
1824
2302
  /**
@@ -1864,11 +2342,32 @@ export declare class TestHost implements Transport {
1864
2342
  * await host.settle(20);
1865
2343
  * assert.equal(window.props["status-text"], expected);
1866
2344
  * ```
2345
+ *
2346
+ * @capability None.
2347
+ * @throws Never.
2348
+ * @since 1.0.0
1867
2349
  */
1868
2350
  settle(ms?: number): Promise<void>;
1869
2351
  /**
1870
2352
  * Close the fake stream servers and delete the host's temporary
1871
2353
  * directories. `stop()` from {@link startPlugin} calls it.
2354
+ *
2355
+ * @capability None.
2356
+ *
2357
+ * @example Clean up a host that was never started
2358
+ * A test that made a host but failed before `startPlugin` removes the
2359
+ * host's temporary directories itself. `stop()` from `startPlugin` calls
2360
+ * `close` for you otherwise.
2361
+ * ```ts
2362
+ * const host = new TestHost();
2363
+ * try {
2364
+ * // … preset the host; the test fails here …
2365
+ * } finally {
2366
+ * host.close(); // deletes the host's temporary data
2367
+ * }
2368
+ * ```
2369
+ *
2370
+ * @since 1.0.0
1872
2371
  */
1873
2372
  close(): void;
1874
2373
  }
@@ -1934,6 +2433,16 @@ export declare class TestHost implements Transport {
1934
2433
  * }
1935
2434
  * });
1936
2435
  * ```
2436
+ *
2437
+ * @remarks
2438
+ * The way into a plugin's tests: one call starts the plugin the way the
2439
+ * player's runner does and returns the host to drive, the `plugin` object
2440
+ * to call directly, and `stop` to end it like the player.
2441
+ *
2442
+ * @capability None.
2443
+ * @see [Test a plugin](../../docs/how-to/testing.md)
2444
+ * @since 1.0.0
2445
+ * @category Testing
1937
2446
  */
1938
2447
  export declare function startPlugin(activate: (plugin: Plugin) => unknown, options?: {
1939
2448
  /**