Skip to content

Commit 522436a

Browse files
committed
test: Further suite refactoring
1 parent d807e6e commit 522436a

File tree

7 files changed

+598
-564
lines changed

7 files changed

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

0 commit comments

Comments
 (0)