@remit/ui 0.0.127 → 0.0.129

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.
Files changed (34) hide show
  1. package/package.json +2 -3
  2. package/src/components/brief-sections.keyboard.test.ts +8 -36
  3. package/src/components/calendar-types.ts +2 -0
  4. package/src/components/compose-body.language.test.ts +8 -58
  5. package/src/components/compose-body.spellcheck.test.ts +8 -55
  6. package/src/components/event-editor.render.test.ts +2 -2
  7. package/src/components/event-suggestion-card.render.test.ts +1 -0
  8. package/src/components/event-suggestion-card.stories.tsx +1 -0
  9. package/src/components/filter-rule-editor.create.test.ts +10 -59
  10. package/src/components/folder-tree-picker.create.test.ts +18 -63
  11. package/src/components/message-list-pane.brief-filter.test.ts +4 -31
  12. package/src/components/nav-link-surface.interaction.test.ts +7 -34
  13. package/src/components/nav-sidebar.keyboard.test.ts +11 -40
  14. package/src/components/password-input.render.test.ts +8 -50
  15. package/src/components/plain-text-editor.mount.test.ts +11 -66
  16. package/src/components/rich-text-editor.mount.test.ts +9 -63
  17. package/src/components/rich-text-formatting.test.ts +27 -45
  18. package/src/components/rich-text-image-node.test.ts +60 -83
  19. package/src/components/rich-text-markdown.test.ts +3 -25
  20. package/src/components/rich-text-paste.test.ts +28 -42
  21. package/src/components/rich-text-spellcheck-menu.test.ts +28 -94
  22. package/src/components/rich-text-spellcheck.test.ts +11 -65
  23. package/src/components/swipeable-row.gesture.test.ts +8 -42
  24. package/src/components/touch-list.follows-sections.test.ts +4 -27
  25. package/src/lib/adopted-html.test.ts +3 -22
  26. package/src/lib/compose-language.test.ts +2 -7
  27. package/src/lib/roving-focus.test.ts +16 -43
  28. package/src/lib/use-list-cursor.test.ts +3 -27
  29. package/src/lib/use-list-keyboard.test.ts +6 -30
  30. package/src/lib/use-long-press.test.ts +12 -43
  31. package/src/lib/use-rendered-row-ids.test.ts +3 -24
  32. package/src/lib/use-selection.hook.test.ts +3 -26
  33. package/src/lib/use-suggest-list.test.ts +5 -29
  34. package/src/lib/use-triage-keyboard.test.ts +6 -29
@@ -18,9 +18,9 @@
18
18
  * and snapped back, and nothing else happened.
19
19
  */
20
20
 
21
+ import "@remit/test-dom";
21
22
  import assert from "node:assert/strict";
22
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
23
- import type { JSDOM } from "jsdom";
23
+ import { afterEach, beforeEach, describe, it } from "node:test";
24
24
  import { act, createElement } from "react";
25
25
  import { createRoot, type Root } from "react-dom/client";
26
26
  import type { ThreadRowData } from "./app-shell-types.js";
@@ -39,45 +39,11 @@ const thread: ThreadRowData = {
39
39
  isRead: false,
40
40
  };
41
41
 
42
- let dom: JSDOM;
43
42
  let container: HTMLElement;
44
43
  let root: Root;
45
44
 
46
- before(async () => {
47
- const { JSDOM: JSDOMCtor } = await import("jsdom");
48
- dom = new JSDOMCtor(
49
- "<!doctype html><html><body><div id=root></div></body></html>",
50
- { url: "http://localhost/", pretendToBeVisual: true },
51
- );
52
- globalThis.window = dom.window as unknown as typeof globalThis.window;
53
- globalThis.document = dom.window.document;
54
- globalThis.HTMLElement = dom.window.HTMLElement;
55
- globalThis.Element = dom.window.Element;
56
- globalThis.SVGElement = dom.window.SVGElement;
57
- globalThis.PointerEvent = dom.window.PointerEvent;
58
- // jsdom does not implement the pointer-capture methods at all (not even
59
- // as no-ops) — SwipeableRow calls setPointerCapture once it claims the
60
- // horizontal axis, so an unpolyfilled call throws mid-gesture.
61
- dom.window.Element.prototype.setPointerCapture = () => undefined;
62
- dom.window.Element.prototype.releasePointerCapture = () => undefined;
63
- dom.window.Element.prototype.hasPointerCapture = () => false;
64
- Object.defineProperty(globalThis, "navigator", {
65
- value: dom.window.navigator,
66
- configurable: true,
67
- });
68
- (
69
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
70
- ).IS_REACT_ACT_ENVIRONMENT = true;
71
- });
72
-
73
- after(() => {
74
- dom.window.close();
75
- });
76
-
77
45
  beforeEach(() => {
78
- container = dom.window.document.getElementById(
79
- "root",
80
- ) as unknown as HTMLElement;
46
+ container = document.getElementById("root") as unknown as HTMLElement;
81
47
  container.innerHTML = "";
82
48
  root = createRoot(container);
83
49
  });
@@ -114,7 +80,7 @@ function mount(handlers: Handlers) {
114
80
  // The open affordance is the one button with no aria-label — the
115
81
  // leading/trailing action buttons ("Mark as read" etc.) only render once
116
82
  // peeked, but querying by absence of aria-label is stable at rest too.
117
- const row = [...dom.window.document.querySelectorAll("button")].find(
83
+ const row = [...document.querySelectorAll("button")].find(
118
84
  (b) => !b.hasAttribute("aria-label"),
119
85
  );
120
86
  assert.ok(row, "open-affordance button did not mount");
@@ -122,7 +88,7 @@ function mount(handlers: Handlers) {
122
88
  }
123
89
 
124
90
  function contextMenu(row: Element) {
125
- const event = new dom.window.MouseEvent("contextmenu", {
91
+ const event = new MouseEvent("contextmenu", {
126
92
  bubbles: true,
127
93
  cancelable: true,
128
94
  });
@@ -139,7 +105,7 @@ function contextMenu(row: Element) {
139
105
  function pointerDown(row: Element, x = 10, y = 10) {
140
106
  act(() => {
141
107
  row.dispatchEvent(
142
- new dom.window.PointerEvent("pointerdown", {
108
+ new PointerEvent("pointerdown", {
143
109
  bubbles: true,
144
110
  pointerType: "touch",
145
111
  pointerId: 1,
@@ -153,7 +119,7 @@ function pointerDown(row: Element, x = 10, y = 10) {
153
119
  function pointerMove(row: Element, x: number, y: number) {
154
120
  act(() => {
155
121
  row.dispatchEvent(
156
- new dom.window.PointerEvent("pointermove", {
122
+ new PointerEvent("pointermove", {
157
123
  bubbles: true,
158
124
  pointerType: "touch",
159
125
  pointerId: 1,
@@ -174,7 +140,7 @@ function pointerUp(row: Element) {
174
140
  // (the horizontal-swipe case here), so this also matches real behavior.
175
141
  act(() => {
176
142
  row.dispatchEvent(
177
- new dom.window.PointerEvent("pointerup", {
143
+ new PointerEvent("pointerup", {
178
144
  bubbles: true,
179
145
  pointerType: "touch",
180
146
  pointerId: 1,
@@ -7,16 +7,15 @@
7
7
  * ids over whatever the consumer passes rather than as a copy of the rows.
8
8
  */
9
9
 
10
+ import "@remit/test-dom";
10
11
  import assert from "node:assert/strict";
11
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
12
- import type { JSDOM } from "jsdom";
12
+ import { afterEach, beforeEach, describe, it } from "node:test";
13
13
  import { act, createElement } from "react";
14
14
  import { createRoot, type Root } from "react-dom/client";
15
15
  import type { ThreadSection } from "./app-shell-types.js";
16
16
  import type { SwipePeek } from "./swipeable-row.js";
17
17
  import { TouchListBody } from "./touch-list.js";
18
18
 
19
- let dom: JSDOM;
20
19
  let container: HTMLElement;
21
20
  let root: Root;
22
21
 
@@ -66,35 +65,13 @@ const click = (label: string) => {
66
65
  assert.ok(button, `no "${label}" control on the page`);
67
66
  act(() => {
68
67
  button.dispatchEvent(
69
- new dom.window.MouseEvent("click", { bubbles: true, cancelable: true }),
68
+ new MouseEvent("click", { bubbles: true, cancelable: true }),
70
69
  );
71
70
  });
72
71
  };
73
72
 
74
- before(async () => {
75
- const { JSDOM: JSDOMCtor } = await import("jsdom");
76
- dom = new JSDOMCtor(
77
- "<!doctype html><html><body><div id=root></div></body></html>",
78
- { url: "http://localhost/", pretendToBeVisual: true },
79
- );
80
- globalThis.window = dom.window as unknown as typeof globalThis.window;
81
- globalThis.document = dom.window.document;
82
- globalThis.HTMLElement = dom.window.HTMLElement;
83
- globalThis.Element = dom.window.Element;
84
- globalThis.SVGElement = dom.window.SVGElement;
85
- (
86
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
87
- ).IS_REACT_ACT_ENVIRONMENT = true;
88
- });
89
-
90
- after(() => {
91
- dom.window.close();
92
- });
93
-
94
73
  beforeEach(() => {
95
- container = dom.window.document.getElementById(
96
- "root",
97
- ) as unknown as HTMLElement;
74
+ container = document.getElementById("root") as unknown as HTMLElement;
98
75
  container.innerHTML = "";
99
76
  root = createRoot(container);
100
77
  });
@@ -3,29 +3,10 @@
3
3
  * over content nobody here wrote — a web page, another mail client, Word — so
4
4
  * the fixtures below are the shapes those actually put on the clipboard.
5
5
  */
6
+ import "@remit/test-dom";
6
7
  import assert from "node:assert/strict";
7
- import { before, describe, it } from "node:test";
8
- import type { JSDOM } from "jsdom";
9
- import type {
10
- sanitizeAdoptedHtml as SanitizeAdoptedHtml,
11
- sanitizeQuotedHtml as SanitizeQuotedHtml,
12
- } from "./adopted-html.js";
13
-
14
- let sanitizeAdoptedHtml: typeof SanitizeAdoptedHtml;
15
- let sanitizeQuotedHtml: typeof SanitizeQuotedHtml;
16
-
17
- before(async () => {
18
- const { JSDOM: JSDOMCtor } = await import("jsdom");
19
- const dom: JSDOM = new JSDOMCtor(
20
- "<!doctype html><html><body></body></html>",
21
- { url: "http://localhost/" },
22
- );
23
- globalThis.window = dom.window as unknown as typeof globalThis.window;
24
- globalThis.document = dom.window.document;
25
- ({ sanitizeAdoptedHtml, sanitizeQuotedHtml } = await import(
26
- "./adopted-html.js"
27
- ));
28
- });
8
+ import { describe, it } from "node:test";
9
+ import { sanitizeAdoptedHtml, sanitizeQuotedHtml } from "./adopted-html.js";
29
10
 
30
11
  describe("sanitizeAdoptedHtml", () => {
31
12
  it("keeps the structure a pasted document is made of", () => {
@@ -1,7 +1,7 @@
1
+ import "@remit/test-dom";
1
2
  import assert from "node:assert/strict";
2
- import { before, describe, it } from "node:test";
3
+ import { describe, it } from "node:test";
3
4
  import { data } from "franc-min/data.js";
4
- import { JSDOM } from "jsdom";
5
5
  import {
6
6
  browserSpellcheckHelp,
7
7
  COMPOSE_LANGUAGE_OPTIONS,
@@ -16,11 +16,6 @@ import {
16
16
  /** The dictionaries the published image stages, per `REMIT_SPELLCHECK_LANGUAGES`. */
17
17
  const BUILT = ["en", "en-GB", "nl"];
18
18
 
19
- before(() => {
20
- const dom = new JSDOM("");
21
- globalThis.DOMParser = dom.window.DOMParser;
22
- });
23
-
24
19
  describe("detectionCodeFor", () => {
25
20
  it("resolves a region through its language", () => {
26
21
  assert.equal(detectionCodeFor("en-GB"), "eng");
@@ -4,9 +4,9 @@
4
4
  * real KeyboardEvents at a real focused node, asserting on the DOM side effects
5
5
  * the hook owns (focus, tabIndex), which `renderToString` cannot reach.
6
6
  */
7
+ import "@remit/test-dom";
7
8
  import assert from "node:assert/strict";
8
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
9
- import type { JSDOM } from "jsdom";
9
+ import { afterEach, beforeEach, describe, it } from "node:test";
10
10
  import { act, createElement, type RefObject, useRef } from "react";
11
11
  import { createRoot, type Root } from "react-dom/client";
12
12
  import {
@@ -51,38 +51,11 @@ describe("rovingNextIndex", () => {
51
51
  });
52
52
  });
53
53
 
54
- let dom: JSDOM;
55
54
  let container: HTMLElement;
56
55
  let root: Root;
57
56
 
58
- before(async () => {
59
- const { JSDOM: JSDOMCtor } = await import("jsdom");
60
- dom = new JSDOMCtor(
61
- "<!doctype html><html><body><div id=root></div></body></html>",
62
- { url: "http://localhost/", pretendToBeVisual: true },
63
- );
64
- globalThis.window = dom.window as unknown as typeof globalThis.window;
65
- globalThis.document = dom.window.document;
66
- globalThis.HTMLElement = dom.window.HTMLElement;
67
- globalThis.Element = dom.window.Element;
68
- globalThis.KeyboardEvent = dom.window.KeyboardEvent;
69
- Object.defineProperty(globalThis, "navigator", {
70
- value: dom.window.navigator,
71
- configurable: true,
72
- });
73
- (
74
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
75
- ).IS_REACT_ACT_ENVIRONMENT = true;
76
- });
77
-
78
- after(() => {
79
- dom.window.close();
80
- });
81
-
82
57
  beforeEach(() => {
83
- container = dom.window.document.getElementById(
84
- "root",
85
- ) as unknown as HTMLElement;
58
+ container = document.getElementById("root") as unknown as HTMLElement;
86
59
  container.innerHTML = "";
87
60
  root = createRoot(container);
88
61
  });
@@ -95,7 +68,7 @@ afterEach(() => {
95
68
 
96
69
  function pressKey(target: Element, key: string, shiftKey = false) {
97
70
  target.dispatchEvent(
98
- new dom.window.KeyboardEvent("keydown", { key, bubbles: true, shiftKey }),
71
+ new KeyboardEvent("keydown", { key, bubbles: true, shiftKey }),
99
72
  );
100
73
  }
101
74
 
@@ -158,7 +131,7 @@ describe("useRovingFocus", () => {
158
131
  act(() => items[0]?.focus());
159
132
  act(() => pressKey(items[0] as Element, "ArrowDown"));
160
133
 
161
- assert.equal(dom.window.document.activeElement, items[1]);
134
+ assert.equal(document.activeElement, items[1]);
162
135
  assert.equal(items[1]?.tabIndex, 0);
163
136
  assert.equal(items[0]?.tabIndex, -1);
164
137
  });
@@ -169,7 +142,7 @@ describe("useRovingFocus", () => {
169
142
  act(() => items[0]?.focus());
170
143
  act(() => pressKey(items[0] as Element, "ArrowUp"));
171
144
 
172
- assert.equal(dom.window.document.activeElement, items[0]);
145
+ assert.equal(document.activeElement, items[0]);
173
146
  });
174
147
 
175
148
  it("ArrowDown clamps at the last row", () => {
@@ -178,7 +151,7 @@ describe("useRovingFocus", () => {
178
151
  act(() => items[1]?.focus());
179
152
  act(() => pressKey(items[1] as Element, "ArrowDown"));
180
153
 
181
- assert.equal(dom.window.document.activeElement, items[1]);
154
+ assert.equal(document.activeElement, items[1]);
182
155
  });
183
156
 
184
157
  it("Home/End jump to the first/last row", () => {
@@ -186,10 +159,10 @@ describe("useRovingFocus", () => {
186
159
  const items = rows();
187
160
  act(() => items[2]?.focus());
188
161
  act(() => pressKey(items[2] as Element, "End"));
189
- assert.equal(dom.window.document.activeElement, items[3]);
162
+ assert.equal(document.activeElement, items[3]);
190
163
 
191
164
  act(() => pressKey(items[3] as Element, "Home"));
192
- assert.equal(dom.window.document.activeElement, items[0]);
165
+ assert.equal(document.activeElement, items[0]);
193
166
  });
194
167
 
195
168
  it("mouse-focusing a row moves the tab stop there too", () => {
@@ -207,7 +180,7 @@ describe("useRovingFocus", () => {
207
180
  act(() => items[0]?.focus());
208
181
  act(() => pressKey(items[0] as Element, "ArrowDown"));
209
182
 
210
- assert.equal(dom.window.document.activeElement, items[1]);
183
+ assert.equal(document.activeElement, items[1]);
211
184
  assert.match(items[1]?.textContent ?? "", /row-1/);
212
185
  });
213
186
 
@@ -219,7 +192,7 @@ describe("useRovingFocus", () => {
219
192
  act(() => nested[1]?.focus());
220
193
  act(() => pressKey(nested[1] as Element, "ArrowDown"));
221
194
 
222
- assert.equal(dom.window.document.activeElement, rows()[0]);
195
+ assert.equal(document.activeElement, rows()[0]);
223
196
  });
224
197
 
225
198
  it("leaves Shift+Arrow to the layer above instead of moving the cursor", () => {
@@ -228,14 +201,14 @@ describe("useRovingFocus", () => {
228
201
  const spy = () => {
229
202
  seen += 1;
230
203
  };
231
- dom.window.addEventListener("keydown", spy);
204
+ window.addEventListener("keydown", spy);
232
205
  const items = rows();
233
206
  act(() => items[0]?.focus());
234
207
  act(() => pressKey(items[0] as Element, "ArrowDown", true));
235
208
  act(() => pressKey(items[0] as Element, "ArrowUp", true));
236
- dom.window.removeEventListener("keydown", spy);
209
+ window.removeEventListener("keydown", spy);
237
210
 
238
- assert.equal(dom.window.document.activeElement, items[0]);
211
+ assert.equal(document.activeElement, items[0]);
239
212
  assert.equal(seen, 2);
240
213
  });
241
214
 
@@ -245,12 +218,12 @@ describe("useRovingFocus", () => {
245
218
  const spy = () => {
246
219
  seen += 1;
247
220
  };
248
- dom.window.addEventListener("keydown", spy);
221
+ window.addEventListener("keydown", spy);
249
222
  const items = rows();
250
223
  act(() => items[0]?.focus());
251
224
  act(() => pressKey(items[0] as Element, "ArrowDown"));
252
225
  act(() => pressKey(items[1] as Element, "Enter"));
253
- dom.window.removeEventListener("keydown", spy);
226
+ window.removeEventListener("keydown", spy);
254
227
 
255
228
  assert.equal(seen, 1);
256
229
  });
@@ -3,44 +3,20 @@
3
3
  * every thread list drives. Mounted against jsdom rather than `renderToString`,
4
4
  * since the state only moves in response to real calls across renders.
5
5
  */
6
+ import "@remit/test-dom";
6
7
  import assert from "node:assert/strict";
7
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
8
- import type { JSDOM } from "jsdom";
8
+ import { afterEach, beforeEach, describe, it } from "node:test";
9
9
  import { act, createElement } from "react";
10
10
  import { createRoot, type Root } from "react-dom/client";
11
11
  import { type ListCursor, useListCursor } from "./use-list-cursor.js";
12
12
 
13
13
  const IDS = ["m1", "m2", "m3", "m4"];
14
14
 
15
- let dom: JSDOM;
16
15
  let container: HTMLElement;
17
16
  let root: Root;
18
17
 
19
- before(async () => {
20
- const { JSDOM: JSDOMCtor } = await import("jsdom");
21
- dom = new JSDOMCtor(
22
- "<!doctype html><html><body><div id=root></div></body></html>",
23
- { url: "http://localhost/", pretendToBeVisual: true },
24
- );
25
- globalThis.window = dom.window as unknown as typeof globalThis.window;
26
- globalThis.document = dom.window.document;
27
- globalThis.HTMLElement = dom.window.HTMLElement;
28
- globalThis.Element = dom.window.Element;
29
- Object.defineProperty(globalThis, "navigator", {
30
- value: dom.window.navigator,
31
- configurable: true,
32
- });
33
- (
34
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
35
- ).IS_REACT_ACT_ENVIRONMENT = true;
36
- });
37
-
38
- after(() => dom.window.close());
39
-
40
18
  beforeEach(() => {
41
- container = dom.window.document.getElementById(
42
- "root",
43
- ) as unknown as HTMLElement;
19
+ container = document.getElementById("root") as unknown as HTMLElement;
44
20
  container.innerHTML = "";
45
21
  root = createRoot(container);
46
22
  });
@@ -4,16 +4,15 @@
4
4
  * path reads its modifiers the same way, the selection follows the rows on
5
5
  * screen, and the footer offers only the actions the layer registered.
6
6
  */
7
+ import "@remit/test-dom";
7
8
  import assert from "node:assert/strict";
8
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
9
- import type { JSDOM } from "jsdom";
9
+ import { afterEach, beforeEach, describe, it } from "node:test";
10
10
  import { act, createElement } from "react";
11
11
  import { createRoot, type Root } from "react-dom/client";
12
12
  import { type ListKeyboard, useListKeyboard } from "./use-list-keyboard.js";
13
13
 
14
14
  const ALL_IDS = ["m1", "m2", "m3", "m4"];
15
15
 
16
- let dom: JSDOM;
17
16
  let container: HTMLElement;
18
17
  let root: Root;
19
18
  let list: ListKeyboard;
@@ -48,7 +47,7 @@ const rerender = async (rowIds: string[]) => {
48
47
  };
49
48
 
50
49
  const pane = (): HTMLElement => {
51
- const element = dom.window.document.getElementById("pane");
50
+ const element = document.getElementById("pane");
52
51
  assert.ok(element, "the harness rendered no pane");
53
52
  return element as unknown as HTMLElement;
54
53
  };
@@ -60,7 +59,7 @@ const press = (
60
59
  ) => {
61
60
  act(() => {
62
61
  target.dispatchEvent(
63
- new dom.window.KeyboardEvent("keydown", {
62
+ new KeyboardEvent("keydown", {
64
63
  key,
65
64
  bubbles: true,
66
65
  cancelable: true,
@@ -89,31 +88,8 @@ const click = (
89
88
 
90
89
  const selected = () => Array.from(list.cursor.selection.selectedIds).sort();
91
90
 
92
- before(async () => {
93
- const { JSDOM: JSDOMCtor } = await import("jsdom");
94
- dom = new JSDOMCtor(
95
- "<!doctype html><html><body><div id=root></div></body></html>",
96
- { url: "http://localhost/", pretendToBeVisual: true },
97
- );
98
- globalThis.window = dom.window as unknown as typeof globalThis.window;
99
- globalThis.document = dom.window.document;
100
- globalThis.HTMLElement = dom.window.HTMLElement;
101
- globalThis.Element = dom.window.Element;
102
- globalThis.SVGElement = dom.window.SVGElement;
103
- globalThis.MutationObserver = dom.window.MutationObserver;
104
- (
105
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
106
- ).IS_REACT_ACT_ENVIRONMENT = true;
107
- });
108
-
109
- after(() => {
110
- dom.window.close();
111
- });
112
-
113
91
  beforeEach(() => {
114
- container = dom.window.document.getElementById(
115
- "root",
116
- ) as unknown as HTMLElement;
92
+ container = document.getElementById("root") as unknown as HTMLElement;
117
93
  container.innerHTML = "";
118
94
  root = createRoot(container);
119
95
  mount();
@@ -158,7 +134,7 @@ describe("useListKeyboard", () => {
158
134
  });
159
135
 
160
136
  it("leaves a key pressed outside its own element alone", () => {
161
- press("j", {}, dom.window.document.body);
137
+ press("j", {}, document.body);
162
138
  assert.equal(list.keyboard.focusedId, undefined);
163
139
  });
164
140
 
@@ -7,24 +7,22 @@
7
7
  * real PointerEvents at a real mounted node and assert on the callback and
8
8
  * the DOM side effects react-aria owns (contextmenu suppression).
9
9
  *
10
- * jsdom is a devDependency scoped to this one test — react-aria's
11
- * pointerdown → threshold timer → onLongPress path, its global
10
+ * react-aria's pointerdown threshold timer onLongPress path, its global
12
11
  * pointerup/pointercancel listeners, and its contextmenu suppression all
13
12
  * need a real `document`/`window`/`PointerEvent`, which `renderToString`
14
13
  * (the pattern used elsewhere in this repo for presentational components)
15
14
  * cannot exercise.
16
15
  */
17
16
 
17
+ import "@remit/test-dom";
18
18
  import assert from "node:assert/strict";
19
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
20
- import type { JSDOM } from "jsdom";
19
+ import { afterEach, beforeEach, describe, it } from "node:test";
21
20
  import { act, createElement } from "react";
22
21
  import { createRoot, type Root } from "react-dom/client";
23
22
  import { useLongPress } from "./use-long-press.js";
24
23
 
25
24
  const THRESHOLD = 40;
26
25
 
27
- let dom: JSDOM;
28
26
  let container: HTMLElement;
29
27
  let root: Root;
30
28
 
@@ -54,14 +52,14 @@ function mount(props: {
54
52
  act(() => {
55
53
  root.render(createElement(Row, props));
56
54
  });
57
- const row = dom.window.document.getElementById("row");
55
+ const row = document.getElementById("row");
58
56
  assert.ok(row, "row did not mount");
59
57
  return row;
60
58
  }
61
59
 
62
60
  function pointerDown(row: Element, pointerType = "touch") {
63
61
  row.dispatchEvent(
64
- new dom.window.PointerEvent("pointerdown", {
62
+ new PointerEvent("pointerdown", {
65
63
  bubbles: true,
66
64
  pointerType,
67
65
  pointerId: 1,
@@ -72,7 +70,7 @@ function pointerDown(row: Element, pointerType = "touch") {
72
70
  }
73
71
 
74
72
  function dispatchContextMenu(row: Element) {
75
- const event = new dom.window.MouseEvent("contextmenu", {
73
+ const event = new MouseEvent("contextmenu", {
76
74
  bubbles: true,
77
75
  cancelable: true,
78
76
  });
@@ -81,8 +79,8 @@ function dispatchContextMenu(row: Element) {
81
79
  }
82
80
 
83
81
  function pointerUp() {
84
- dom.window.document.dispatchEvent(
85
- new dom.window.PointerEvent("pointerup", {
82
+ document.dispatchEvent(
83
+ new PointerEvent("pointerup", {
86
84
  bubbles: true,
87
85
  pointerType: "touch",
88
86
  pointerId: 1,
@@ -94,7 +92,7 @@ function pointerUp() {
94
92
 
95
93
  function pointerUpOn(row: Element) {
96
94
  row.dispatchEvent(
97
- new dom.window.PointerEvent("pointerup", {
95
+ new PointerEvent("pointerup", {
98
96
  bubbles: true,
99
97
  pointerType: "touch",
100
98
  pointerId: 1,
@@ -105,44 +103,15 @@ function pointerUpOn(row: Element) {
105
103
  }
106
104
 
107
105
  function pointerCancel(row: Element) {
108
- row.dispatchEvent(
109
- new dom.window.PointerEvent("pointercancel", { bubbles: true }),
110
- );
106
+ row.dispatchEvent(new PointerEvent("pointercancel", { bubbles: true }));
111
107
  }
112
108
 
113
109
  function wait(ms: number) {
114
110
  return act(() => new Promise((resolve) => setTimeout(resolve, ms)));
115
111
  }
116
112
 
117
- before(async () => {
118
- const { JSDOM: JSDOMCtor } = await import("jsdom");
119
- dom = new JSDOMCtor(
120
- "<!doctype html><html><body><div id=root></div></body></html>",
121
- { url: "http://localhost/", pretendToBeVisual: true },
122
- );
123
- globalThis.window = dom.window as unknown as typeof globalThis.window;
124
- globalThis.document = dom.window.document;
125
- globalThis.HTMLElement = dom.window.HTMLElement;
126
- globalThis.Element = dom.window.Element;
127
- globalThis.SVGElement = dom.window.SVGElement;
128
- globalThis.PointerEvent = dom.window.PointerEvent;
129
- Object.defineProperty(globalThis, "navigator", {
130
- value: dom.window.navigator,
131
- configurable: true,
132
- });
133
- (
134
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
135
- ).IS_REACT_ACT_ENVIRONMENT = true;
136
- });
137
-
138
- after(() => {
139
- dom.window.close();
140
- });
141
-
142
113
  beforeEach(() => {
143
- container = dom.window.document.getElementById(
144
- "root",
145
- ) as unknown as HTMLElement;
114
+ container = document.getElementById("root") as unknown as HTMLElement;
146
115
  container.innerHTML = "";
147
116
  root = createRoot(container);
148
117
  });
@@ -241,7 +210,7 @@ describe("useLongPress (react-aria wrapper)", () => {
241
210
 
242
211
  it("does not suppress contextmenu when no pointer interaction preceded it", async () => {
243
212
  mount({ onLongPress: () => undefined });
244
- const row = dom.window.document.getElementById("row") as Element;
213
+ const row = document.getElementById("row") as Element;
245
214
 
246
215
  assert.equal(dispatchContextMenu(row).defaultPrevented, false);
247
216
  });
@@ -2,14 +2,13 @@
2
2
  * The rendered rows are read from the DOM, so a list body that caps, collapses
3
3
  * or filters itself below its consumer still reports what it shows.
4
4
  */
5
+ import "@remit/test-dom";
5
6
  import assert from "node:assert/strict";
6
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
7
- import type { JSDOM } from "jsdom";
7
+ import { afterEach, beforeEach, describe, it } from "node:test";
8
8
  import { act, createElement, useState } from "react";
9
9
  import { createRoot, type Root } from "react-dom/client";
10
10
  import { useRenderedRowIds } from "./use-rendered-row-ids.js";
11
11
 
12
- let dom: JSDOM;
13
12
  let container: HTMLElement;
14
13
  let root: Root;
15
14
  let rowIds: string[] | undefined;
@@ -39,28 +38,8 @@ const settle = async () => {
39
38
  });
40
39
  };
41
40
 
42
- before(async () => {
43
- const { JSDOM: JSDOMCtor } = await import("jsdom");
44
- dom = new JSDOMCtor(
45
- "<!doctype html><html><body><div id=root></div></body></html>",
46
- { url: "http://localhost/", pretendToBeVisual: true },
47
- );
48
- globalThis.window = dom.window as unknown as typeof globalThis.window;
49
- globalThis.document = dom.window.document;
50
- globalThis.HTMLElement = dom.window.HTMLElement;
51
- globalThis.Element = dom.window.Element;
52
- globalThis.MutationObserver = dom.window.MutationObserver;
53
- (
54
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
55
- ).IS_REACT_ACT_ENVIRONMENT = true;
56
- });
57
-
58
- after(() => dom.window.close());
59
-
60
41
  beforeEach(() => {
61
- container = dom.window.document.getElementById(
62
- "root",
63
- ) as unknown as HTMLElement;
42
+ container = document.getElementById("root") as unknown as HTMLElement;
64
43
  container.innerHTML = "";
65
44
  root = createRoot(container);
66
45
  });