@excom/content-tabs 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/content-tabs.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/content-tabs.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/content-tabs.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 +5 -0
- package/content-tabs-body.ts +26 -0
- package/content-tabs-header.ts +64 -0
- package/content-tabs.ts +174 -0
- package/index.css +5 -0
- package/index.ts +33 -0
- package/package.json +43 -0
- package/rush-logs/content-tabs.apply-exports.cache.log +1 -0
- package/rush-logs/content-tabs.apply-exports.log +1 -0
- package/rush-logs/content-tabs.build_docs.cache.log +1 -0
- package/rush-logs/content-tabs.build_docs.log +1 -0
- package/rush-logs/content-tabs.build_package-metas.cache.log +1 -0
- package/rush-logs/content-tabs.build_package-metas.log +1 -0
- package/src/content-tabs.css +137 -0
- package/support/custom-elements.json +376 -0
- package/support/demos/file-tabs.html +6 -0
- package/support/demos/multi.html +6 -0
- package/support/demos/named.html +6 -0
- package/support/demos/simple.html +8 -0
- package/support/dist-docs/content-tabs-body.md +20 -0
- package/support/dist-docs/content-tabs-header.md +26 -0
- package/support/dist-docs/content-tabs.md +183 -0
- package/support/docs/README.md +74 -0
- package/support/package-meta.json +252 -0
- package/support/tests/content-tabs.test.ts +426 -0
- package/support/tests/file-tabs.view.test.ts +26 -0
- package/support/tests/multi.view.test.ts +29 -0
- package/support/tests/named.view.test.ts +27 -0
- package/support/tests/simple.view.test.ts +29 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,426 @@
|
|
|
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
|
+
/*
|
|
13
|
+
* `<content-tabs-header>` listens for native `click`. `_handleClick`
|
|
14
|
+
* is sync, but body sync is a parent microtask (`queueMicrotask` ->
|
|
15
|
+
* `_syncBodies`), so tick once before asserting body state.
|
|
16
|
+
*/
|
|
17
|
+
const clickHeader = async (header: Element) => {
|
|
18
|
+
header.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
19
|
+
await wait(0);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const buildTabs = (attrs = "") =>
|
|
23
|
+
fixture<HTMLContentTabsElement>(
|
|
24
|
+
`<content-tabs ${attrs}>
|
|
25
|
+
<content-tabs-header>Tab 1</content-tabs-header>
|
|
26
|
+
<content-tabs-header>Tab 2</content-tabs-header>
|
|
27
|
+
<content-tabs-body>Content 1</content-tabs-body>
|
|
28
|
+
<content-tabs-body>Content 2</content-tabs-body>
|
|
29
|
+
</content-tabs>`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const queryParts = (root: HTMLContentTabsElement) => {
|
|
33
|
+
const headers = root.querySelectorAll("content-tabs-header");
|
|
34
|
+
const bodies = root.querySelectorAll("content-tabs-body");
|
|
35
|
+
return {
|
|
36
|
+
header1: headers[0],
|
|
37
|
+
header2: headers[1],
|
|
38
|
+
body1: bodies[0],
|
|
39
|
+
body2: bodies[1],
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe("content-tabs", () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
document.body.innerHTML = "";
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("opens the clicked tab and closes others (default `single` behavior)", async () => {
|
|
50
|
+
const tabs = buildTabs();
|
|
51
|
+
const { header1, header2, body1, body2 } = queryParts(tabs);
|
|
52
|
+
|
|
53
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
54
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
55
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
56
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
57
|
+
|
|
58
|
+
await clickHeader(header1);
|
|
59
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
60
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
61
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
62
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
63
|
+
|
|
64
|
+
await clickHeader(header2);
|
|
65
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
66
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
67
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
68
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("treats `tab-type=single` the same as the default", async () => {
|
|
72
|
+
const tabs = buildTabs(`tab-type="single"`);
|
|
73
|
+
const { header1, header2, body1, body2 } = queryParts(tabs);
|
|
74
|
+
|
|
75
|
+
await clickHeader(header1);
|
|
76
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
77
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
78
|
+
|
|
79
|
+
/* Re-clicking an open header in `single` keeps it open.
|
|
80
|
+
`_handleClick` sets `is-open` true except in `toggle` / `multi`. */
|
|
81
|
+
await clickHeader(header1);
|
|
82
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
83
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
84
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
85
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("keeps multiple tabs open with `tab-type=multi`", async () => {
|
|
89
|
+
const tabs = buildTabs(`tab-type="multi"`);
|
|
90
|
+
const { header1, header2, body1, body2 } = queryParts(tabs);
|
|
91
|
+
|
|
92
|
+
await clickHeader(header1);
|
|
93
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
94
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
95
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
96
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
97
|
+
|
|
98
|
+
await clickHeader(header2);
|
|
99
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
100
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
101
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
102
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
103
|
+
|
|
104
|
+
// Click again: this tab off, the other untouched. Parent does not
|
|
105
|
+
// auto-close peers in `multi`.
|
|
106
|
+
await clickHeader(header1);
|
|
107
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
108
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
109
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
110
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("closes the clicked tab when re-clicked with `tab-type=toggle`", async () => {
|
|
114
|
+
const tabs = buildTabs(`tab-type="toggle"`);
|
|
115
|
+
const { header1, header2, body1, body2 } = queryParts(tabs);
|
|
116
|
+
|
|
117
|
+
await clickHeader(header1);
|
|
118
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
119
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
120
|
+
|
|
121
|
+
// Same header again -> closes it (toggle off).
|
|
122
|
+
await clickHeader(header1);
|
|
123
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
124
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
125
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
126
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
127
|
+
|
|
128
|
+
// Different header -> opens it, closes others (single-style switch).
|
|
129
|
+
await clickHeader(header1);
|
|
130
|
+
await clickHeader(header2);
|
|
131
|
+
expect(header1).dom.to.equalTag(`<content-tabs-header></content-tabs-header>`);
|
|
132
|
+
expect(header2).dom.to.equalTag(`<content-tabs-header is-open></content-tabs-header>`);
|
|
133
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
134
|
+
expect(body2).dom.to.equalTag(`<content-tabs-body is-open></content-tabs-body>`);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("pairs a header to its body by `tab-name` when set", async () => {
|
|
138
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
139
|
+
`<content-tabs>
|
|
140
|
+
<content-tabs-header tab-name="a">A</content-tabs-header>
|
|
141
|
+
<content-tabs-header tab-name="b">B</content-tabs-header>
|
|
142
|
+
<content-tabs-body tab-name="b">Body B</content-tabs-body>
|
|
143
|
+
<content-tabs-body tab-name="a">Body A</content-tabs-body>
|
|
144
|
+
</content-tabs>`,
|
|
145
|
+
);
|
|
146
|
+
const headerA = tabs.querySelector(
|
|
147
|
+
'content-tabs-header[tab-name="a"]',
|
|
148
|
+
) as HTMLContentTabsHeaderElement;
|
|
149
|
+
const bodyA = tabs.querySelector(
|
|
150
|
+
'content-tabs-body[tab-name="a"]',
|
|
151
|
+
) as HTMLContentTabsBodyElement;
|
|
152
|
+
const bodyB = tabs.querySelector(
|
|
153
|
+
'content-tabs-body[tab-name="b"]',
|
|
154
|
+
) as HTMLContentTabsBodyElement;
|
|
155
|
+
|
|
156
|
+
await clickHeader(headerA);
|
|
157
|
+
expect(bodyA).dom.to.equalTag(`<content-tabs-body tab-name="a" is-open></content-tabs-body>`);
|
|
158
|
+
expect(bodyB).dom.to.equalTag(`<content-tabs-body tab-name="b"></content-tabs-body>`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("isolates nested content-tabs from the outer instance", async () => {
|
|
162
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
163
|
+
`<content-tabs id="outer">
|
|
164
|
+
<content-tabs-header>Outer 1</content-tabs-header>
|
|
165
|
+
<content-tabs-header>Outer 2</content-tabs-header>
|
|
166
|
+
<content-tabs-body>
|
|
167
|
+
<content-tabs id="inner">
|
|
168
|
+
<content-tabs-header>Inner 1</content-tabs-header>
|
|
169
|
+
<content-tabs-header>Inner 2</content-tabs-header>
|
|
170
|
+
<content-tabs-body>Inner content 1</content-tabs-body>
|
|
171
|
+
<content-tabs-body>Inner content 2</content-tabs-body>
|
|
172
|
+
</content-tabs>
|
|
173
|
+
</content-tabs-body>
|
|
174
|
+
<content-tabs-body>Outer content 2</content-tabs-body>
|
|
175
|
+
</content-tabs>`,
|
|
176
|
+
);
|
|
177
|
+
const outer = tabs;
|
|
178
|
+
const inner = tabs.querySelector("#inner") as HTMLContentTabsElement;
|
|
179
|
+
const outerHeaders = [
|
|
180
|
+
...outer.querySelectorAll("content-tabs-header"),
|
|
181
|
+
].filter((h) => h.closest("content-tabs") === outer);
|
|
182
|
+
const innerHeaders = [...inner.querySelectorAll("content-tabs-header")];
|
|
183
|
+
|
|
184
|
+
// Inner header only: outer stays closed (parent stops propagation).
|
|
185
|
+
await clickHeader(innerHeaders[1]);
|
|
186
|
+
expect(innerHeaders[0]).dom.to.equalTag(`<content-tabs-header>Inner 1</content-tabs-header>`);
|
|
187
|
+
expect(innerHeaders[1]).dom.to.equalTag(`<content-tabs-header is-open>Inner 2</content-tabs-header>`);
|
|
188
|
+
expect(outerHeaders[0]).dom.to.equalTag(`<content-tabs-header>Outer 1</content-tabs-header>`);
|
|
189
|
+
expect(outerHeaders[1]).dom.to.equalTag(`<content-tabs-header>Outer 2</content-tabs-header>`);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("content-tabs (provision)", () => {
|
|
194
|
+
afterEach(() => {
|
|
195
|
+
document.body.innerHTML = "";
|
|
196
|
+
vi.restoreAllMocks();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Provision is computed in the batched body sync, one microtask after
|
|
200
|
+
// connect / a header change
|
|
201
|
+
const settle = () => wait(0);
|
|
202
|
+
|
|
203
|
+
it("reads the initial is-open headers after mount", async () => {
|
|
204
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
205
|
+
`<content-tabs>
|
|
206
|
+
<content-tabs-header>Tab 1</content-tabs-header>
|
|
207
|
+
<content-tabs-header is-open>Tab 2</content-tabs-header>
|
|
208
|
+
<content-tabs-body>Content 1</content-tabs-body>
|
|
209
|
+
<content-tabs-body is-open>Content 2</content-tabs-body>
|
|
210
|
+
</content-tabs>`,
|
|
211
|
+
);
|
|
212
|
+
await settle();
|
|
213
|
+
expect(tabs.provision).toEqual({
|
|
214
|
+
tabType: null,
|
|
215
|
+
openTabs: [1],
|
|
216
|
+
activeTab: 1,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("reports no active tab when none is open", async () => {
|
|
221
|
+
const tabs = buildTabs();
|
|
222
|
+
await settle();
|
|
223
|
+
expect(tabs.provision).toEqual({
|
|
224
|
+
tabType: null,
|
|
225
|
+
openTabs: [],
|
|
226
|
+
activeTab: null,
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("follows clicks in `single` and emits neutron-provision on each change", async () => {
|
|
231
|
+
const tabs = buildTabs(`tab-type="single"`);
|
|
232
|
+
const { header1, header2 } = queryParts(tabs);
|
|
233
|
+
await settle();
|
|
234
|
+
const provisionSpy = vi.fn();
|
|
235
|
+
tabs.addEventListener("neutron-provision", provisionSpy);
|
|
236
|
+
|
|
237
|
+
await clickHeader(header1);
|
|
238
|
+
expect(tabs.provision).toEqual({
|
|
239
|
+
tabType: "single",
|
|
240
|
+
openTabs: [0],
|
|
241
|
+
activeTab: 0,
|
|
242
|
+
});
|
|
243
|
+
expect(provisionSpy).toHaveBeenCalledTimes(1);
|
|
244
|
+
|
|
245
|
+
await clickHeader(header2);
|
|
246
|
+
expect(tabs.provision).toEqual({
|
|
247
|
+
tabType: "single",
|
|
248
|
+
openTabs: [1],
|
|
249
|
+
activeTab: 1,
|
|
250
|
+
});
|
|
251
|
+
expect(provisionSpy).toHaveBeenCalledTimes(2);
|
|
252
|
+
|
|
253
|
+
// re-clicking the open header in `single` is a no-op, no new object
|
|
254
|
+
const before = tabs.provision;
|
|
255
|
+
await clickHeader(header2);
|
|
256
|
+
expect(tabs.provision).toBe(before);
|
|
257
|
+
expect(provisionSpy).toHaveBeenCalledTimes(2);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("lists every open tab in header order in `multi`", async () => {
|
|
261
|
+
const tabs = buildTabs(`tab-type="multi"`);
|
|
262
|
+
const { header1, header2 } = queryParts(tabs);
|
|
263
|
+
|
|
264
|
+
await clickHeader(header2);
|
|
265
|
+
expect(tabs.provision).toEqual({
|
|
266
|
+
tabType: "multi",
|
|
267
|
+
openTabs: [1],
|
|
268
|
+
activeTab: 1,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await clickHeader(header1);
|
|
272
|
+
expect(tabs.provision).toEqual({
|
|
273
|
+
tabType: "multi",
|
|
274
|
+
openTabs: [0, 1],
|
|
275
|
+
activeTab: 0,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await clickHeader(header1);
|
|
279
|
+
expect(tabs.provision).toEqual({
|
|
280
|
+
tabType: "multi",
|
|
281
|
+
openTabs: [1],
|
|
282
|
+
activeTab: 1,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("clears activeTab when the open header is re-clicked in `toggle`", async () => {
|
|
287
|
+
const tabs = buildTabs(`tab-type="toggle"`);
|
|
288
|
+
const { header1 } = queryParts(tabs);
|
|
289
|
+
|
|
290
|
+
await clickHeader(header1);
|
|
291
|
+
expect(tabs.provision).toEqual({
|
|
292
|
+
tabType: "toggle",
|
|
293
|
+
openTabs: [0],
|
|
294
|
+
activeTab: 0,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
await clickHeader(header1);
|
|
298
|
+
expect(tabs.provision).toEqual({
|
|
299
|
+
tabType: "toggle",
|
|
300
|
+
openTabs: [],
|
|
301
|
+
activeTab: null,
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("identifies named headers by tab-name and unnamed ones by index", async () => {
|
|
306
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
307
|
+
`<content-tabs tab-type="multi">
|
|
308
|
+
<content-tabs-header tab-name="a" is-open>A</content-tabs-header>
|
|
309
|
+
<content-tabs-header>Positional</content-tabs-header>
|
|
310
|
+
<content-tabs-header tab-name="c">C</content-tabs-header>
|
|
311
|
+
<content-tabs-body tab-name="a">Body A</content-tabs-body>
|
|
312
|
+
<content-tabs-body>Body positional</content-tabs-body>
|
|
313
|
+
<content-tabs-body tab-name="c">Body C</content-tabs-body>
|
|
314
|
+
</content-tabs>`,
|
|
315
|
+
);
|
|
316
|
+
const headers = tabs.querySelectorAll("content-tabs-header");
|
|
317
|
+
await settle();
|
|
318
|
+
expect(tabs.provision).toEqual({
|
|
319
|
+
tabType: "multi",
|
|
320
|
+
openTabs: ["a"],
|
|
321
|
+
activeTab: "a",
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await clickHeader(headers[2]);
|
|
325
|
+
await clickHeader(headers[1]);
|
|
326
|
+
expect(tabs.provision).toEqual({
|
|
327
|
+
tabType: "multi",
|
|
328
|
+
openTabs: ["a", 1, "c"],
|
|
329
|
+
activeTab: "a",
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await clickHeader(headers[0]);
|
|
333
|
+
expect(tabs.provision?.activeTab).toBe(1);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("excludes nested groups from the outer provision (and vice versa)", async () => {
|
|
337
|
+
const outer = fixture<HTMLContentTabsElement>(
|
|
338
|
+
`<content-tabs id="outer">
|
|
339
|
+
<content-tabs-header is-open>Outer 1</content-tabs-header>
|
|
340
|
+
<content-tabs-header>Outer 2</content-tabs-header>
|
|
341
|
+
<content-tabs-body is-open>
|
|
342
|
+
<content-tabs id="inner">
|
|
343
|
+
<content-tabs-header>Inner 1</content-tabs-header>
|
|
344
|
+
<content-tabs-header is-open>Inner 2</content-tabs-header>
|
|
345
|
+
<content-tabs-body>Inner content 1</content-tabs-body>
|
|
346
|
+
<content-tabs-body is-open>Inner content 2</content-tabs-body>
|
|
347
|
+
</content-tabs>
|
|
348
|
+
</content-tabs-body>
|
|
349
|
+
<content-tabs-body>Outer content 2</content-tabs-body>
|
|
350
|
+
</content-tabs>`,
|
|
351
|
+
);
|
|
352
|
+
const inner = outer.querySelector("#inner") as HTMLContentTabsElement;
|
|
353
|
+
const innerHeaders = [...inner.querySelectorAll("content-tabs-header")];
|
|
354
|
+
await settle();
|
|
355
|
+
|
|
356
|
+
expect(outer.provision).toEqual({
|
|
357
|
+
tabType: null,
|
|
358
|
+
openTabs: [0],
|
|
359
|
+
activeTab: 0,
|
|
360
|
+
});
|
|
361
|
+
expect(inner.provision).toEqual({
|
|
362
|
+
tabType: null,
|
|
363
|
+
openTabs: [1],
|
|
364
|
+
activeTab: 1,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await clickHeader(innerHeaders[0]);
|
|
368
|
+
expect(inner.provision?.activeTab).toBe(0);
|
|
369
|
+
expect(outer.provision?.activeTab).toBe(0);
|
|
370
|
+
expect(outer.provision?.openTabs).toEqual([0]);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("content-tabs (unpaired headers)", () => {
|
|
375
|
+
afterEach(() => {
|
|
376
|
+
document.body.innerHTML = "";
|
|
377
|
+
vi.restoreAllMocks();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("tolerates a named header without a matching body", async () => {
|
|
381
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
382
|
+
`<content-tabs>
|
|
383
|
+
<content-tabs-header tab-name="a">A</content-tabs-header>
|
|
384
|
+
<content-tabs-header tab-name="orphan">Orphan</content-tabs-header>
|
|
385
|
+
<content-tabs-body tab-name="a">Body A</content-tabs-body>
|
|
386
|
+
</content-tabs>`,
|
|
387
|
+
);
|
|
388
|
+
const headerA = tabs.querySelector('content-tabs-header[tab-name="a"]')!;
|
|
389
|
+
const orphan = tabs.querySelector(
|
|
390
|
+
'content-tabs-header[tab-name="orphan"]',
|
|
391
|
+
)!;
|
|
392
|
+
const bodyA = tabs.querySelector('content-tabs-body[tab-name="a"]')!;
|
|
393
|
+
|
|
394
|
+
await clickHeader(orphan);
|
|
395
|
+
expect(orphan).dom.to.equalTag(
|
|
396
|
+
`<content-tabs-header tab-name="orphan" is-open></content-tabs-header>`,
|
|
397
|
+
);
|
|
398
|
+
expect(bodyA).dom.to.equalTag(
|
|
399
|
+
`<content-tabs-body tab-name="a"></content-tabs-body>`,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
await clickHeader(headerA);
|
|
403
|
+
expect(orphan).dom.to.equalTag(
|
|
404
|
+
`<content-tabs-header tab-name="orphan"></content-tabs-header>`,
|
|
405
|
+
);
|
|
406
|
+
expect(bodyA).dom.to.equalTag(
|
|
407
|
+
`<content-tabs-body tab-name="a" is-open></content-tabs-body>`,
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("tolerates more positional headers than bodies", async () => {
|
|
412
|
+
const tabs = fixture<HTMLContentTabsElement>(
|
|
413
|
+
`<content-tabs>
|
|
414
|
+
<content-tabs-header>One</content-tabs-header>
|
|
415
|
+
<content-tabs-header>Two</content-tabs-header>
|
|
416
|
+
<content-tabs-body>Body one</content-tabs-body>
|
|
417
|
+
</content-tabs>`,
|
|
418
|
+
);
|
|
419
|
+
const { header2, body1 } = queryParts(tabs);
|
|
420
|
+
await clickHeader(header2);
|
|
421
|
+
expect(header2).dom.to.equalTag(
|
|
422
|
+
`<content-tabs-header is-open></content-tabs-header>`,
|
|
423
|
+
);
|
|
424
|
+
expect(body1).dom.to.equalTag(`<content-tabs-body></content-tabs-body>`);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
wait,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
10
|
+
|
|
11
|
+
describe("file-tabs view", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = "";
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("switches file tab bodies", async () => {
|
|
17
|
+
const { root } = await mountView(readDemo(import.meta.url, "file-tabs"));
|
|
18
|
+
const headers = root.querySelectorAll("content-tabs-header");
|
|
19
|
+
const bodies = root.querySelectorAll("content-tabs-body");
|
|
20
|
+
expect(root.classList.contains("file-tabs")).toBe(true);
|
|
21
|
+
headers[1].dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
22
|
+
await wait(0);
|
|
23
|
+
expect(bodies[1].hasAttribute("is-open")).toBe(true);
|
|
24
|
+
expect(bodies[1].textContent).toMatch(/double/);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
wait,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
10
|
+
|
|
11
|
+
describe("multi view", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = "";
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("keeps multiple accordion sections open", async () => {
|
|
17
|
+
const { root } = await mountView(readDemo(import.meta.url, "multi"));
|
|
18
|
+
const headers = root.querySelectorAll("content-tabs-header");
|
|
19
|
+
const bodies = root.querySelectorAll("content-tabs-body");
|
|
20
|
+
expect(root.getAttribute("tab-type")).toBe("multi");
|
|
21
|
+
expect(headers[0].hasAttribute("is-open")).toBe(true);
|
|
22
|
+
headers[1].dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
23
|
+
await wait(0);
|
|
24
|
+
expect(headers[0].hasAttribute("is-open")).toBe(true);
|
|
25
|
+
expect(headers[1].hasAttribute("is-open")).toBe(true);
|
|
26
|
+
expect(bodies[0].hasAttribute("is-open")).toBe(true);
|
|
27
|
+
expect(bodies[1].hasAttribute("is-open")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
wait,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
10
|
+
|
|
11
|
+
describe("named view", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = "";
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("pairs headers and bodies by tab-name", async () => {
|
|
17
|
+
const { root } = await mountView(readDemo(import.meta.url, "named"));
|
|
18
|
+
const headerB = root.querySelector('content-tabs-header[tab-name="b"]')!;
|
|
19
|
+
const bodyA = root.querySelector('content-tabs-body[tab-name="a"]')!;
|
|
20
|
+
const bodyB = root.querySelector('content-tabs-body[tab-name="b"]')!;
|
|
21
|
+
expect(bodyA.hasAttribute("is-open")).toBe(true);
|
|
22
|
+
headerB.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
23
|
+
await wait(0);
|
|
24
|
+
expect(bodyB.hasAttribute("is-open")).toBe(true);
|
|
25
|
+
expect(bodyA.hasAttribute("is-open")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
wait,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
10
|
+
|
|
11
|
+
describe("simple view", () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = "";
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("switches the open tab", async () => {
|
|
17
|
+
const { root } = await mountView(readDemo(import.meta.url, "simple"));
|
|
18
|
+
const headers = root.querySelectorAll("content-tabs-header");
|
|
19
|
+
const bodies = root.querySelectorAll("content-tabs-body");
|
|
20
|
+
expect(headers[0].hasAttribute("is-open")).toBe(true);
|
|
21
|
+
expect(bodies[0].hasAttribute("is-open")).toBe(true);
|
|
22
|
+
headers[1].dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
23
|
+
await wait(0);
|
|
24
|
+
expect(headers[1].hasAttribute("is-open")).toBe(true);
|
|
25
|
+
expect(bodies[1].hasAttribute("is-open")).toBe(true);
|
|
26
|
+
expect(headers[0].hasAttribute("is-open")).toBe(false);
|
|
27
|
+
expect(bodies[0].hasAttribute("is-open")).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|