@fairfox/polly 0.86.0 → 0.88.0

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.
@@ -203,23 +203,27 @@
203
203
  @layer polly-components {
204
204
  .root_-n6UrQ {
205
205
  box-sizing: border-box;
206
- padding: var(--polly-space-sm) var(--polly-space-md);
206
+ min-block-size: var(--polly-control-height-md);
207
+ padding-block-start: 0;
208
+ padding-block-end: 0;
209
+ padding-inline: var(--polly-field-padding);
207
210
  font: inherit;
208
211
  font-family: var(--polly-font-sans);
209
212
  color: var(--polly-text);
210
213
  line-height: var(--polly-line-height-base);
211
214
  border: var(--polly-border-width-default) solid transparent;
212
- border-radius: var(--polly-radius-md);
213
- min-block-size: calc(1.5em + 2 * var(--polly-space-sm) + 2 * var(--polly-border-width-default));
215
+ border-radius: var(--polly-control-radius);
214
216
  inline-size: 100%;
215
217
  }
216
218
 
217
219
  .view_-n6UrQ {
220
+ display: flex;
218
221
  cursor: text;
219
222
  white-space: pre-wrap;
220
223
  word-break: break-word;
221
224
  transition: background var(--polly-motion-fast) ease, border-color var(--polly-motion-fast) ease;
222
225
  background: none;
226
+ align-items: center;
223
227
  }
224
228
 
225
229
  .view_-n6UrQ:hover {
@@ -238,7 +242,9 @@
238
242
  }
239
243
 
240
244
  .root_-n6UrQ[data-variant="multi"] {
245
+ display: block;
241
246
  field-sizing: content;
247
+ padding-block: calc((var(--polly-control-height-md) - 1.5em - 2 * var(--polly-border-width-default)) / 2);
242
248
  resize: vertical;
243
249
  max-block-size: 40em;
244
250
  }
@@ -252,7 +258,8 @@
252
258
  }
253
259
 
254
260
  .trigger_HX48zA {
255
- display: inline-block;
261
+ display: inline-flex;
262
+ align-items: center;
256
263
  }
257
264
 
258
265
  .menu_HX48zA {
@@ -349,10 +356,13 @@
349
356
  display: inline-flex;
350
357
  align-items: center;
351
358
  gap: var(--polly-space-sm);
359
+ min-block-size: var(--polly-control-height-md);
352
360
  max-width: min(100%, var(--polly-control-max-width));
353
- padding: var(--polly-space-sm) var(--polly-space-md);
361
+ padding-block-start: 0;
362
+ padding-block-end: 0;
363
+ padding-inline: var(--polly-field-padding);
354
364
  border: var(--polly-border-width-default) solid var(--polly-border);
355
- border-radius: var(--polly-radius-sm);
365
+ border-radius: var(--polly-control-radius);
356
366
  background-color: var(--polly-surface);
357
367
  font-family: inherit;
358
368
  font-size: var(--polly-text-md);
@@ -406,7 +416,7 @@
406
416
  }
407
417
 
408
418
  .actionBtn_daofbw {
409
- display: inline-block;
419
+ display: block;
410
420
  padding: var(--polly-space-xs) var(--polly-space-sm);
411
421
  border-radius: var(--polly-radius-sm);
412
422
  font-family: inherit;
@@ -449,6 +459,8 @@
449
459
 
450
460
  .optionCheck_daofbw {
451
461
  accent-color: var(--polly-accent);
462
+ inline-size: var(--polly-checkbox-size);
463
+ block-size: var(--polly-checkbox-size);
452
464
  vertical-align: middle;
453
465
  pointer-events: none;
454
466
  margin-inline-end: var(--polly-space-sm);
@@ -494,20 +506,26 @@
494
506
  /* src/polly-ui/Button.module.css */
495
507
  @layer polly-components {
496
508
  .btn_c4HKfA {
497
- display: inline-block;
498
- padding: var(--polly-space-sm) var(--polly-space-lg);
509
+ display: inline-flex;
510
+ justify-content: center;
511
+ align-items: center;
512
+ gap: var(--polly-space-sm);
513
+ min-block-size: var(--polly-control-height-md);
514
+ padding-block-start: 0;
515
+ padding-block-end: 0;
516
+ padding-inline: var(--polly-control-padding-md);
499
517
  border: var(--polly-border-width-default) solid transparent;
500
- border-radius: var(--polly-radius-sm);
518
+ border-radius: var(--polly-control-radius);
501
519
  font-family: inherit;
502
520
  font-size: var(--polly-text-md);
503
521
  font-weight: var(--polly-weight-medium);
522
+ line-height: var(--polly-line-height-tight);
504
523
  text-align: center;
505
524
  text-decoration: none;
506
525
  cursor: pointer;
507
526
  user-select: none;
508
527
  box-sizing: border-box;
509
528
  transition: background-color var(--polly-motion-fast), border-color var(--polly-motion-fast), color var(--polly-motion-fast);
510
- line-height: 1.2;
511
529
  }
512
530
 
513
531
  .btn_c4HKfA:disabled, .btn_c4HKfA[aria-disabled="true"] {
@@ -582,20 +600,31 @@
582
600
  }
583
601
 
584
602
  .btnSmall_c4HKfA {
585
- padding: var(--polly-space-xs) var(--polly-space-md);
603
+ min-block-size: var(--polly-control-height-sm);
604
+ padding-inline: var(--polly-control-padding-sm);
586
605
  font-size: var(--polly-text-sm);
587
606
  }
588
607
 
589
608
  .btnLarge_c4HKfA {
590
- padding: var(--polly-space-md) var(--polly-space-xl);
609
+ min-block-size: var(--polly-control-height-lg);
610
+ padding-inline: var(--polly-control-padding-lg);
591
611
  font-size: var(--polly-text-lg);
592
612
  }
593
613
 
594
614
  .btnCircle_c4HKfA {
595
615
  border-radius: var(--polly-radius-full);
596
616
  aspect-ratio: 1;
597
- width: var(--polly-control-height-md);
598
- padding: 0;
617
+ inline-size: var(--polly-control-height-md);
618
+ padding-inline-start: 0;
619
+ padding-inline-end: 0;
620
+ }
621
+
622
+ .btnSmall_c4HKfA.btnCircle_c4HKfA {
623
+ inline-size: var(--polly-control-height-sm);
624
+ }
625
+
626
+ .btnLarge_c4HKfA.btnCircle_c4HKfA {
627
+ inline-size: var(--polly-control-height-lg);
599
628
  }
600
629
 
601
630
  .btnFullWidth_c4HKfA {
@@ -608,6 +637,22 @@
608
637
  text-overflow: ellipsis;
609
638
  white-space: nowrap;
610
639
  }
640
+
641
+ .icon_c4HKfA {
642
+ display: inline-flex;
643
+ flex: none;
644
+ align-items: center;
645
+ }
646
+
647
+ .label_c4HKfA {
648
+ min-inline-size: 0;
649
+ }
650
+
651
+ .btnBounded_c4HKfA .label_c4HKfA {
652
+ overflow: hidden;
653
+ text-overflow: ellipsis;
654
+ white-space: nowrap;
655
+ }
611
656
  }
612
657
 
613
658
  /* src/polly-ui/Surface.module.css */
@@ -691,7 +736,11 @@
691
736
  /* src/polly-ui/Checkbox.module.css */
692
737
  @layer polly-components {
693
738
  .checkbox_EKE5sg {
694
- display: inline-block;
739
+ display: inline-flex;
740
+ align-items: center;
741
+ gap: var(--polly-space-sm);
742
+ box-sizing: border-box;
743
+ min-block-size: var(--polly-control-height-md);
695
744
  cursor: pointer;
696
745
  font-family: inherit;
697
746
  font-size: var(--polly-text-md);
@@ -706,14 +755,15 @@
706
755
 
707
756
  .input_EKE5sg {
708
757
  accent-color: var(--polly-accent);
709
- vertical-align: middle;
758
+ inline-size: var(--polly-checkbox-size);
759
+ block-size: var(--polly-checkbox-size);
710
760
  cursor: inherit;
761
+ flex: none;
711
762
  margin: 0;
712
763
  }
713
764
 
714
765
  .label_EKE5sg {
715
- vertical-align: middle;
716
- margin-inline-start: var(--polly-space-sm);
766
+ min-inline-size: 0;
717
767
  }
718
768
  }
719
769
 
@@ -831,13 +881,19 @@
831
881
 
832
882
  .cancel_ZfTX4A, .confirm_ZfTX4A, .confirmDanger_ZfTX4A {
833
883
  appearance: none;
834
- padding: var(--polly-space-sm) var(--polly-space-lg);
884
+ display: inline-flex;
885
+ min-block-size: var(--polly-control-height-md);
886
+ padding-block-start: 0;
887
+ padding-block-end: 0;
888
+ padding-inline: var(--polly-control-padding-md);
835
889
  font: inherit;
836
890
  font-weight: var(--polly-weight-medium);
837
891
  border: var(--polly-border-width-default) solid var(--polly-border);
838
- border-radius: var(--polly-radius-md);
892
+ border-radius: var(--polly-control-radius);
839
893
  cursor: pointer;
840
894
  transition: background var(--polly-motion-fast) ease;
895
+ justify-content: center;
896
+ align-items: center;
841
897
  }
842
898
 
843
899
  .cancel_ZfTX4A {
@@ -918,14 +974,20 @@
918
974
 
919
975
  .close_fLMrWA {
920
976
  appearance: none;
977
+ display: inline-flex;
978
+ min-block-size: var(--polly-control-height-md);
921
979
  color: var(--polly-text);
922
980
  border: var(--polly-border-width-default) solid transparent;
923
- border-radius: var(--polly-radius-md);
924
- padding: var(--polly-space-sm) var(--polly-space-md);
981
+ border-radius: var(--polly-control-radius);
982
+ padding-block-start: 0;
983
+ padding-block-end: 0;
984
+ padding-inline: var(--polly-field-padding);
925
985
  font: inherit;
926
986
  cursor: pointer;
927
987
  transition: background var(--polly-motion-fast) ease, border-color var(--polly-motion-fast) ease;
928
988
  background: none;
989
+ justify-content: center;
990
+ align-items: center;
929
991
  }
930
992
 
931
993
  .close_fLMrWA:hover {
@@ -949,10 +1011,13 @@
949
1011
  display: inline-flex;
950
1012
  align-items: center;
951
1013
  gap: var(--polly-space-sm);
1014
+ box-sizing: border-box;
952
1015
  min-block-size: var(--polly-control-height-md);
953
- padding: var(--polly-space-sm) var(--polly-space-md);
1016
+ padding-block-start: 0;
1017
+ padding-block-end: 0;
1018
+ padding-inline: var(--polly-control-padding-md);
954
1019
  border: var(--polly-border-width-default) solid var(--polly-border);
955
- border-radius: var(--polly-radius-sm);
1020
+ border-radius: var(--polly-control-radius);
956
1021
  background-color: var(--polly-surface);
957
1022
  font-family: inherit;
958
1023
  font-size: var(--polly-text-md);
@@ -1083,8 +1148,11 @@
1083
1148
  }
1084
1149
 
1085
1150
  .tab_xKO6GA {
1086
- display: inline-block;
1087
- padding: var(--polly-space-sm) var(--polly-space-lg);
1151
+ display: inline-flex;
1152
+ min-block-size: var(--polly-control-height-md);
1153
+ padding-block-start: 0;
1154
+ padding-block-end: 0;
1155
+ padding-inline: var(--polly-control-padding-md);
1088
1156
  border: none;
1089
1157
  border-bottom: var(--polly-border-width-medium) solid transparent;
1090
1158
  font-family: inherit;
@@ -1095,6 +1163,8 @@
1095
1163
  white-space: nowrap;
1096
1164
  transition: color var(--polly-motion-fast), border-color var(--polly-motion-fast);
1097
1165
  background: none;
1166
+ justify-content: center;
1167
+ align-items: center;
1098
1168
  }
1099
1169
 
1100
1170
  .tab_xKO6GA:hover:not(:disabled) {
@@ -1192,13 +1262,16 @@
1192
1262
  @layer polly-components {
1193
1263
  .input_ez4_Vg {
1194
1264
  box-sizing: border-box;
1195
- padding: var(--polly-space-sm) var(--polly-space-md);
1265
+ min-block-size: var(--polly-control-height-md);
1266
+ padding-block-start: 0;
1267
+ padding-block-end: 0;
1268
+ padding-inline: var(--polly-field-padding);
1196
1269
  font: inherit;
1197
1270
  font-family: var(--polly-font-sans);
1198
1271
  color: var(--polly-text);
1199
1272
  background: var(--polly-surface);
1200
1273
  border: var(--polly-border-width-default) solid var(--polly-border);
1201
- border-radius: var(--polly-radius-md);
1274
+ border-radius: var(--polly-control-radius);
1202
1275
  transition: border-color var(--polly-motion-fast) ease, background var(--polly-motion-fast) ease;
1203
1276
  inline-size: 100%;
1204
1277
  }
@@ -1218,7 +1291,8 @@
1218
1291
 
1219
1292
  .input_ez4_Vg[data-polly-input-variant="multi"] {
1220
1293
  field-sizing: content;
1221
- min-block-size: calc(1.5em + 2 * var(--polly-space-sm) + 2 * var(--polly-border-width-default));
1294
+ padding-block: calc((var(--polly-control-height-md) - 1.5em - 2 * var(--polly-border-width-default)) / 2);
1295
+ min-block-size: var(--polly-control-height-md);
1222
1296
  resize: vertical;
1223
1297
  max-block-size: 40em;
1224
1298
  }
@@ -1266,13 +1340,19 @@
1266
1340
 
1267
1341
  .close_xVzRuA {
1268
1342
  appearance: none;
1343
+ display: inline-flex;
1344
+ min-block-size: var(--polly-control-height-sm);
1269
1345
  color: inherit;
1270
1346
  border: var(--polly-border-width-default) solid transparent;
1271
- border-radius: var(--polly-radius-sm);
1272
- padding: var(--polly-space-xs) var(--polly-space-sm);
1347
+ border-radius: var(--polly-control-radius);
1348
+ padding-block-start: 0;
1349
+ padding-block-end: 0;
1350
+ padding-inline: var(--polly-space-sm);
1273
1351
  font: inherit;
1274
1352
  cursor: pointer;
1275
1353
  background: none;
1354
+ justify-content: center;
1355
+ align-items: center;
1276
1356
  }
1277
1357
 
1278
1358
  .close_xVzRuA:hover {
@@ -1283,13 +1363,16 @@
1283
1363
  /* src/polly-ui/Toggle.module.css */
1284
1364
  @layer polly-components {
1285
1365
  .toggle_rnf-SQ {
1286
- display: inline-block;
1366
+ display: inline-flex;
1367
+ align-items: center;
1368
+ gap: var(--polly-space-sm);
1369
+ box-sizing: border-box;
1370
+ min-block-size: var(--polly-control-height-md);
1287
1371
  cursor: pointer;
1288
1372
  font-family: inherit;
1289
1373
  font-size: var(--polly-text-md);
1290
1374
  color: var(--polly-text);
1291
1375
  user-select: none;
1292
- vertical-align: middle;
1293
1376
  }
1294
1377
 
1295
1378
  .disabled_rnf-SQ {
@@ -1310,12 +1393,12 @@
1310
1393
  }
1311
1394
 
1312
1395
  .track_rnf-SQ {
1313
- display: inline-block;
1396
+ display: block;
1314
1397
  position: relative;
1315
1398
  border-radius: var(--polly-radius-full);
1316
1399
  background-color: var(--polly-border);
1317
- vertical-align: middle;
1318
1400
  transition: background-color var(--polly-motion-fast);
1401
+ flex: none;
1319
1402
  width: 36px;
1320
1403
  height: 20px;
1321
1404
  }
@@ -1350,7 +1433,6 @@
1350
1433
  }
1351
1434
 
1352
1435
  .label_rnf-SQ {
1353
- vertical-align: middle;
1354
- margin-inline-start: var(--polly-space-sm);
1436
+ min-inline-size: 0;
1355
1437
  }
1356
1438
  }
@@ -14,6 +14,13 @@
14
14
 
15
15
  @layer polly-defaults {
16
16
  :root {
17
+ /* Tell the UA which schemes the page supports, so native controls —
18
+ * date and time pickers, the checkbox box, scrollbars, autofill —
19
+ * follow the palette instead of staying light on a dark page
20
+ * (polly#179). The media query below asks what the user prefers;
21
+ * this property answers what the page can render. */
22
+ color-scheme: light dark;
23
+
17
24
  /* Surfaces */
18
25
  --polly-surface: #ffffff;
19
26
  --polly-surface-raised: #ffffff;
@@ -75,10 +82,39 @@
75
82
  --polly-measure-prose: 68ch;
76
83
  --polly-measure-page: 1040px;
77
84
 
78
- /* Control heights — used by Button, Input, Select. */
79
- --polly-control-height-sm: 1.75rem;
80
- --polly-control-height-md: 2.25rem;
81
- --polly-control-height-lg: 2.75rem;
85
+ /* Control heights — the single source of truth for how tall an
86
+ * interactive control is. Button, TextInput, ActionInput, Select,
87
+ * FileInput, Dropdown, Checkbox, Toggle and Tabs all set
88
+ * `min-block-size` from this ladder, so a row of mixed controls
89
+ * lines up without the consumer measuring anything. The ladder used to
90
+ * exist without anything reading it, and the same row held five different
91
+ * heights (37.19 / 40 / 42 / 42 / 54 px).
92
+ *
93
+ * Control text is centred by the flex/native box rather than by
94
+ * vertical padding, so changing a height here moves every control
95
+ * together and never re-cuts the text position. */
96
+ --polly-control-height-sm: 2rem;
97
+ --polly-control-height-md: 2.5rem;
98
+ --polly-control-height-lg: 3rem;
99
+
100
+ /* Control corner — one radius for every control, so a Button beside
101
+ * a TextInput has the same corner. Containers (Collapsible, the
102
+ * Dropdown menu, Output, a code block) keep --polly-radius-md
103
+ * independently; this token exists so a consumer can re-cut control
104
+ * corners without touching container corners. */
105
+ --polly-control-radius: var(--polly-radius-md);
106
+
107
+ /* Control inline padding — buttons carry more air than fields, and
108
+ * the ladder tracks the height ladder. */
109
+ --polly-control-padding-sm: var(--polly-space-md);
110
+ --polly-control-padding-md: var(--polly-space-lg);
111
+ --polly-control-padding-lg: var(--polly-space-xl);
112
+ --polly-field-padding: var(--polly-space-md);
113
+
114
+ /* Native checkbox box. The UA default is 13x13, which reads small
115
+ * beside 16px text; Checkbox and the Select option check both size
116
+ * from this. */
117
+ --polly-checkbox-size: 1.125rem;
82
118
 
83
119
  /* Control widths — caps and floors for Button, Select, Dropdown
84
120
  * triggers so semi-dynamic labels (multi-select joins, user-
@@ -194,6 +230,7 @@
194
230
 
195
231
  /* Explicit mode override — wins over prefers-color-scheme. */
196
232
  [data-polly-theme="light"] {
233
+ color-scheme: light;
197
234
  --polly-surface: #ffffff;
198
235
  --polly-surface-raised: #ffffff;
199
236
  --polly-surface-sunken: #f4f5f7;
@@ -225,6 +262,7 @@
225
262
  }
226
263
 
227
264
  [data-polly-theme="dark"] {
265
+ color-scheme: dark;
228
266
  --polly-surface: #14171c;
229
267
  --polly-surface-raised: #1c2027;
230
268
  --polly-surface-sunken: #0f1216;
@@ -72,6 +72,12 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
72
72
  });
73
73
 
74
74
  // tools/test/src/browser/harness.ts
75
+ function reportProgress(event) {
76
+ const fn = window["__pollyProgress"];
77
+ if (typeof fn === "function") {
78
+ fn(event);
79
+ }
80
+ }
75
81
  var results = [];
76
82
  var suites = [];
77
83
  function describe(name, fn) {
@@ -230,19 +236,24 @@ function cleanup(container) {
230
236
  container.innerHTML = "";
231
237
  }
232
238
  async function done() {
239
+ reportProgress({ kind: "plan", total: suites.reduce((n, s) => n + s.tests.length, 0) });
233
240
  for (const suite of suites) {
234
241
  for (const t of suite.tests) {
235
242
  const fullName = `${suite.name} > ${t.name}`;
243
+ reportProgress({ kind: "start", name: fullName });
244
+ let result;
236
245
  try {
237
246
  await t.fn();
238
- results.push({ name: fullName, passed: true });
247
+ result = { name: fullName, passed: true };
239
248
  } catch (err) {
240
- results.push({
249
+ result = {
241
250
  name: fullName,
242
251
  passed: false,
243
252
  error: err instanceof Error ? err.message : String(err)
244
- });
253
+ };
245
254
  }
255
+ results.push(result);
256
+ reportProgress({ kind: "result", ...result });
246
257
  }
247
258
  }
248
259
  const report = window["__pollyReport"];
@@ -273,4 +284,4 @@ export {
273
284
  waitFor
274
285
  };
275
286
 
276
- //# debugId=F9043B3B399565AC64756E2164756E21
287
+ //# debugId=B042476A041C912A64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../tools/test/src/browser/harness.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Browser-side test harness for Polly applications.\n *\n * Provides describe/test/expect/done that run inside a Puppeteer-launched\n * browser tab and record results on window.__testResults for the Node-side\n * runner to collect. Matchers cover both value assertions and DOM element\n * assertions so that Preact component tests and WebRTC adapter tests use\n * the same harness.\n *\n * @example\n * ```typescript\n * import { describe, test, expect, done, flush, cleanup } from \"@fairfox/polly/test/browser\";\n *\n * const app = document.getElementById(\"app\")!;\n *\n * describe(\"my feature\", () => {\n * test(\"renders correctly\", async () => {\n * render(<MyComponent />, app);\n * await flush();\n * expect(app.querySelector(\"h1\")).toHaveTextContent(\"Hello\");\n * cleanup(app);\n * });\n * });\n *\n * done();\n * ```\n */\n\ninterface TestResult {\n name: string;\n passed: boolean;\n error?: string;\n}\n\nconst results: TestResult[] = [];\nconst suites: Array<{\n name: string;\n tests: Array<{ name: string; fn: () => Promise<void> | void }>;\n}> = [];\n\nexport function describe(name: string, fn: () => void): void {\n suites.push({ name, tests: [] });\n fn();\n}\n\nexport function test(name: string, fn: () => Promise<void> | void): void {\n const suite = suites[suites.length - 1];\n if (suite) {\n suite.tests.push({ name, fn });\n }\n}\n\nfunction assertElement(value: unknown): Element {\n if (!(value instanceof Element)) {\n throw new Error(`Expected an Element, got ${typeof value}: ${String(value)}`);\n }\n return value;\n}\n\nexport function expect<T>(actual: T) {\n return {\n // Value matchers\n toBe(expected: T) {\n if (actual !== expected) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) !== JSON.stringify(expected)) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toContain(sub: string) {\n if (!String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" to contain \"${sub}\"`);\n }\n },\n toBeTruthy() {\n if (!actual) throw new Error(`Expected truthy, got ${String(actual)}`);\n },\n toBeFalsy() {\n if (actual) throw new Error(`Expected falsy, got ${String(actual)}`);\n },\n toBeNull() {\n if (actual !== null) throw new Error(`Expected null, got ${String(actual)}`);\n },\n toBeDefined() {\n if (actual === undefined || actual === null) {\n throw new Error(`Expected value to be defined, got ${String(actual)}`);\n }\n },\n toBeUndefined() {\n if (actual !== undefined) {\n throw new Error(`Expected undefined, got ${JSON.stringify(actual)}`);\n }\n },\n toBeGreaterThan(expected: number) {\n if (typeof actual !== \"number\" || actual <= expected) {\n throw new Error(`Expected ${String(actual)} to be greater than ${expected}`);\n }\n },\n toHaveLength(expected: number) {\n const obj = actual;\n const len = obj && typeof obj === \"object\" && \"length\" in obj ? Number(obj.length) : -1;\n if (len !== expected) throw new Error(`Expected length ${expected}, got ${len}`);\n },\n toExist() {\n if (actual == null) throw new Error(`Expected value to exist, got ${String(actual)}`);\n },\n\n // DOM element matchers\n toHaveTextContent(expected: string) {\n const el = assertElement(actual);\n if (!el.textContent?.includes(expected)) {\n throw new Error(\n `Expected text content to include ${JSON.stringify(expected)}, got ${JSON.stringify(el.textContent)}`\n );\n }\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (!(el instanceof HTMLInputElement) || !el.checked) {\n throw new Error(\"Expected element to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (!el.hasAttribute(\"disabled\") && el.getAttribute(\"aria-disabled\") !== \"true\") {\n throw new Error(\"Expected element to be disabled\");\n }\n },\n toHaveValue(expected: string) {\n const el = assertElement(actual);\n const inputEl =\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el : null;\n if (!inputEl || inputEl.value !== expected) {\n throw new Error(\n `Expected value ${JSON.stringify(expected)}, got ${JSON.stringify(inputEl?.value ?? \"(not an input)\")}`\n );\n }\n },\n toHaveAttribute(name: string, value?: string) {\n const el = assertElement(actual);\n if (!el.hasAttribute(name)) {\n throw new Error(`Expected element to have attribute \"${name}\"`);\n }\n if (value !== undefined && el.getAttribute(name) !== value) {\n throw new Error(\n `Expected attribute \"${name}\" to be ${JSON.stringify(value)}, got ${JSON.stringify(el.getAttribute(name))}`\n );\n }\n },\n\n // .not variants\n not: {\n toBe(expected: T) {\n if (actual === expected) {\n throw new Error(`Expected value NOT to be ${JSON.stringify(expected)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) === JSON.stringify(expected)) {\n throw new Error(`Expected value NOT to equal ${JSON.stringify(expected)}`);\n }\n },\n toContain(sub: string) {\n if (String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" NOT to contain \"${sub}\"`);\n }\n },\n toBeNull() {\n if (actual === null) throw new Error(\"Expected value NOT to be null\");\n },\n toExist() {\n if (actual != null) throw new Error(`Expected value NOT to exist, got ${String(actual)}`);\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (el instanceof HTMLInputElement && el.checked) {\n throw new Error(\"Expected element NOT to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (el.hasAttribute(\"disabled\") || el.getAttribute(\"aria-disabled\") === \"true\") {\n throw new Error(\"Expected element NOT to be disabled\");\n }\n },\n toHaveAttribute(name: string) {\n const el = assertElement(actual);\n if (el.hasAttribute(name)) {\n throw new Error(`Expected element NOT to have attribute \"${name}\"`);\n }\n },\n },\n };\n}\n\n/**\n * Flush microtasks and pending DOM updates. Call after signal assignments\n * or render calls to give the reactive system and the browser a chance to\n * settle before asserting on the result.\n */\nexport function flush(ms = 50): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Clear a container's rendered content. Call at the end of each test to\n * prevent state leaking between tests. If you use Preact's render(), pass\n * the same container; the function calls render(null, container) if Preact\n * is available, otherwise sets innerHTML to \"\".\n */\nexport function cleanup(container: Element): void {\n container.innerHTML = \"\";\n}\n\n/**\n * Run all registered tests and report the tally to the Node-side runner.\n *\n * The runner injects `window.__pollyReport` (via Puppeteer's\n * `page.exposeFunction`) before navigating, so reporting is a single\n * push from the page to the runner — no polling, no waiting on a long-\n * running CDP call from the Node side. Polled `page.evaluate` was the\n * root cause of the intermittent `Runtime.callFunctionOn` stalls\n * tracked in polly#138.\n *\n * Call this at the end of every .browser.ts test file.\n */\nexport async function done(): Promise<void> {\n for (const suite of suites) {\n for (const t of suite.tests) {\n const fullName = `${suite.name} > ${t.name}`;\n try {\n await t.fn();\n results.push({ name: fullName, passed: true });\n } catch (err) {\n results.push({\n name: fullName,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n const report = (window as unknown as Record<string, unknown>)[\"__pollyReport\"];\n if (!isReportFunction(report)) {\n throw new Error(\n \"harness.done(): window.__pollyReport is not defined. \" +\n \"This harness must be driven by the Polly browser test runner.\"\n );\n }\n report(results);\n}\n\n/** The runner injects `window.__pollyReport`; all we can check is callability. */\nfunction isReportFunction(value: unknown): value is (r: TestResult[]) => void {\n return typeof value === \"function\";\n}\n\n/**\n * Wait until a predicate returns true, polling every intervalMs. Rejects\n * after timeoutMs.\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n timeoutMs = 5000,\n intervalMs = 25\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await predicate()) return;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n throw new Error(`waitFor timed out after ${timeoutMs}ms`);\n}\n"
5
+ "/**\n * Browser-side test harness for Polly applications.\n *\n * Provides describe/test/expect/done that run inside a Puppeteer-launched\n * browser tab and record results on window.__testResults for the Node-side\n * runner to collect. Matchers cover both value assertions and DOM element\n * assertions so that Preact component tests and WebRTC adapter tests use\n * the same harness.\n *\n * @example\n * ```typescript\n * import { describe, test, expect, done, flush, cleanup } from \"@fairfox/polly/test/browser\";\n *\n * const app = document.getElementById(\"app\")!;\n *\n * describe(\"my feature\", () => {\n * test(\"renders correctly\", async () => {\n * render(<MyComponent />, app);\n * await flush();\n * expect(app.querySelector(\"h1\")).toHaveTextContent(\"Hello\");\n * cleanup(app);\n * });\n * });\n *\n * done();\n * ```\n */\n\nimport type { ProgressEvent } from \"./runner-core.ts\";\n\ninterface TestResult {\n name: string;\n passed: boolean;\n error?: string;\n}\n\n/**\n * Push one progress event to the runner, if it is listening.\n *\n * The runner binds `window.__pollyProgress` next to `__pollyReport`. Older\n * runners bind only the latter, so a missing binding is not an error — the\n * suite just runs silently, as it always did.\n */\nfunction reportProgress(event: ProgressEvent): void {\n const fn = (window as unknown as Record<string, unknown>)[\"__pollyProgress\"];\n if (typeof fn === \"function\") {\n (fn as (e: ProgressEvent) => void)(event);\n }\n}\n\nconst results: TestResult[] = [];\nconst suites: Array<{\n name: string;\n tests: Array<{ name: string; fn: () => Promise<void> | void }>;\n}> = [];\n\nexport function describe(name: string, fn: () => void): void {\n suites.push({ name, tests: [] });\n fn();\n}\n\nexport function test(name: string, fn: () => Promise<void> | void): void {\n const suite = suites[suites.length - 1];\n if (suite) {\n suite.tests.push({ name, fn });\n }\n}\n\nfunction assertElement(value: unknown): Element {\n if (!(value instanceof Element)) {\n throw new Error(`Expected an Element, got ${typeof value}: ${String(value)}`);\n }\n return value;\n}\n\nexport function expect<T>(actual: T) {\n return {\n // Value matchers\n toBe(expected: T) {\n if (actual !== expected) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) !== JSON.stringify(expected)) {\n throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);\n }\n },\n toContain(sub: string) {\n if (!String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" to contain \"${sub}\"`);\n }\n },\n toBeTruthy() {\n if (!actual) throw new Error(`Expected truthy, got ${String(actual)}`);\n },\n toBeFalsy() {\n if (actual) throw new Error(`Expected falsy, got ${String(actual)}`);\n },\n toBeNull() {\n if (actual !== null) throw new Error(`Expected null, got ${String(actual)}`);\n },\n toBeDefined() {\n if (actual === undefined || actual === null) {\n throw new Error(`Expected value to be defined, got ${String(actual)}`);\n }\n },\n toBeUndefined() {\n if (actual !== undefined) {\n throw new Error(`Expected undefined, got ${JSON.stringify(actual)}`);\n }\n },\n toBeGreaterThan(expected: number) {\n if (typeof actual !== \"number\" || actual <= expected) {\n throw new Error(`Expected ${String(actual)} to be greater than ${expected}`);\n }\n },\n toHaveLength(expected: number) {\n const obj = actual;\n const len = obj && typeof obj === \"object\" && \"length\" in obj ? Number(obj.length) : -1;\n if (len !== expected) throw new Error(`Expected length ${expected}, got ${len}`);\n },\n toExist() {\n if (actual == null) throw new Error(`Expected value to exist, got ${String(actual)}`);\n },\n\n // DOM element matchers\n toHaveTextContent(expected: string) {\n const el = assertElement(actual);\n if (!el.textContent?.includes(expected)) {\n throw new Error(\n `Expected text content to include ${JSON.stringify(expected)}, got ${JSON.stringify(el.textContent)}`\n );\n }\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (!(el instanceof HTMLInputElement) || !el.checked) {\n throw new Error(\"Expected element to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (!el.hasAttribute(\"disabled\") && el.getAttribute(\"aria-disabled\") !== \"true\") {\n throw new Error(\"Expected element to be disabled\");\n }\n },\n toHaveValue(expected: string) {\n const el = assertElement(actual);\n const inputEl =\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el : null;\n if (!inputEl || inputEl.value !== expected) {\n throw new Error(\n `Expected value ${JSON.stringify(expected)}, got ${JSON.stringify(inputEl?.value ?? \"(not an input)\")}`\n );\n }\n },\n toHaveAttribute(name: string, value?: string) {\n const el = assertElement(actual);\n if (!el.hasAttribute(name)) {\n throw new Error(`Expected element to have attribute \"${name}\"`);\n }\n if (value !== undefined && el.getAttribute(name) !== value) {\n throw new Error(\n `Expected attribute \"${name}\" to be ${JSON.stringify(value)}, got ${JSON.stringify(el.getAttribute(name))}`\n );\n }\n },\n\n // .not variants\n not: {\n toBe(expected: T) {\n if (actual === expected) {\n throw new Error(`Expected value NOT to be ${JSON.stringify(expected)}`);\n }\n },\n toEqual(expected: T) {\n if (JSON.stringify(actual) === JSON.stringify(expected)) {\n throw new Error(`Expected value NOT to equal ${JSON.stringify(expected)}`);\n }\n },\n toContain(sub: string) {\n if (String(actual).includes(sub)) {\n throw new Error(`Expected \"${String(actual)}\" NOT to contain \"${sub}\"`);\n }\n },\n toBeNull() {\n if (actual === null) throw new Error(\"Expected value NOT to be null\");\n },\n toExist() {\n if (actual != null) throw new Error(`Expected value NOT to exist, got ${String(actual)}`);\n },\n toBeChecked() {\n const el = assertElement(actual);\n if (el instanceof HTMLInputElement && el.checked) {\n throw new Error(\"Expected element NOT to be checked\");\n }\n },\n toBeDisabled() {\n const el = assertElement(actual);\n if (el.hasAttribute(\"disabled\") || el.getAttribute(\"aria-disabled\") === \"true\") {\n throw new Error(\"Expected element NOT to be disabled\");\n }\n },\n toHaveAttribute(name: string) {\n const el = assertElement(actual);\n if (el.hasAttribute(name)) {\n throw new Error(`Expected element NOT to have attribute \"${name}\"`);\n }\n },\n },\n };\n}\n\n/**\n * Flush microtasks and pending DOM updates. Call after signal assignments\n * or render calls to give the reactive system and the browser a chance to\n * settle before asserting on the result.\n */\nexport function flush(ms = 50): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Clear a container's rendered content. Call at the end of each test to\n * prevent state leaking between tests. If you use Preact's render(), pass\n * the same container; the function calls render(null, container) if Preact\n * is available, otherwise sets innerHTML to \"\".\n */\nexport function cleanup(container: Element): void {\n container.innerHTML = \"\";\n}\n\n/**\n * Run all registered tests and report the tally to the Node-side runner.\n *\n * The runner injects `window.__pollyReport` (via Puppeteer's\n * `page.exposeFunction`) before navigating, so reporting is a single\n * push from the page to the runner — no polling, no waiting on a long-\n * running CDP call from the Node side. Polled `page.evaluate` was the\n * root cause of the intermittent `Runtime.callFunctionOn` stalls\n * tracked in polly#138.\n *\n * Call this at the end of every .browser.ts test file.\n */\nexport async function done(): Promise<void> {\n // Announce the plan before running anything. A page that stops part-way —\n // a runaway loop, an unsettled promise — has then already told the runner\n // how many tests it owed, so the stalled file reports how many never ran\n // instead of counting as a single failure (polly#177).\n reportProgress({ kind: \"plan\", total: suites.reduce((n, s) => n + s.tests.length, 0) });\n\n for (const suite of suites) {\n for (const t of suite.tests) {\n const fullName = `${suite.name} > ${t.name}`;\n reportProgress({ kind: \"start\", name: fullName });\n let result: TestResult;\n try {\n await t.fn();\n result = { name: fullName, passed: true };\n } catch (err) {\n result = {\n name: fullName,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n results.push(result);\n reportProgress({ kind: \"result\", ...result });\n }\n }\n\n const report = (window as unknown as Record<string, unknown>)[\"__pollyReport\"];\n if (!isReportFunction(report)) {\n throw new Error(\n \"harness.done(): window.__pollyReport is not defined. \" +\n \"This harness must be driven by the Polly browser test runner.\"\n );\n }\n report(results);\n}\n\n/** The runner injects `window.__pollyReport`; all we can check is callability. */\nfunction isReportFunction(value: unknown): value is (r: TestResult[]) => void {\n return typeof value === \"function\";\n}\n\n/**\n * Wait until a predicate returns true, polling every intervalMs. Rejects\n * after timeoutMs.\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n timeoutMs = 5000,\n intervalMs = 25\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await predicate()) return;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n throw new Error(`waitFor timed out after ${timeoutMs}ms`);\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "F9043B3B399565AC64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "B042476A041C912A64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -4,9 +4,16 @@
4
4
  *
5
5
  * Finds all *.browser.ts files in a given directory, bundles each with
6
6
  * Bun.build for the browser target (with an internal Automerge WASM fix),
7
- * serves the bundle on an ephemeral port, opens a Puppeteer page, and
8
- * polls window.__done for results. Prints pass/fail per test and exits
9
- * non-zero if any test failed.
7
+ * serves the bundle on an ephemeral port, and opens a Puppeteer page in its
8
+ * own browser context. The page pushes its results back through bindings the
9
+ * runner exposes (`__pollyReport` for the final tally, `__pollyProgress` per
10
+ * test). Prints pass/fail per test and exits non-zero if any test failed.
11
+ *
12
+ * A file that stops reporting is diagnosed rather than merely timed out: the
13
+ * runner asks the page whether its main thread is still free, so a runaway
14
+ * loop in the code under test reads differently from a suite that never
15
+ * finished, and the tests that never ran are counted (polly#177). Set
16
+ * POLLY_BROWSER_STACK=1 to also print the stack a wedged page is executing.
10
17
  *
11
18
  * A signalling server for WebRTC tests starts automatically on a random
12
19
  * port. The URL is injected into the bundle via process.env.SIGNALING_URL.