Skip to content

Commit c6d239e

Browse files
authored
test: Refactor test suite (#119)
* test: Create dedicated options & static props test sections * test: Remove a few extraneous async's * test: Further suite refactoring * test: Finish up refactor
1 parent 0a8ccf7 commit c6d239e

File tree

7 files changed

+606
-471
lines changed

7 files changed

+606
-471
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"build": "microbundle -f cjs,es,umd --no-generateTypes",
1313
"lint": "eslint src/*.js",
1414
"test": "npm run test:types & npm run test:browser",
15-
"test:browser": "wtr test/*.test.{js,jsx}",
16-
"test:types": "tsc -p test/",
15+
"test:browser": "wtr test/browser/*.test.{js,jsx}",
16+
"test:types": "tsc -p test/types/",
1717
"prettier": "prettier **/*.{js,jsx} --write",
1818
"prepublishOnly": "npm run build && npm run lint && npm run test"
1919
},

test/browser/index.test.jsx

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { assert } from '@open-wc/testing';
2+
import { h, createContext, Fragment } from 'preact';
3+
import { useContext } from 'preact/hooks';
4+
import { act } from 'preact/test-utils';
5+
import registerElement from '../../src/index';
6+
7+
describe('web components', () => {
8+
/** @type {HTMLDivElement} */
9+
let root;
10+
11+
beforeEach(() => {
12+
root = document.createElement('div');
13+
document.body.appendChild(root);
14+
});
15+
16+
afterEach(() => {
17+
document.body.removeChild(root);
18+
});
19+
20+
it('renders ok, updates on attr change', () => {
21+
function Clock({ time }) {
22+
return <span>{time}</span>;
23+
}
24+
25+
registerElement(Clock, 'x-clock', ['time']);
26+
27+
const el = document.createElement('x-clock');
28+
el.setAttribute('time', '10:28:57 PM');
29+
30+
root.appendChild(el);
31+
assert.equal(
32+
root.innerHTML,
33+
'<x-clock time="10:28:57 PM"><span>10:28:57 PM</span></x-clock>'
34+
);
35+
36+
el.setAttribute('time', '11:01:10 AM');
37+
assert.equal(
38+
root.innerHTML,
39+
'<x-clock time="11:01:10 AM"><span>11:01:10 AM</span></x-clock>'
40+
);
41+
});
42+
43+
// #50
44+
it('remove attributes without crashing', () => {
45+
function NullProps({ size = 'md' }) {
46+
return <div>{size.toUpperCase()}</div>;
47+
}
48+
49+
registerElement(NullProps, 'x-null-props', ['size'], { shadow: true });
50+
51+
const el = document.createElement('x-null-props');
52+
assert.doesNotThrow(() => (el.size = 'foo'));
53+
root.appendChild(el);
54+
55+
assert.doesNotThrow(() => el.removeAttribute('size'));
56+
});
57+
58+
describe('DOM properties', () => {
59+
it('passes property changes to props', () => {
60+
function Clock({ time }) {
61+
return <span>{time}</span>;
62+
}
63+
64+
registerElement(Clock, 'x-clock-props', ['time']);
65+
66+
const el = document.createElement('x-clock-props');
67+
68+
el.time = '10:28:57 PM';
69+
assert.equal(el.time, '10:28:57 PM');
70+
71+
root.appendChild(el);
72+
assert.equal(
73+
root.innerHTML,
74+
'<x-clock-props time="10:28:57 PM"><span>10:28:57 PM</span></x-clock-props>'
75+
);
76+
77+
el.time = '11:01:10 AM';
78+
assert.equal(el.time, '11:01:10 AM');
79+
80+
assert.equal(
81+
root.innerHTML,
82+
'<x-clock-props time="11:01:10 AM"><span>11:01:10 AM</span></x-clock-props>'
83+
);
84+
});
85+
86+
function DummyButton({ onClick, text = 'click' }) {
87+
return <button onClick={onClick}>{text}</button>;
88+
}
89+
90+
registerElement(DummyButton, 'x-dummy-button', ['onClick', 'text']);
91+
92+
it('passes simple properties changes to props', () => {
93+
const el = document.createElement('x-dummy-button');
94+
95+
el.text = 'foo';
96+
assert.equal(el.text, 'foo');
97+
98+
root.appendChild(el);
99+
assert.equal(
100+
root.innerHTML,
101+
'<x-dummy-button text="foo"><button>foo</button></x-dummy-button>'
102+
);
103+
104+
// Update
105+
el.text = 'bar';
106+
assert.equal(
107+
root.innerHTML,
108+
'<x-dummy-button text="bar"><button>bar</button></x-dummy-button>'
109+
);
110+
});
111+
112+
it('passes complex properties changes to props', () => {
113+
const el = document.createElement('x-dummy-button');
114+
115+
let clicks = 0;
116+
const onClick = () => clicks++;
117+
el.onClick = onClick;
118+
assert.equal(el.onClick, onClick);
119+
120+
root.appendChild(el);
121+
assert.equal(
122+
root.innerHTML,
123+
'<x-dummy-button><button>click</button></x-dummy-button>'
124+
);
125+
126+
act(() => {
127+
el.querySelector('button').click();
128+
});
129+
assert.equal(clicks, 1);
130+
131+
// Update
132+
let other = 0;
133+
el.onClick = () => other++;
134+
act(() => {
135+
el.querySelector('button').click();
136+
});
137+
assert.equal(other, 1);
138+
});
139+
140+
it('sets complex property after other property', () => {
141+
const el = document.createElement('x-dummy-button');
142+
143+
// set simple property first
144+
el.text = 'click me';
145+
146+
let clicks = 0;
147+
const onClick = () => clicks++;
148+
el.onClick = onClick;
149+
150+
assert.equal(el.text, 'click me');
151+
assert.equal(el.onClick, onClick);
152+
153+
root.appendChild(el);
154+
assert.equal(
155+
root.innerHTML,
156+
'<x-dummy-button text="click me"><button>click me</button></x-dummy-button>'
157+
);
158+
159+
act(() => {
160+
el.querySelector('button').click();
161+
});
162+
163+
assert.equal(el.onClick, onClick);
164+
assert.equal(clicks, 1);
165+
});
166+
});
167+
168+
it('renders slots as props with shadow DOM', () => {
169+
function Slots({ text, children }) {
170+
return (
171+
<span class="wrapper">
172+
<div class="children">{children}</div>
173+
<div class="slotted">{text}</div>
174+
</span>
175+
);
176+
}
177+
178+
registerElement(Slots, 'x-slots', [], { shadow: true });
179+
180+
const el = document.createElement('x-slots');
181+
182+
// <span slot="text">here is a slot</span>
183+
const slot = document.createElement('span');
184+
slot.textContent = 'here is a slot';
185+
slot.slot = 'text';
186+
el.appendChild(slot);
187+
188+
// <div>no slot</div>
189+
const noSlot = document.createElement('div');
190+
noSlot.textContent = 'no slot';
191+
el.appendChild(noSlot);
192+
el.appendChild(slot);
193+
194+
root.appendChild(el);
195+
assert.equal(
196+
root.innerHTML,
197+
'<x-slots><div>no slot</div><span slot="text">here is a slot</span></x-slots>'
198+
);
199+
200+
const shadowHTML = document.querySelector('x-slots').shadowRoot.innerHTML;
201+
assert.equal(
202+
shadowHTML,
203+
'<span class="wrapper"><div class="children"><slot><div>no slot</div></slot></div><div class="slotted"><slot name="text"><span>here is a slot</span></slot></div></span>'
204+
);
205+
});
206+
207+
describe('attribute/property name transformation', () => {
208+
const kebabName = 'custom-date-long-name';
209+
const camelName = 'customDateLongName';
210+
const lowerName = camelName.toLowerCase();
211+
function PropNameTransform(props) {
212+
return (
213+
<span>
214+
{props[kebabName]} {props[lowerName]} {props[camelName]}
215+
</span>
216+
);
217+
}
218+
registerElement(PropNameTransform, 'x-prop-name-transform', [
219+
kebabName,
220+
camelName,
221+
]);
222+
223+
it('handles kebab-case attributes with passthrough', () => {
224+
const el = document.createElement('x-prop-name-transform');
225+
el.setAttribute(kebabName, '11/11/2011');
226+
el.setAttribute(camelName, 'pretended to be camel');
227+
228+
root.appendChild(el);
229+
assert.equal(
230+
root.innerHTML,
231+
`<x-prop-name-transform ${kebabName}="11/11/2011" ${lowerName}="pretended to be camel"><span>11/11/2011 pretended to be camel 11/11/2011</span></x-prop-name-transform>`
232+
);
233+
234+
el.setAttribute(kebabName, '01/01/2001');
235+
assert.equal(
236+
root.innerHTML,
237+
`<x-prop-name-transform ${kebabName}="01/01/2001" ${lowerName}="pretended to be camel"><span>01/01/2001 pretended to be camel 01/01/2001</span></x-prop-name-transform>`
238+
);
239+
});
240+
});
241+
242+
describe('children', () => {
243+
/** @param {string} name */
244+
function createTestElement(name) {
245+
const el = document.createElement(name);
246+
const child1 = document.createElement('p');
247+
child1.textContent = 'Child 1';
248+
const child2 = document.createElement('p');
249+
child2.textContent = 'Child 2';
250+
el.appendChild(child1);
251+
el.appendChild(child2);
252+
return el;
253+
}
254+
255+
it('supports controlling light DOM children', () => {
256+
function LightDomChildren({ children }) {
257+
return (
258+
<Fragment>
259+
<h1>Light DOM Children</h1>
260+
<div>{children}</div>
261+
</Fragment>
262+
);
263+
}
264+
265+
registerElement(LightDomChildren, 'light-dom-children', []);
266+
registerElement(LightDomChildren, 'light-dom-children-shadow-false', [], {
267+
shadow: false,
268+
});
269+
270+
root.appendChild(createTestElement('light-dom-children'));
271+
root.appendChild(createTestElement('light-dom-children-shadow-false'));
272+
273+
assert.equal(
274+
document.querySelector('light-dom-children').innerHTML,
275+
'<h1>Light DOM Children</h1><div><p>Child 1</p><p>Child 2</p></div>'
276+
);
277+
assert.equal(
278+
document.querySelector('light-dom-children-shadow-false').innerHTML,
279+
'<h1>Light DOM Children</h1><div><p>Child 1</p><p>Child 2</p></div>'
280+
);
281+
});
282+
283+
it('supports controlling shadow DOM children', () => {
284+
function ShadowDomChildren({ children }) {
285+
return (
286+
<Fragment>
287+
<h1>Light DOM Children</h1>
288+
<div>{children}</div>
289+
</Fragment>
290+
);
291+
}
292+
293+
registerElement(ShadowDomChildren, 'shadow-dom-children', [], {
294+
shadow: true,
295+
});
296+
297+
root.appendChild(createTestElement('shadow-dom-children'));
298+
299+
assert.equal(
300+
document.querySelector('shadow-dom-children').shadowRoot.innerHTML,
301+
'<h1>Light DOM Children</h1><div><slot><p>Child 1</p><p>Child 2</p></slot></div>'
302+
);
303+
});
304+
});
305+
306+
describe('context', () => {
307+
const Theme = createContext('light');
308+
309+
function DisplayTheme() {
310+
const theme = useContext(Theme);
311+
return <p>Active theme: {theme}</p>;
312+
}
313+
314+
function Parent({ children, theme = 'dark' }) {
315+
return (
316+
<Theme.Provider value={theme}>
317+
<div class="children">{children}</div>
318+
</Theme.Provider>
319+
);
320+
}
321+
322+
registerElement(Parent, 'x-parent', ['theme'], { shadow: true });
323+
registerElement(DisplayTheme, 'x-display-theme', [], { shadow: true });
324+
325+
it('passes context over custom element boundaries', () => {
326+
const el = document.createElement('x-parent');
327+
328+
const noSlot = document.createElement('x-display-theme');
329+
el.appendChild(noSlot);
330+
331+
root.appendChild(el);
332+
assert.equal(
333+
root.innerHTML,
334+
'<x-parent><x-display-theme></x-display-theme></x-parent>'
335+
);
336+
337+
const getShadowHTML = () =>
338+
document.querySelector('x-display-theme').shadowRoot.innerHTML;
339+
assert.equal(getShadowHTML(), '<p>Active theme: dark</p>');
340+
341+
// Trigger context update
342+
act(() => {
343+
el.setAttribute('theme', 'sunny');
344+
});
345+
assert.equal(getShadowHTML(), '<p>Active theme: sunny</p>');
346+
});
347+
});
348+
});

0 commit comments

Comments
 (0)