@pie-lib/test-utils 0.22.2-next.0 → 0.22.3-next.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.
@@ -0,0 +1,200 @@
1
+ // Note: These helpers are for light DOM web components (no Shadow DOM)
2
+ // Standard React Testing Library queries work directly on these components
3
+
4
+ /**
5
+ * Wait for a custom element to be defined
6
+ * Custom elements are registered asynchronously
7
+ *
8
+ * @param {string} tagName - Custom element tag name (e.g., 'my-component')
9
+ * @param {number} timeout - Timeout in milliseconds
10
+ * @returns {Promise<void>}
11
+ *
12
+ * @example
13
+ * await waitForCustomElement('pie-chart');
14
+ * const chart = document.createElement('pie-chart');
15
+ *
16
+ * @example
17
+ * await waitForCustomElement('my-component', 5000);
18
+ */
19
+ export async function waitForCustomElement(tagName, timeout = 3000) {
20
+ if (customElements.get(tagName)) {
21
+ return;
22
+ }
23
+
24
+ return new Promise((resolve, reject) => {
25
+ const timer = setTimeout(() => {
26
+ reject(
27
+ new Error(
28
+ `Custom element '${tagName}' not defined within ${timeout}ms. ` +
29
+ 'Make sure the element is registered with customElements.define().'
30
+ )
31
+ );
32
+ }, timeout);
33
+
34
+ customElements.whenDefined(tagName).then(() => {
35
+ clearTimeout(timer);
36
+ resolve();
37
+ });
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Render a web component and wait for it to be ready
43
+ * Handles the full lifecycle: wait for definition, create, append, wait for render
44
+ *
45
+ * @param {string} tagName - Custom element tag name
46
+ * @param {Object} attributes - Attributes to set on the element
47
+ * @param {Object} properties - Properties to set on the element
48
+ * @param {HTMLElement} container - Container to append to (defaults to document.body)
49
+ * @returns {Promise<HTMLElement>} The custom element
50
+ *
51
+ * @example
52
+ * const chart = await renderWebComponent('pie-chart', {
53
+ * type: 'bar',
54
+ * 'data-testid': 'my-chart'
55
+ * });
56
+ *
57
+ * @example
58
+ * const button = await renderWebComponent('custom-button',
59
+ * { 'aria-label': 'Submit' },
60
+ * { onClick: jest.fn() }
61
+ * );
62
+ */
63
+ export async function renderWebComponent(
64
+ tagName,
65
+ attributes = {},
66
+ properties = {},
67
+ container = document.body
68
+ ) {
69
+ await waitForCustomElement(tagName);
70
+
71
+ const element = document.createElement(tagName);
72
+
73
+ // Set attributes (strings)
74
+ Object.entries(attributes).forEach(([key, value]) => {
75
+ element.setAttribute(key, value);
76
+ });
77
+
78
+ // Set properties (objects, functions, etc.)
79
+ Object.entries(properties).forEach(([key, value]) => {
80
+ element[key] = value;
81
+ });
82
+
83
+ container.appendChild(element);
84
+
85
+ // Wait for component to render (custom elements may be async)
86
+ await new Promise((resolve) => setTimeout(resolve, 0));
87
+
88
+ return element;
89
+ }
90
+
91
+ /**
92
+ * Dispatch a custom event on an element
93
+ * Web components often use custom events for communication
94
+ *
95
+ * @param {HTMLElement} element - Element to dispatch event from
96
+ * @param {string} eventName - Event name (e.g., 'change', 'custom-event')
97
+ * @param {*} detail - Event detail data
98
+ * @param {Object} options - Event options (bubbles, composed, etc.)
99
+ *
100
+ * @example
101
+ * dispatchCustomEvent(chart, 'data-changed', { value: [1, 2, 3] });
102
+ *
103
+ * @example
104
+ * dispatchCustomEvent(button, 'custom-click', null, { bubbles: false });
105
+ */
106
+ export function dispatchCustomEvent(
107
+ element,
108
+ eventName,
109
+ detail = null,
110
+ options = {}
111
+ ) {
112
+ const event = new CustomEvent(eventName, {
113
+ detail,
114
+ bubbles: true,
115
+ composed: true, // Allow event to cross shadow DOM boundary
116
+ ...options,
117
+ });
118
+
119
+ element.dispatchEvent(event);
120
+ return event;
121
+ }
122
+
123
+ /**
124
+ * Listen for a custom event and return a promise that resolves when fired
125
+ * Useful for testing event emissions
126
+ *
127
+ * @param {HTMLElement} element - Element to listen to
128
+ * @param {string} eventName - Event name to wait for
129
+ * @param {number} timeout - Timeout in milliseconds
130
+ * @returns {Promise<CustomEvent>} Promise that resolves with the event
131
+ *
132
+ * @example
133
+ * const promise = waitForEvent(chart, 'data-loaded');
134
+ * chart.loadData();
135
+ * const event = await promise;
136
+ * expect(event.detail).toEqual({ loaded: true });
137
+ *
138
+ * @example
139
+ * await waitForEvent(component, 'ready', 5000);
140
+ */
141
+ export function waitForEvent(element, eventName, timeout = 3000) {
142
+ return new Promise((resolve, reject) => {
143
+ const timer = setTimeout(() => {
144
+ element.removeEventListener(eventName, handler);
145
+ reject(
146
+ new Error(`Event '${eventName}' not fired within ${timeout}ms`)
147
+ );
148
+ }, timeout);
149
+
150
+ const handler = (event) => {
151
+ clearTimeout(timer);
152
+ element.removeEventListener(eventName, handler);
153
+ resolve(event);
154
+ };
155
+
156
+ element.addEventListener(eventName, handler);
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Check if a custom element is defined
162
+ * Useful for verifying element registration
163
+ *
164
+ * @param {string} tagName - Custom element tag name
165
+ * @returns {boolean} True if element is defined
166
+ *
167
+ * @example
168
+ * if (isCustomElementDefined('pie-chart')) {
169
+ * // Element is ready to use
170
+ * }
171
+ */
172
+ export function isCustomElementDefined(tagName) {
173
+ return typeof customElements !== 'undefined' && customElements.get(tagName) !== undefined;
174
+ }
175
+
176
+ /**
177
+ * Helper to create and configure a custom element
178
+ * For light DOM web components that render React
179
+ *
180
+ * @param {string} tagName - Custom element tag name
181
+ * @param {Object} props - Props to pass to the element
182
+ * @returns {HTMLElement} The custom element
183
+ *
184
+ * @example
185
+ * const chart = createCustomElement('pie-chart', {
186
+ * data: [1, 2, 3],
187
+ * type: 'bar'
188
+ * });
189
+ * document.body.appendChild(chart);
190
+ */
191
+ export function createCustomElement(tagName, props = {}) {
192
+ const element = document.createElement(tagName);
193
+
194
+ // Set properties directly on the element
195
+ Object.entries(props).forEach(([key, value]) => {
196
+ element[key] = value;
197
+ });
198
+
199
+ return element;
200
+ }