@pie-lib/test-utils 0.22.1 → 0.22.2-next.164
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/CHANGELOG.md +6 -64
- package/README.md +463 -0
- package/lib/index.js +255 -23
- package/lib/index.js.map +1 -1
- package/lib/keyboard.js +173 -0
- package/lib/keyboard.js.map +1 -0
- package/lib/web-components.js +248 -0
- package/lib/web-components.js.map +1 -0
- package/package.json +13 -5
- package/src/__tests__/index.test.js +88 -41
- package/src/__tests__/keyboard.test.js +116 -0
- package/src/index.js +132 -11
- package/src/keyboard.js +126 -0
- package/src/web-components.js +200 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.createCustomElement = createCustomElement;
|
|
8
|
+
exports.dispatchCustomEvent = dispatchCustomEvent;
|
|
9
|
+
exports.isCustomElementDefined = isCustomElementDefined;
|
|
10
|
+
exports.renderWebComponent = renderWebComponent;
|
|
11
|
+
exports.waitForCustomElement = waitForCustomElement;
|
|
12
|
+
exports.waitForEvent = waitForEvent;
|
|
13
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
14
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
15
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
16
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
17
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
18
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
19
|
+
// Note: These helpers are for light DOM web components (no Shadow DOM)
|
|
20
|
+
// Standard React Testing Library queries work directly on these components
|
|
21
|
+
/**
|
|
22
|
+
* Wait for a custom element to be defined
|
|
23
|
+
* Custom elements are registered asynchronously
|
|
24
|
+
*
|
|
25
|
+
* @param {string} tagName - Custom element tag name (e.g., 'my-component')
|
|
26
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* await waitForCustomElement('pie-chart');
|
|
31
|
+
* const chart = document.createElement('pie-chart');
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* await waitForCustomElement('my-component', 5000);
|
|
35
|
+
*/
|
|
36
|
+
function waitForCustomElement(_x) {
|
|
37
|
+
return _waitForCustomElement.apply(this, arguments);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Render a web component and wait for it to be ready
|
|
41
|
+
* Handles the full lifecycle: wait for definition, create, append, wait for render
|
|
42
|
+
*
|
|
43
|
+
* @param {string} tagName - Custom element tag name
|
|
44
|
+
* @param {Object} attributes - Attributes to set on the element
|
|
45
|
+
* @param {Object} properties - Properties to set on the element
|
|
46
|
+
* @param {HTMLElement} container - Container to append to (defaults to document.body)
|
|
47
|
+
* @returns {Promise<HTMLElement>} The custom element
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* const chart = await renderWebComponent('pie-chart', {
|
|
51
|
+
* type: 'bar',
|
|
52
|
+
* 'data-testid': 'my-chart'
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const button = await renderWebComponent('custom-button',
|
|
57
|
+
* { 'aria-label': 'Submit' },
|
|
58
|
+
* { onClick: jest.fn() }
|
|
59
|
+
* );
|
|
60
|
+
*/
|
|
61
|
+
function _waitForCustomElement() {
|
|
62
|
+
_waitForCustomElement = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(tagName) {
|
|
63
|
+
var timeout,
|
|
64
|
+
_args = arguments;
|
|
65
|
+
return _regenerator["default"].wrap(function (_context) {
|
|
66
|
+
while (1) switch (_context.prev = _context.next) {
|
|
67
|
+
case 0:
|
|
68
|
+
timeout = _args.length > 1 && _args[1] !== undefined ? _args[1] : 3000;
|
|
69
|
+
if (!customElements.get(tagName)) {
|
|
70
|
+
_context.next = 1;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
return _context.abrupt("return");
|
|
74
|
+
case 1:
|
|
75
|
+
return _context.abrupt("return", new Promise(function (resolve, reject) {
|
|
76
|
+
var timer = setTimeout(function () {
|
|
77
|
+
reject(new Error("Custom element '".concat(tagName, "' not defined within ").concat(timeout, "ms. ") + 'Make sure the element is registered with customElements.define().'));
|
|
78
|
+
}, timeout);
|
|
79
|
+
customElements.whenDefined(tagName).then(function () {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolve();
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
case 2:
|
|
85
|
+
case "end":
|
|
86
|
+
return _context.stop();
|
|
87
|
+
}
|
|
88
|
+
}, _callee);
|
|
89
|
+
}));
|
|
90
|
+
return _waitForCustomElement.apply(this, arguments);
|
|
91
|
+
}
|
|
92
|
+
function renderWebComponent(_x2) {
|
|
93
|
+
return _renderWebComponent.apply(this, arguments);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Dispatch a custom event on an element
|
|
97
|
+
* Web components often use custom events for communication
|
|
98
|
+
*
|
|
99
|
+
* @param {HTMLElement} element - Element to dispatch event from
|
|
100
|
+
* @param {string} eventName - Event name (e.g., 'change', 'custom-event')
|
|
101
|
+
* @param {*} detail - Event detail data
|
|
102
|
+
* @param {Object} options - Event options (bubbles, composed, etc.)
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* dispatchCustomEvent(chart, 'data-changed', { value: [1, 2, 3] });
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* dispatchCustomEvent(button, 'custom-click', null, { bubbles: false });
|
|
109
|
+
*/
|
|
110
|
+
function _renderWebComponent() {
|
|
111
|
+
_renderWebComponent = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(tagName) {
|
|
112
|
+
var attributes,
|
|
113
|
+
properties,
|
|
114
|
+
container,
|
|
115
|
+
element,
|
|
116
|
+
_args2 = arguments;
|
|
117
|
+
return _regenerator["default"].wrap(function (_context2) {
|
|
118
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
119
|
+
case 0:
|
|
120
|
+
attributes = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
|
|
121
|
+
properties = _args2.length > 2 && _args2[2] !== undefined ? _args2[2] : {};
|
|
122
|
+
container = _args2.length > 3 && _args2[3] !== undefined ? _args2[3] : document.body;
|
|
123
|
+
_context2.next = 1;
|
|
124
|
+
return waitForCustomElement(tagName);
|
|
125
|
+
case 1:
|
|
126
|
+
element = document.createElement(tagName); // Set attributes (strings)
|
|
127
|
+
Object.entries(attributes).forEach(function (_ref3) {
|
|
128
|
+
var _ref4 = (0, _slicedToArray2["default"])(_ref3, 2),
|
|
129
|
+
key = _ref4[0],
|
|
130
|
+
value = _ref4[1];
|
|
131
|
+
element.setAttribute(key, value);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Set properties (objects, functions, etc.)
|
|
135
|
+
Object.entries(properties).forEach(function (_ref5) {
|
|
136
|
+
var _ref6 = (0, _slicedToArray2["default"])(_ref5, 2),
|
|
137
|
+
key = _ref6[0],
|
|
138
|
+
value = _ref6[1];
|
|
139
|
+
element[key] = value;
|
|
140
|
+
});
|
|
141
|
+
container.appendChild(element);
|
|
142
|
+
|
|
143
|
+
// Wait for component to render (custom elements may be async)
|
|
144
|
+
_context2.next = 2;
|
|
145
|
+
return new Promise(function (resolve) {
|
|
146
|
+
return setTimeout(resolve, 0);
|
|
147
|
+
});
|
|
148
|
+
case 2:
|
|
149
|
+
return _context2.abrupt("return", element);
|
|
150
|
+
case 3:
|
|
151
|
+
case "end":
|
|
152
|
+
return _context2.stop();
|
|
153
|
+
}
|
|
154
|
+
}, _callee2);
|
|
155
|
+
}));
|
|
156
|
+
return _renderWebComponent.apply(this, arguments);
|
|
157
|
+
}
|
|
158
|
+
function dispatchCustomEvent(element, eventName) {
|
|
159
|
+
var detail = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
160
|
+
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
161
|
+
var event = new CustomEvent(eventName, _objectSpread({
|
|
162
|
+
detail: detail,
|
|
163
|
+
bubbles: true,
|
|
164
|
+
composed: true
|
|
165
|
+
}, options));
|
|
166
|
+
element.dispatchEvent(event);
|
|
167
|
+
return event;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Listen for a custom event and return a promise that resolves when fired
|
|
172
|
+
* Useful for testing event emissions
|
|
173
|
+
*
|
|
174
|
+
* @param {HTMLElement} element - Element to listen to
|
|
175
|
+
* @param {string} eventName - Event name to wait for
|
|
176
|
+
* @param {number} timeout - Timeout in milliseconds
|
|
177
|
+
* @returns {Promise<CustomEvent>} Promise that resolves with the event
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* const promise = waitForEvent(chart, 'data-loaded');
|
|
181
|
+
* chart.loadData();
|
|
182
|
+
* const event = await promise;
|
|
183
|
+
* expect(event.detail).toEqual({ loaded: true });
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* await waitForEvent(component, 'ready', 5000);
|
|
187
|
+
*/
|
|
188
|
+
function waitForEvent(element, eventName) {
|
|
189
|
+
var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 3000;
|
|
190
|
+
return new Promise(function (resolve, reject) {
|
|
191
|
+
var timer = setTimeout(function () {
|
|
192
|
+
element.removeEventListener(eventName, _handler);
|
|
193
|
+
reject(new Error("Event '".concat(eventName, "' not fired within ").concat(timeout, "ms")));
|
|
194
|
+
}, timeout);
|
|
195
|
+
var _handler = function handler(event) {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
element.removeEventListener(eventName, _handler);
|
|
198
|
+
resolve(event);
|
|
199
|
+
};
|
|
200
|
+
element.addEventListener(eventName, _handler);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if a custom element is defined
|
|
206
|
+
* Useful for verifying element registration
|
|
207
|
+
*
|
|
208
|
+
* @param {string} tagName - Custom element tag name
|
|
209
|
+
* @returns {boolean} True if element is defined
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* if (isCustomElementDefined('pie-chart')) {
|
|
213
|
+
* // Element is ready to use
|
|
214
|
+
* }
|
|
215
|
+
*/
|
|
216
|
+
function isCustomElementDefined(tagName) {
|
|
217
|
+
return typeof customElements !== 'undefined' && customElements.get(tagName) !== undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Helper to create and configure a custom element
|
|
222
|
+
* For light DOM web components that render React
|
|
223
|
+
*
|
|
224
|
+
* @param {string} tagName - Custom element tag name
|
|
225
|
+
* @param {Object} props - Props to pass to the element
|
|
226
|
+
* @returns {HTMLElement} The custom element
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* const chart = createCustomElement('pie-chart', {
|
|
230
|
+
* data: [1, 2, 3],
|
|
231
|
+
* type: 'bar'
|
|
232
|
+
* });
|
|
233
|
+
* document.body.appendChild(chart);
|
|
234
|
+
*/
|
|
235
|
+
function createCustomElement(tagName) {
|
|
236
|
+
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
237
|
+
var element = document.createElement(tagName);
|
|
238
|
+
|
|
239
|
+
// Set properties directly on the element
|
|
240
|
+
Object.entries(props).forEach(function (_ref) {
|
|
241
|
+
var _ref2 = (0, _slicedToArray2["default"])(_ref, 2),
|
|
242
|
+
key = _ref2[0],
|
|
243
|
+
value = _ref2[1];
|
|
244
|
+
element[key] = value;
|
|
245
|
+
});
|
|
246
|
+
return element;
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=web-components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-components.js","names":["waitForCustomElement","_x","_waitForCustomElement","apply","arguments","_asyncToGenerator2","_regenerator","mark","_callee","tagName","timeout","_args","wrap","_context","prev","next","length","undefined","customElements","get","abrupt","Promise","resolve","reject","timer","setTimeout","Error","concat","whenDefined","then","clearTimeout","stop","renderWebComponent","_x2","_renderWebComponent","_callee2","attributes","properties","container","element","_args2","_context2","document","body","createElement","Object","entries","forEach","_ref3","_ref4","_slicedToArray2","key","value","setAttribute","_ref5","_ref6","appendChild","dispatchCustomEvent","eventName","detail","options","event","CustomEvent","_objectSpread","bubbles","composed","dispatchEvent","waitForEvent","removeEventListener","handler","addEventListener","isCustomElementDefined","createCustomElement","props","_ref","_ref2"],"sources":["../src/web-components.js"],"sourcesContent":["// Note: These helpers are for light DOM web components (no Shadow DOM)\n// Standard React Testing Library queries work directly on these components\n\n/**\n * Wait for a custom element to be defined\n * Custom elements are registered asynchronously\n *\n * @param {string} tagName - Custom element tag name (e.g., 'my-component')\n * @param {number} timeout - Timeout in milliseconds\n * @returns {Promise<void>}\n *\n * @example\n * await waitForCustomElement('pie-chart');\n * const chart = document.createElement('pie-chart');\n *\n * @example\n * await waitForCustomElement('my-component', 5000);\n */\nexport async function waitForCustomElement(tagName, timeout = 3000) {\n if (customElements.get(tagName)) {\n return;\n }\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `Custom element '${tagName}' not defined within ${timeout}ms. ` +\n 'Make sure the element is registered with customElements.define().'\n )\n );\n }, timeout);\n\n customElements.whenDefined(tagName).then(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n}\n\n/**\n * Render a web component and wait for it to be ready\n * Handles the full lifecycle: wait for definition, create, append, wait for render\n *\n * @param {string} tagName - Custom element tag name\n * @param {Object} attributes - Attributes to set on the element\n * @param {Object} properties - Properties to set on the element\n * @param {HTMLElement} container - Container to append to (defaults to document.body)\n * @returns {Promise<HTMLElement>} The custom element\n *\n * @example\n * const chart = await renderWebComponent('pie-chart', {\n * type: 'bar',\n * 'data-testid': 'my-chart'\n * });\n *\n * @example\n * const button = await renderWebComponent('custom-button',\n * { 'aria-label': 'Submit' },\n * { onClick: jest.fn() }\n * );\n */\nexport async function renderWebComponent(\n tagName,\n attributes = {},\n properties = {},\n container = document.body\n) {\n await waitForCustomElement(tagName);\n\n const element = document.createElement(tagName);\n\n // Set attributes (strings)\n Object.entries(attributes).forEach(([key, value]) => {\n element.setAttribute(key, value);\n });\n\n // Set properties (objects, functions, etc.)\n Object.entries(properties).forEach(([key, value]) => {\n element[key] = value;\n });\n\n container.appendChild(element);\n\n // Wait for component to render (custom elements may be async)\n await new Promise((resolve) => setTimeout(resolve, 0));\n\n return element;\n}\n\n/**\n * Dispatch a custom event on an element\n * Web components often use custom events for communication\n *\n * @param {HTMLElement} element - Element to dispatch event from\n * @param {string} eventName - Event name (e.g., 'change', 'custom-event')\n * @param {*} detail - Event detail data\n * @param {Object} options - Event options (bubbles, composed, etc.)\n *\n * @example\n * dispatchCustomEvent(chart, 'data-changed', { value: [1, 2, 3] });\n *\n * @example\n * dispatchCustomEvent(button, 'custom-click', null, { bubbles: false });\n */\nexport function dispatchCustomEvent(\n element,\n eventName,\n detail = null,\n options = {}\n) {\n const event = new CustomEvent(eventName, {\n detail,\n bubbles: true,\n composed: true, // Allow event to cross shadow DOM boundary\n ...options,\n });\n\n element.dispatchEvent(event);\n return event;\n}\n\n/**\n * Listen for a custom event and return a promise that resolves when fired\n * Useful for testing event emissions\n *\n * @param {HTMLElement} element - Element to listen to\n * @param {string} eventName - Event name to wait for\n * @param {number} timeout - Timeout in milliseconds\n * @returns {Promise<CustomEvent>} Promise that resolves with the event\n *\n * @example\n * const promise = waitForEvent(chart, 'data-loaded');\n * chart.loadData();\n * const event = await promise;\n * expect(event.detail).toEqual({ loaded: true });\n *\n * @example\n * await waitForEvent(component, 'ready', 5000);\n */\nexport function waitForEvent(element, eventName, timeout = 3000) {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n element.removeEventListener(eventName, handler);\n reject(\n new Error(`Event '${eventName}' not fired within ${timeout}ms`)\n );\n }, timeout);\n\n const handler = (event) => {\n clearTimeout(timer);\n element.removeEventListener(eventName, handler);\n resolve(event);\n };\n\n element.addEventListener(eventName, handler);\n });\n}\n\n/**\n * Check if a custom element is defined\n * Useful for verifying element registration\n *\n * @param {string} tagName - Custom element tag name\n * @returns {boolean} True if element is defined\n *\n * @example\n * if (isCustomElementDefined('pie-chart')) {\n * // Element is ready to use\n * }\n */\nexport function isCustomElementDefined(tagName) {\n return typeof customElements !== 'undefined' && customElements.get(tagName) !== undefined;\n}\n\n/**\n * Helper to create and configure a custom element\n * For light DOM web components that render React\n *\n * @param {string} tagName - Custom element tag name\n * @param {Object} props - Props to pass to the element\n * @returns {HTMLElement} The custom element\n *\n * @example\n * const chart = createCustomElement('pie-chart', {\n * data: [1, 2, 3],\n * type: 'bar'\n * });\n * document.body.appendChild(chart);\n */\nexport function createCustomElement(tagName, props = {}) {\n const element = document.createElement(tagName);\n\n // Set properties directly on the element\n Object.entries(props).forEach(([key, value]) => {\n element[key] = value;\n });\n\n return element;\n}\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-lib/test-utils",
|
|
3
|
-
"version": "0.22.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.22.2-next.164+09821f09",
|
|
4
|
+
"description": "Testing utilities for pie-lib packages with React Testing Library and MUI support",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
7
7
|
"publishConfig": {
|
|
@@ -10,9 +10,17 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"
|
|
14
|
-
"react": "^16.
|
|
13
|
+
"@mui/material": "^7.3.4",
|
|
14
|
+
"@testing-library/react": "^16.3.0",
|
|
15
|
+
"react": "^18.2.0",
|
|
16
|
+
"react-dom": "^18.2.0"
|
|
15
17
|
},
|
|
16
|
-
"
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@emotion/react": "^11.14.0",
|
|
20
|
+
"@emotion/styled": "^11.11.0",
|
|
21
|
+
"@testing-library/jest-dom": "^5.16.5",
|
|
22
|
+
"@testing-library/user-event": "^14.5.2"
|
|
23
|
+
},
|
|
24
|
+
"gitHead": "09821f09cfcaee178971c57f4134645350be2222",
|
|
17
25
|
"scripts": {}
|
|
18
26
|
}
|
|
@@ -1,45 +1,92 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
expect(
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(
|
|
42
|
-
|
|
2
|
+
import { renderWithTheme, renderWithProviders, screen, createTestTheme } from '../index';
|
|
3
|
+
import { Button } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
describe('@pie-lib/test-utils', () => {
|
|
6
|
+
describe('renderWithTheme', () => {
|
|
7
|
+
it('renders a component with default MUI theme', () => {
|
|
8
|
+
renderWithTheme(<Button>Click me</Button>);
|
|
9
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
10
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders a component with custom theme', () => {
|
|
14
|
+
const customTheme = createTestTheme({
|
|
15
|
+
palette: {
|
|
16
|
+
primary: {
|
|
17
|
+
main: '#ff0000',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
renderWithTheme(<Button color="primary">Themed Button</Button>, {
|
|
23
|
+
theme: customTheme,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('accepts additional render options', () => {
|
|
30
|
+
const { container } = renderWithTheme(<Button>Test</Button>, {
|
|
31
|
+
container: document.body,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(container).toBe(document.body);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('renderWithProviders', () => {
|
|
39
|
+
it('renders with theme provider by default', () => {
|
|
40
|
+
renderWithProviders(<Button>Provided Button</Button>);
|
|
41
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders with additional providers', () => {
|
|
45
|
+
const TestProvider = ({ children }) => (
|
|
46
|
+
<div data-testid="test-provider">{children}</div>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
renderWithProviders(<Button>Multi Provider</Button>, {
|
|
50
|
+
providers: [TestProvider],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(screen.getByTestId('test-provider')).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('createTestTheme', () => {
|
|
59
|
+
it('creates a theme with custom options', () => {
|
|
60
|
+
const theme = createTestTheme({
|
|
61
|
+
palette: {
|
|
62
|
+
mode: 'dark',
|
|
63
|
+
primary: {
|
|
64
|
+
main: '#90caf9',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(theme.palette.mode).toBe('dark');
|
|
70
|
+
expect(theme.palette.primary.main).toBe('#90caf9');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('creates a theme with default options when none provided', () => {
|
|
74
|
+
const theme = createTestTheme();
|
|
75
|
+
expect(theme.palette).toBeDefined();
|
|
76
|
+
expect(theme.spacing).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('RTL re-exports', () => {
|
|
81
|
+
it('exports screen utility', () => {
|
|
82
|
+
renderWithTheme(<div>Test Content</div>);
|
|
83
|
+
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('exports render function', async () => {
|
|
87
|
+
const { render, screen: screenImport } = await import('../index');
|
|
88
|
+
render(<div>Direct Render</div>);
|
|
89
|
+
expect(screenImport.getByText('Direct Render')).toBeInTheDocument();
|
|
43
90
|
});
|
|
44
91
|
});
|
|
45
92
|
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Keys, pressKey, typeAndSubmit, clearAndType, navigateWithKeys } from '../keyboard';
|
|
4
|
+
|
|
5
|
+
describe('Keyboard helpers', () => {
|
|
6
|
+
describe('Keys constant', () => {
|
|
7
|
+
it('exports common key codes', () => {
|
|
8
|
+
expect(Keys.ENTER).toBe(13);
|
|
9
|
+
expect(Keys.ESCAPE).toBe(27);
|
|
10
|
+
expect(Keys.SPACE).toBe(32);
|
|
11
|
+
expect(Keys.TAB).toBe(9);
|
|
12
|
+
expect(Keys.ARROW_DOWN).toBe(40);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('pressKey', () => {
|
|
17
|
+
it('dispatches keyboard event with keyCode', () => {
|
|
18
|
+
const onKeyDown = jest.fn();
|
|
19
|
+
render(<input onKeyDown={onKeyDown} />);
|
|
20
|
+
const input = screen.getByRole('textbox');
|
|
21
|
+
|
|
22
|
+
pressKey(input, Keys.ENTER);
|
|
23
|
+
|
|
24
|
+
expect(onKeyDown).toHaveBeenCalled();
|
|
25
|
+
const event = onKeyDown.mock.calls[0][0];
|
|
26
|
+
expect(event.keyCode).toBe(13);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('dispatches different event types', () => {
|
|
30
|
+
const onKeyUp = jest.fn();
|
|
31
|
+
render(<input onKeyUp={onKeyUp} />);
|
|
32
|
+
const input = screen.getByRole('textbox');
|
|
33
|
+
|
|
34
|
+
pressKey(input, Keys.ESCAPE, 'keyup');
|
|
35
|
+
|
|
36
|
+
expect(onKeyUp).toHaveBeenCalled();
|
|
37
|
+
const event = onKeyUp.mock.calls[0][0];
|
|
38
|
+
expect(event.keyCode).toBe(27);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('passes additional options', () => {
|
|
42
|
+
const onKeyDown = jest.fn();
|
|
43
|
+
render(<input onKeyDown={onKeyDown} />);
|
|
44
|
+
const input = screen.getByRole('textbox');
|
|
45
|
+
|
|
46
|
+
pressKey(input, Keys.ENTER, 'keydown', { ctrlKey: true });
|
|
47
|
+
|
|
48
|
+
const event = onKeyDown.mock.calls[0][0];
|
|
49
|
+
expect(event.ctrlKey).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('typeAndSubmit', () => {
|
|
54
|
+
it('types text and presses Enter', async () => {
|
|
55
|
+
const onKeyDown = jest.fn();
|
|
56
|
+
render(<input onKeyDown={onKeyDown} />);
|
|
57
|
+
const input = screen.getByRole('textbox');
|
|
58
|
+
|
|
59
|
+
await typeAndSubmit(input, 'hello');
|
|
60
|
+
|
|
61
|
+
expect(input).toHaveValue('hello');
|
|
62
|
+
expect(onKeyDown).toHaveBeenCalled();
|
|
63
|
+
// Check that the last keyDown event was Enter
|
|
64
|
+
const lastEvent = onKeyDown.mock.calls[onKeyDown.mock.calls.length - 1][0];
|
|
65
|
+
expect(lastEvent.keyCode).toBe(Keys.ENTER);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('clearAndType', () => {
|
|
70
|
+
it('clears existing value and types new text', async () => {
|
|
71
|
+
render(<input defaultValue="old value" />);
|
|
72
|
+
const input = screen.getByRole('textbox');
|
|
73
|
+
|
|
74
|
+
await clearAndType(input, 'new value');
|
|
75
|
+
|
|
76
|
+
expect(input).toHaveValue('new value');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('navigateWithKeys', () => {
|
|
81
|
+
it('navigates down with arrow keys', () => {
|
|
82
|
+
const onKeyDown = jest.fn();
|
|
83
|
+
render(<div role="listbox" onKeyDown={onKeyDown} />);
|
|
84
|
+
const listbox = screen.getByRole('listbox');
|
|
85
|
+
|
|
86
|
+
navigateWithKeys(listbox, 3, 'vertical');
|
|
87
|
+
|
|
88
|
+
expect(onKeyDown).toHaveBeenCalledTimes(3);
|
|
89
|
+
const events = onKeyDown.mock.calls.map((call) => call[0].keyCode);
|
|
90
|
+
expect(events).toEqual([Keys.ARROW_DOWN, Keys.ARROW_DOWN, Keys.ARROW_DOWN]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('navigates up with negative steps', () => {
|
|
94
|
+
const onKeyDown = jest.fn();
|
|
95
|
+
render(<div role="listbox" onKeyDown={onKeyDown} />);
|
|
96
|
+
const listbox = screen.getByRole('listbox');
|
|
97
|
+
|
|
98
|
+
navigateWithKeys(listbox, -2, 'vertical');
|
|
99
|
+
|
|
100
|
+
expect(onKeyDown).toHaveBeenCalledTimes(2);
|
|
101
|
+
const events = onKeyDown.mock.calls.map((call) => call[0].keyCode);
|
|
102
|
+
expect(events).toEqual([Keys.ARROW_UP, Keys.ARROW_UP]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('navigates horizontally', () => {
|
|
106
|
+
const onKeyDown = jest.fn();
|
|
107
|
+
render(<div role="tablist" onKeyDown={onKeyDown} />);
|
|
108
|
+
const tablist = screen.getByRole('tablist');
|
|
109
|
+
|
|
110
|
+
navigateWithKeys(tablist, 2, 'horizontal');
|
|
111
|
+
|
|
112
|
+
const events = onKeyDown.mock.calls.map((call) => call[0].keyCode);
|
|
113
|
+
expect(events).toEqual([Keys.ARROW_RIGHT, Keys.ARROW_RIGHT]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|