@excom/dismiss-watcher 0.1.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.
- package/.rush/temp/chunked-rush-logs/dismiss-watcher.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/dismiss-watcher.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/dismiss-watcher.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +6 -0
- package/dismiss-watcher.ts +209 -0
- package/index.ts +21 -0
- package/package.json +48 -0
- package/rush-logs/dismiss-watcher.apply-exports.cache.log +1 -0
- package/rush-logs/dismiss-watcher.apply-exports.log +1 -0
- package/rush-logs/dismiss-watcher.build_docs.cache.log +1 -0
- package/rush-logs/dismiss-watcher.build_docs.log +1 -0
- package/rush-logs/dismiss-watcher.build_package-metas.cache.log +1 -0
- package/rush-logs/dismiss-watcher.build_package-metas.log +1 -0
- package/support/custom-elements.json +180 -0
- package/support/demos/drawer.html +19 -0
- package/support/demos/simple.html +24 -0
- package/support/dist-docs/dismiss-watcher.md +176 -0
- package/support/docs/README.md +62 -0
- package/support/package-meta.json +106 -0
- package/support/tests/dismiss-watcher.test.ts +370 -0
- package/support/tests/drawer.view.test.ts +71 -0
- package/support/tests/simple.view.test.ts +57 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
fixture,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import "../../index";
|
|
11
|
+
|
|
12
|
+
const outsideMouseup = (target: EventTarget = document.body) =>
|
|
13
|
+
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
14
|
+
|
|
15
|
+
/** `<div><nav><dismiss-watcher …></dismiss-watcher></nav><button></button></div>` */
|
|
16
|
+
const mountPanel = (attrs = "is-active") => {
|
|
17
|
+
const root = fixture<HTMLDivElement>(
|
|
18
|
+
`<div>
|
|
19
|
+
<nav><dismiss-watcher ${attrs}></dismiss-watcher></nav>
|
|
20
|
+
<button type="button">outside</button>
|
|
21
|
+
</div>`
|
|
22
|
+
);
|
|
23
|
+
const nav = root.querySelector("nav")!;
|
|
24
|
+
const watcher = root.querySelector("dismiss-watcher")!;
|
|
25
|
+
const outside = root.querySelector("button")!;
|
|
26
|
+
const dismiss = vi.fn();
|
|
27
|
+
root.addEventListener("dismiss-watcher-dismiss", dismiss);
|
|
28
|
+
return { root, nav, watcher, outside, dismiss };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("dismiss-watcher", () => {
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
document.body.innerHTML = "";
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("never renders children", () => {
|
|
38
|
+
const { watcher } = mountPanel();
|
|
39
|
+
expect(watcher).dom.to.equalTag(
|
|
40
|
+
`<dismiss-watcher is-active></dismiss-watcher>`
|
|
41
|
+
);
|
|
42
|
+
expect(watcher.childNodes.length).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("fires dismiss with reason outside-click and dispatches fire-event at the target", async () => {
|
|
46
|
+
const { nav, watcher, outside, dismiss } = mountPanel(
|
|
47
|
+
`is-active fire-event="menu-close"`
|
|
48
|
+
);
|
|
49
|
+
const menuClose = vi.fn();
|
|
50
|
+
nav.addEventListener("menu-close", menuClose);
|
|
51
|
+
|
|
52
|
+
outsideMouseup(outside);
|
|
53
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
54
|
+
const event = dismiss.mock.calls[0][0] as CustomEvent;
|
|
55
|
+
expect(event.target).toBe(watcher);
|
|
56
|
+
expect(event.bubbles).toBe(true);
|
|
57
|
+
expect(event.cancelable).toBe(true);
|
|
58
|
+
expect(event.detail).toEqual({ reason: "outside-click" });
|
|
59
|
+
|
|
60
|
+
// default action runs in the next task
|
|
61
|
+
expect(menuClose).not.toHaveBeenCalled();
|
|
62
|
+
await wait(0);
|
|
63
|
+
expect(menuClose).toHaveBeenCalledTimes(1);
|
|
64
|
+
const fired = menuClose.mock.calls[0][0] as CustomEvent;
|
|
65
|
+
expect(fired.target).toBe(nav);
|
|
66
|
+
expect(fired.bubbles).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("invokes every command-name on the target, before fire-event", async () => {
|
|
70
|
+
const { nav, outside } = mountPanel(
|
|
71
|
+
`is-active command-name="--close --blur" fire-event="menu-close"`
|
|
72
|
+
);
|
|
73
|
+
const seen: string[] = [];
|
|
74
|
+
nav.addEventListener("command", (e) => {
|
|
75
|
+
const { command, source, bubbles } = e as Event & {
|
|
76
|
+
command: string;
|
|
77
|
+
source: Element | null;
|
|
78
|
+
};
|
|
79
|
+
expect(bubbles).toBe(false);
|
|
80
|
+
expect(source).toBe(nav.querySelector("dismiss-watcher"));
|
|
81
|
+
seen.push(command);
|
|
82
|
+
});
|
|
83
|
+
nav.addEventListener("menu-close", (e) => seen.push(e.type));
|
|
84
|
+
outsideMouseup(outside);
|
|
85
|
+
await wait(0);
|
|
86
|
+
expect(seen).toEqual(["--close", "--blur", "menu-close"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("dispatches every fire-event token", async () => {
|
|
90
|
+
const { nav, outside } = mountPanel(
|
|
91
|
+
`is-active fire-event="menu-close menu-blur"`
|
|
92
|
+
);
|
|
93
|
+
const seen: string[] = [];
|
|
94
|
+
nav.addEventListener("menu-close", (e) => seen.push(e.type));
|
|
95
|
+
nav.addEventListener("menu-blur", (e) => seen.push(e.type));
|
|
96
|
+
outsideMouseup(outside);
|
|
97
|
+
await wait(0);
|
|
98
|
+
expect(seen).toEqual(["menu-close", "menu-blur"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("ignores a mouseup inside the target", async () => {
|
|
102
|
+
const { nav, dismiss } = mountPanel(`is-active fire-event="menu-close"`);
|
|
103
|
+
const menuClose = vi.fn();
|
|
104
|
+
nav.addEventListener("menu-close", menuClose);
|
|
105
|
+
const inner = document.createElement("a");
|
|
106
|
+
nav.append(inner);
|
|
107
|
+
outsideMouseup(inner);
|
|
108
|
+
outsideMouseup(nav);
|
|
109
|
+
await wait(0);
|
|
110
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
111
|
+
expect(menuClose).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("fires dismiss with reason escape through the CloseWatcher", async () => {
|
|
115
|
+
const { nav, watcher, dismiss } = mountPanel(
|
|
116
|
+
`is-active fire-event="menu-close"`
|
|
117
|
+
);
|
|
118
|
+
const menuClose = vi.fn();
|
|
119
|
+
nav.addEventListener("menu-close", menuClose);
|
|
120
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
121
|
+
|
|
122
|
+
// the shim only reacts to trusted Escape keydowns
|
|
123
|
+
const keydown = new KeyboardEvent("keydown", {
|
|
124
|
+
key: "Escape",
|
|
125
|
+
bubbles: true,
|
|
126
|
+
cancelable: true,
|
|
127
|
+
});
|
|
128
|
+
Object.defineProperty(keydown, "isTrusted", { value: true });
|
|
129
|
+
document.dispatchEvent(keydown);
|
|
130
|
+
await wait(0);
|
|
131
|
+
|
|
132
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect((dismiss.mock.calls[0][0] as CustomEvent).detail).toEqual({
|
|
134
|
+
reason: "escape",
|
|
135
|
+
});
|
|
136
|
+
await wait(0);
|
|
137
|
+
expect(menuClose).toHaveBeenCalledTimes(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("re-arms the CloseWatcher after Escape so a cancelled dismissal keeps working", async () => {
|
|
141
|
+
const { root, watcher, dismiss } = mountPanel(`is-active`);
|
|
142
|
+
root.addEventListener("dismiss-watcher-dismiss", (e) =>
|
|
143
|
+
e.preventDefault()
|
|
144
|
+
);
|
|
145
|
+
const first = watcher._closeWatcher!;
|
|
146
|
+
first.requestClose();
|
|
147
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
148
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
149
|
+
expect(watcher._closeWatcher).not.toBe(first);
|
|
150
|
+
watcher._closeWatcher!.requestClose();
|
|
151
|
+
expect(dismiss).toHaveBeenCalledTimes(2);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("preventDefault() on dismiss skips fire-event", async () => {
|
|
155
|
+
const { root, nav, outside, dismiss } = mountPanel(
|
|
156
|
+
`is-active fire-event="menu-close"`
|
|
157
|
+
);
|
|
158
|
+
const menuClose = vi.fn();
|
|
159
|
+
nav.addEventListener("menu-close", menuClose);
|
|
160
|
+
root.addEventListener("dismiss-watcher-dismiss", (e) =>
|
|
161
|
+
e.preventDefault()
|
|
162
|
+
);
|
|
163
|
+
outsideMouseup(outside);
|
|
164
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
165
|
+
await wait(0);
|
|
166
|
+
expect(menuClose).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("resolves target-ref relative to :scope", async () => {
|
|
170
|
+
const root = fixture<HTMLDivElement>(
|
|
171
|
+
`<div>
|
|
172
|
+
<dismiss-watcher is-active target-ref=":scope + nav" fire-event="menu-close"></dismiss-watcher>
|
|
173
|
+
<nav><a href="#">inside</a></nav>
|
|
174
|
+
<button type="button">outside</button>
|
|
175
|
+
</div>`
|
|
176
|
+
);
|
|
177
|
+
const nav = root.querySelector("nav")!;
|
|
178
|
+
const menuClose = vi.fn();
|
|
179
|
+
nav.addEventListener("menu-close", menuClose);
|
|
180
|
+
const dismiss = vi.fn();
|
|
181
|
+
root.addEventListener("dismiss-watcher-dismiss", dismiss);
|
|
182
|
+
|
|
183
|
+
// inside the resolved target: nothing
|
|
184
|
+
outsideMouseup(nav.querySelector("a")!);
|
|
185
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
186
|
+
// outside it, even though the click is inside the watcher's parent
|
|
187
|
+
outsideMouseup(root.querySelector("button")!);
|
|
188
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
189
|
+
await wait(0);
|
|
190
|
+
expect(menuClose).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect((menuClose.mock.calls[0][0] as Event).target).toBe(nav);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("does nothing without a resolvable target", async () => {
|
|
195
|
+
const root = fixture<HTMLDivElement>(
|
|
196
|
+
`<div>
|
|
197
|
+
<dismiss-watcher is-active target-ref=":scope + nav" fire-event="menu-close"></dismiss-watcher>
|
|
198
|
+
<button type="button">outside</button>
|
|
199
|
+
</div>`
|
|
200
|
+
);
|
|
201
|
+
const dismiss = vi.fn();
|
|
202
|
+
root.addEventListener("dismiss-watcher-dismiss", dismiss);
|
|
203
|
+
outsideMouseup(root.querySelector("button")!);
|
|
204
|
+
await wait(0);
|
|
205
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("adds the document listener and a CloseWatcher when is-active is set", () => {
|
|
209
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
210
|
+
const { watcher } = mountPanel("");
|
|
211
|
+
expect(watcher._closeWatcher).toBeFalsy();
|
|
212
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
213
|
+
"mouseup",
|
|
214
|
+
watcher._handleOutsideMouseup,
|
|
215
|
+
expect.anything()
|
|
216
|
+
);
|
|
217
|
+
watcher.isActive = true;
|
|
218
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
219
|
+
"mouseup",
|
|
220
|
+
watcher._handleOutsideMouseup,
|
|
221
|
+
expect.anything()
|
|
222
|
+
);
|
|
223
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("removes the document listener and destroys the watcher when is-active is unset", () => {
|
|
227
|
+
const { watcher, outside, dismiss } = mountPanel();
|
|
228
|
+
const removeSpy = vi.spyOn(document, "removeEventListener");
|
|
229
|
+
const closeWatcher = watcher._closeWatcher!;
|
|
230
|
+
const destroySpy = vi.spyOn(closeWatcher, "destroy");
|
|
231
|
+
const unlistenSpy = vi.spyOn(closeWatcher, "removeEventListener");
|
|
232
|
+
|
|
233
|
+
watcher.removeAttribute("is-active");
|
|
234
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
235
|
+
"mouseup",
|
|
236
|
+
watcher._handleOutsideMouseup,
|
|
237
|
+
expect.anything()
|
|
238
|
+
);
|
|
239
|
+
expect(unlistenSpy).toHaveBeenCalledWith("close", watcher._handleClose);
|
|
240
|
+
expect(destroySpy).toHaveBeenCalled();
|
|
241
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
242
|
+
|
|
243
|
+
outsideMouseup(outside);
|
|
244
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("tears down on a genuine disconnect", async () => {
|
|
248
|
+
const { watcher, dismiss } = mountPanel();
|
|
249
|
+
const removeSpy = vi.spyOn(document, "removeEventListener");
|
|
250
|
+
const destroySpy = vi.spyOn(watcher._closeWatcher!, "destroy");
|
|
251
|
+
watcher.remove();
|
|
252
|
+
await wait(0);
|
|
253
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
254
|
+
"mouseup",
|
|
255
|
+
watcher._handleOutsideMouseup,
|
|
256
|
+
expect.anything()
|
|
257
|
+
);
|
|
258
|
+
expect(destroySpy).toHaveBeenCalled();
|
|
259
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
260
|
+
outsideMouseup();
|
|
261
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("keeps both watchers across a synchronous DOM move", async () => {
|
|
265
|
+
const { root, watcher, outside, dismiss } = mountPanel();
|
|
266
|
+
const closeWatcher = watcher._closeWatcher!;
|
|
267
|
+
const destroySpy = vi.spyOn(closeWatcher, "destroy");
|
|
268
|
+
const home = document.createElement("aside");
|
|
269
|
+
root.append(home);
|
|
270
|
+
home.append(watcher); // disconnect + connect in the same tick = move
|
|
271
|
+
await wait(0);
|
|
272
|
+
expect(destroySpy).not.toHaveBeenCalled();
|
|
273
|
+
expect(watcher._closeWatcher).toBe(closeWatcher);
|
|
274
|
+
// the tracked document listener is restored: target is now `home`
|
|
275
|
+
outsideMouseup(outside);
|
|
276
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("rebuilds on a genuine reconnect", async () => {
|
|
280
|
+
const { root, nav, watcher, outside, dismiss } = mountPanel();
|
|
281
|
+
watcher.remove();
|
|
282
|
+
await wait(0);
|
|
283
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
284
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
285
|
+
nav.append(watcher);
|
|
286
|
+
await wait(0);
|
|
287
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
288
|
+
"mouseup",
|
|
289
|
+
watcher._handleOutsideMouseup,
|
|
290
|
+
expect.anything()
|
|
291
|
+
);
|
|
292
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
293
|
+
outsideMouseup(outside);
|
|
294
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(root.contains(watcher)).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("watches both when neither watch-* attribute is present", () => {
|
|
299
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
300
|
+
const { watcher } = mountPanel("is-active");
|
|
301
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
302
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
303
|
+
"mouseup",
|
|
304
|
+
watcher._handleOutsideMouseup,
|
|
305
|
+
expect.anything()
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("watch-escape alone skips the document listener", () => {
|
|
310
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
311
|
+
const { watcher, outside, dismiss } = mountPanel("is-active watch-escape");
|
|
312
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
313
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
314
|
+
"mouseup",
|
|
315
|
+
watcher._handleOutsideMouseup,
|
|
316
|
+
expect.anything()
|
|
317
|
+
);
|
|
318
|
+
outsideMouseup(outside);
|
|
319
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
320
|
+
watcher._closeWatcher!.requestClose();
|
|
321
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("watch-outside-click alone skips the CloseWatcher", () => {
|
|
325
|
+
const { watcher, outside, dismiss } = mountPanel(
|
|
326
|
+
"is-active watch-outside-click"
|
|
327
|
+
);
|
|
328
|
+
expect(watcher._closeWatcher).toBeFalsy();
|
|
329
|
+
outsideMouseup(outside);
|
|
330
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
331
|
+
expect((dismiss.mock.calls[0][0] as CustomEvent).detail.reason).toBe(
|
|
332
|
+
"outside-click"
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("re-applies when watch-* changes while active", () => {
|
|
337
|
+
const { watcher, outside, dismiss } = mountPanel("is-active");
|
|
338
|
+
const removeSpy = vi.spyOn(document, "removeEventListener");
|
|
339
|
+
const destroySpy = vi.spyOn(watcher._closeWatcher!, "destroy");
|
|
340
|
+
|
|
341
|
+
watcher.setAttribute("watch-escape", "");
|
|
342
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
343
|
+
"mouseup",
|
|
344
|
+
watcher._handleOutsideMouseup,
|
|
345
|
+
expect.anything()
|
|
346
|
+
);
|
|
347
|
+
outsideMouseup(outside);
|
|
348
|
+
expect(dismiss).not.toHaveBeenCalled();
|
|
349
|
+
expect(destroySpy).not.toHaveBeenCalled();
|
|
350
|
+
|
|
351
|
+
watcher.removeAttribute("watch-escape");
|
|
352
|
+
watcher.setAttribute("watch-outside-click", "");
|
|
353
|
+
expect(destroySpy).toHaveBeenCalled();
|
|
354
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
355
|
+
outsideMouseup(outside);
|
|
356
|
+
expect(dismiss).toHaveBeenCalledTimes(1);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("does not re-apply when watch-* changes while inactive", () => {
|
|
360
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
361
|
+
const { watcher } = mountPanel("");
|
|
362
|
+
watcher.setAttribute("watch-outside-click", "");
|
|
363
|
+
expect(addSpy).not.toHaveBeenCalledWith(
|
|
364
|
+
"mouseup",
|
|
365
|
+
watcher._handleOutsideMouseup,
|
|
366
|
+
expect.anything()
|
|
367
|
+
);
|
|
368
|
+
expect(watcher._closeWatcher).toBeFalsy();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { invokeCommand } from "@excom/neutron";
|
|
2
|
+
import "@excom/quark-sheet";
|
|
3
|
+
import "@excom/event-handler";
|
|
4
|
+
import "@excom/content-drawer";
|
|
5
|
+
import "../../index";
|
|
6
|
+
import {
|
|
7
|
+
afterEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
import {
|
|
13
|
+
flush,
|
|
14
|
+
mountView,
|
|
15
|
+
readDemo,
|
|
16
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
17
|
+
|
|
18
|
+
const outsideMouseup = (target: EventTarget) =>
|
|
19
|
+
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
20
|
+
|
|
21
|
+
describe("drawer view", () => {
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
document.body.innerHTML = "";
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("opens the drawer, then dismisses on outside click and on Escape", async () => {
|
|
27
|
+
const { root } = await mountView(readDemo(import.meta.url, "drawer"));
|
|
28
|
+
const drawer = root.querySelector("content-drawer")!;
|
|
29
|
+
const watcher = drawer.querySelector("dismiss-watcher")!;
|
|
30
|
+
const button = root.querySelector<HTMLButtonElement>("button[command]")!;
|
|
31
|
+
expect(button.getAttribute("command")).toBe("--open");
|
|
32
|
+
// happy-dom has no Command API: dispatch what the button would have
|
|
33
|
+
const open = () => invokeCommand(drawer, "--open", button);
|
|
34
|
+
expect(drawer.firstElementChild).toBe(watcher);
|
|
35
|
+
expect(drawer.hasAttribute("is-open")).toBe(false);
|
|
36
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
37
|
+
|
|
38
|
+
open();
|
|
39
|
+
await flush();
|
|
40
|
+
expect(drawer.hasAttribute("is-open")).toBe(true);
|
|
41
|
+
expect(watcher.hasAttribute("is-active")).toBe(true);
|
|
42
|
+
expect(watcher._closeWatcher).toBeTruthy();
|
|
43
|
+
|
|
44
|
+
// inside the drawer: stays open
|
|
45
|
+
outsideMouseup(drawer.querySelector("h2")!);
|
|
46
|
+
await flush();
|
|
47
|
+
expect(drawer.hasAttribute("is-open")).toBe(true);
|
|
48
|
+
|
|
49
|
+
// outside: `--close` is invoked on the drawer, inverse rule deactivates
|
|
50
|
+
outsideMouseup(document.body);
|
|
51
|
+
await flush();
|
|
52
|
+
expect(drawer.hasAttribute("is-open")).toBe(false);
|
|
53
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
54
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
55
|
+
|
|
56
|
+
// open, not toggle: a mouseup on the trigger closes, the click re-opens
|
|
57
|
+
open();
|
|
58
|
+
await flush();
|
|
59
|
+
expect(drawer.hasAttribute("is-open")).toBe(true);
|
|
60
|
+
outsideMouseup(button);
|
|
61
|
+
await flush();
|
|
62
|
+
open();
|
|
63
|
+
await flush();
|
|
64
|
+
expect(drawer.hasAttribute("is-open")).toBe(true);
|
|
65
|
+
|
|
66
|
+
watcher._closeWatcher!.requestClose();
|
|
67
|
+
await flush();
|
|
68
|
+
expect(drawer.hasAttribute("is-open")).toBe(false);
|
|
69
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import "@excom/quark-sheet";
|
|
2
|
+
import "@excom/event-handler";
|
|
3
|
+
import "../../index";
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import {
|
|
11
|
+
click,
|
|
12
|
+
flush,
|
|
13
|
+
mountView,
|
|
14
|
+
readDemo,
|
|
15
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
16
|
+
|
|
17
|
+
const outsideMouseup = (target: EventTarget) =>
|
|
18
|
+
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
19
|
+
|
|
20
|
+
describe("simple view", () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
document.body.innerHTML = "";
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("opens the menu, then dismisses on outside click and on Escape", async () => {
|
|
26
|
+
const { root } = await mountView(readDemo(import.meta.url, "simple"));
|
|
27
|
+
const watcher = root.querySelector("dismiss-watcher")!;
|
|
28
|
+
const button = root.querySelector("[role='button']")!;
|
|
29
|
+
expect(root.hasAttribute("data-is-open")).toBe(false);
|
|
30
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
31
|
+
|
|
32
|
+
click(button);
|
|
33
|
+
await flush();
|
|
34
|
+
expect(root.hasAttribute("data-is-open")).toBe(true);
|
|
35
|
+
expect(watcher.hasAttribute("is-active")).toBe(true);
|
|
36
|
+
|
|
37
|
+
// inside the panel: stays open
|
|
38
|
+
outsideMouseup(root.querySelector("nav a")!);
|
|
39
|
+
await flush();
|
|
40
|
+
expect(root.hasAttribute("data-is-open")).toBe(true);
|
|
41
|
+
|
|
42
|
+
// outside: closes, and the inverse rule deactivates the watcher
|
|
43
|
+
outsideMouseup(document.body);
|
|
44
|
+
await flush();
|
|
45
|
+
expect(root.hasAttribute("data-is-open")).toBe(false);
|
|
46
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
47
|
+
expect(watcher._closeWatcher).toBeNull();
|
|
48
|
+
|
|
49
|
+
click(button);
|
|
50
|
+
await flush();
|
|
51
|
+
expect(root.hasAttribute("data-is-open")).toBe(true);
|
|
52
|
+
watcher._closeWatcher!.requestClose();
|
|
53
|
+
await flush();
|
|
54
|
+
expect(root.hasAttribute("data-is-open")).toBe(false);
|
|
55
|
+
expect(watcher.hasAttribute("is-active")).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|