@noma.to/qwik-testing-library 1.5.1 → 1.6.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/README.md CHANGED
@@ -92,6 +92,7 @@ src="https://raw.githubusercontent.com/ianlet/qwik-testing-library/main/high-vol
92
92
  - [Setup](#setup)
93
93
  - [Examples](#examples)
94
94
  - [Qwikstart](#qwikstart)
95
+ - [Testing Hooks (experimental)](#testing-hooks-experimental)
95
96
  - [Mocking Component Callbacks (experimental)](#mocking-component-callbacks-experimental)
96
97
  - [Qwik City - `server$` calls](#qwik-city---server-calls)
97
98
  - [Gotchas](#gotchas)
@@ -264,6 +265,109 @@ describe("<Counter />", () => {
264
265
  })
265
266
  ```
266
267
 
268
+ ### Testing Hooks (experimental)
269
+
270
+ > [!WARNING]
271
+ > This feature is under a testing phase and thus experimental.
272
+ > Its API may change in the future, so use it at your own risk.
273
+
274
+ `renderHook` lets you test custom hooks in isolation, without building a wrapper component by hand.
275
+ This is especially useful for library authors who need to battle-test the hooks they provide to their users.
276
+
277
+ ```tsx
278
+ // use-counter.tsx
279
+
280
+ import { $, useSignal } from "@builder.io/qwik";
281
+
282
+ export function useCounter(initial = 0) {
283
+ const count = useSignal(initial);
284
+ const increment$ = $(() => count.value++);
285
+
286
+ return { count, increment$ };
287
+ }
288
+ ```
289
+
290
+ ```tsx
291
+ // use-counter.spec.tsx
292
+
293
+ import { renderHook } from "@noma.to/qwik-testing-library";
294
+ import { useCounter } from "./use-counter";
295
+
296
+ describe("useCounter", () => {
297
+ it("should start at 0", async () => {
298
+ const { result } = await renderHook(useCounter);
299
+
300
+ expect(result.count.value).toBe(0);
301
+ });
302
+
303
+ it("should increment", async () => {
304
+ const { result } = await renderHook(useCounter);
305
+
306
+ await result.increment$();
307
+
308
+ expect(result.count.value).toBe(1);
309
+ });
310
+ });
311
+ ```
312
+
313
+ The `result` is the direct return value of your hook callback. Because Qwik signals are stable reactive
314
+ references, you can read and mutate them directly — no `.current` wrapper needed.
315
+
316
+ #### ESLint `qwik/use-method-usage`
317
+
318
+ The Qwik ESLint plugin only allows `use*` calls inside `component$` or `use*`-named functions.
319
+ This is a known limitation — a discussion is in progress with the Qwik team to relax their ESLint rule.
320
+ In the meantime, when you need to pass arguments to your hook, wrap it in a `use*`-named function to stay lint-clean:
321
+
322
+ ```tsx
323
+ // passing the hook by reference — lint-clean
324
+ const { result } = await renderHook(useCounter);
325
+
326
+ // passing arguments — extract a use*-named function
327
+ function useCounterFrom10() {
328
+ return useCounter(10);
329
+ }
330
+ const { result } = await renderHook(useCounterFrom10);
331
+ ```
332
+
333
+ #### Providing Context
334
+
335
+ If your hook depends on context, use the `wrapper` option to provide it:
336
+
337
+ ```tsx
338
+ import { renderHook } from "@noma.to/qwik-testing-library";
339
+ import { component$, createContextId, useContextProvider, useStore, Slot } from "@builder.io/qwik";
340
+ import { useTheme } from "./use-theme";
341
+
342
+ const ThemeContext = createContextId<{ mode: string }>("theme");
343
+
344
+ const ThemeProvider = component$(() => {
345
+ useContextProvider(ThemeContext, useStore({ mode: "dark" }));
346
+ return <Slot />;
347
+ });
348
+
349
+ it("should read theme from context", async () => {
350
+ const { result } = await renderHook(useTheme, {
351
+ wrapper: ThemeProvider,
352
+ });
353
+
354
+ expect(result.mode).toBe("dark");
355
+ });
356
+ ```
357
+
358
+ #### Cleanup
359
+
360
+ `renderHook` integrates with automatic cleanup, just like `render`.
361
+ You can also call `unmount()` manually if needed:
362
+
363
+ ```tsx
364
+ const { result, unmount } = await renderHook(useCounter);
365
+
366
+ // ... assertions ...
367
+
368
+ unmount();
369
+ ```
370
+
267
371
  ### Mocking Component Callbacks (experimental)
268
372
 
269
373
  > [!WARNING]
@@ -290,7 +394,7 @@ Here's an example on how to use the `mock$` function:
290
394
  // import qwik-testing methods
291
395
  import {render, screen, waitFor} from "@noma.to/qwik-testing-library";
292
396
  // import qwik-mock methods
293
- import {mock$, clearAllMock} from "@noma.to/qwik-mock";
397
+ import {clearAllMocks, mock$} from "@noma.to/qwik-mock";
294
398
  // import the userEvent methods to interact with the DOM
295
399
  import {userEvent} from "@testing-library/user-event";
296
400
 
@@ -300,9 +404,7 @@ import {Counter} from "./counter";
300
404
  // describe the test suite
301
405
  describe("<Counter />", () => {
302
406
  // initialize a mock
303
- // note: the empty callback is required but currently unused
304
- const onChangeMock = mock$(() => {
305
- });
407
+ const onChangeMock = mock$();
306
408
 
307
409
  // setup beforeEach block to run before each test
308
410
  beforeEach(() => {
@@ -325,15 +427,25 @@ describe("<Counter />", () => {
325
427
  await user.click(decrementBtn);
326
428
 
327
429
  // assert that the onChange$ callback was called with the right value
328
- // note: QRLs are async in Qwik, so we need to resolve them to verify interactions
329
430
  await waitFor(() =>
330
- expect(onChangeMock.resolve()).resolves.toHaveBeenCalledWith(-1),
431
+ expect(onChangeMock).toHaveBeenCalledWith(-1),
331
432
  );
332
433
  });
333
434
  });
334
435
  })
335
436
  ```
336
437
 
438
+ You can also provide a default implementation to `mock$`:
439
+
440
+ ```tsx
441
+ const onSubmitMock = mock$(() => "success");
442
+
443
+ await render(<SubmitForm onSubmit$={onSubmitMock} />);
444
+ await user.click(screen.getByRole("button", { name: "Submit" }));
445
+
446
+ expect(await screen.findByText("success")).toBeInTheDocument();
447
+ ```
448
+
337
449
  ### Qwik City - `server$` calls
338
450
 
339
451
  If one of your Qwik components uses `server$` calls, your tests might fail with a rather cryptic message (e.g. `QWIK
@@ -0,0 +1,28 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
+ key = keys[i];
18
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
+ get: ((k) => from[k]).bind(null, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ return to;
24
+ };
25
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
26
+ //#endregion
27
+ exports.__exportAll = __exportAll;
28
+ exports.__reExport = __reExport;
@@ -0,0 +1,27 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
+ key = keys[i];
18
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
+ get: ((k) => from[k]).bind(null, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ return to;
24
+ };
25
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
26
+ //#endregion
27
+ export { __exportAll, __reExport };
@@ -1,12 +1,16 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const qwikTestingLibrary = require("./lib/qwik-testing-library.qwik.cjs");
4
- const dom = require("@testing-library/dom");
5
- exports.cleanup = qwikTestingLibrary.cleanup;
6
- exports.render = qwikTestingLibrary.render;
7
- Object.keys(dom).forEach((k) => {
8
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
9
- enumerable: true,
10
- get: () => dom[k]
11
- });
2
+ require("./_virtual/_rolldown/runtime.qwik.cjs");
3
+ const require_qwik_testing_library = require("./lib/qwik-testing-library.qwik.cjs");
4
+ //#endregion
5
+ exports.cleanup = require_qwik_testing_library.cleanup;
6
+ exports.render = require_qwik_testing_library.render;
7
+ exports.renderHook = require_qwik_testing_library.renderHook;
8
+ var _testing_library_dom = require("@testing-library/dom");
9
+ Object.keys(_testing_library_dom).forEach(function(k) {
10
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
11
+ enumerable: true,
12
+ get: function() {
13
+ return _testing_library_dom[k];
14
+ }
15
+ });
12
16
  });
@@ -1,6 +1,5 @@
1
- import { cleanup, render } from "./lib/qwik-testing-library.qwik.mjs";
1
+ import "./_virtual/_rolldown/runtime.qwik.mjs";
2
+ import { cleanup, render, renderHook } from "./lib/qwik-testing-library.qwik.mjs";
2
3
  export * from "@testing-library/dom";
3
- export {
4
- cleanup,
5
- render
6
- };
4
+ //#endregion
5
+ export { cleanup, render, renderHook };
@@ -1,106 +1,90 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
- const jsxRuntime = require("@builder.io/qwik/jsx-runtime");
26
- const dom = require("@testing-library/dom");
27
- const server = require("@builder.io/qwik/server");
1
+ require("../_virtual/_rolldown/runtime.qwik.cjs");
2
+ let _testing_library_dom = require("@testing-library/dom");
3
+ let _builder_io_qwik_server = require("@builder.io/qwik/server");
4
+ let _builder_io_qwik_jsx_runtime = require("@builder.io/qwik/jsx-runtime");
5
+ //#region src/lib/qwik-testing-library.tsx
28
6
  if (typeof HTMLTemplateElement !== "undefined") {
29
- const testTemplate = document.createElement("template");
30
- const testNode = document.createComment("test");
31
- testTemplate.insertBefore(testNode, null);
32
- const needsPatch = testTemplate.childNodes.length === 0;
33
- testNode.remove();
34
- if (needsPatch) {
35
- Object.defineProperty(HTMLTemplateElement.prototype, "childNodes", {
36
- get() {
37
- return this.content.childNodes;
38
- }
39
- });
40
- }
7
+ const testTemplate = document.createElement("template");
8
+ const testNode = document.createComment("test");
9
+ testTemplate.insertBefore(testNode, null);
10
+ const needsPatch = testTemplate.childNodes.length === 0;
11
+ testNode.remove();
12
+ if (needsPatch) Object.defineProperty(HTMLTemplateElement.prototype, "childNodes", { get() {
13
+ return this.content.childNodes;
14
+ } });
41
15
  }
42
16
  if (typeof process === "undefined" || !process.env?.QTL_SKIP_AUTO_CLEANUP) {
43
- if (typeof afterEach === "function") {
44
- afterEach(() => {
45
- cleanup();
46
- });
47
- }
17
+ if (typeof afterEach === "function") afterEach(() => {
18
+ cleanup();
19
+ });
48
20
  }
49
- const mountedContainers = /* @__PURE__ */ new Set();
21
+ var mountedContainers = /* @__PURE__ */ new Set();
50
22
  async function render(ui, options = {}) {
51
- const qwik = await import("@builder.io/qwik");
52
- let { container, baseElement = container, wrapper: Wrapper } = options;
53
- const { queries, serverData } = options;
54
- if (!baseElement) {
55
- baseElement = document.body;
56
- }
57
- if (!container) {
58
- container = baseElement.insertBefore(
59
- document.createElement("host"),
60
- baseElement.firstChild
61
- );
62
- }
63
- const wrappedUi = !Wrapper ? ui : /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { children: ui });
64
- const doc = baseElement.ownerDocument;
65
- const win = doc.defaultView;
66
- new Function("document", "window", server.getQwikLoaderScript())(doc, win);
67
- const { cleanup: cleanup2 } = await qwik.render(container, wrappedUi, { serverData });
68
- mountedContainers.add({ container, componentCleanup: cleanup2 });
69
- return {
70
- container,
71
- baseElement,
72
- asFragment: () => {
73
- if (typeof document.createRange === "function") {
74
- return document.createRange().createContextualFragment(container.innerHTML);
75
- } else {
76
- const template = document.createElement("template");
77
- template.innerHTML = container.innerHTML;
78
- return template.content;
79
- }
80
- },
81
- debug: (el = baseElement, maxLength, options2) => Array.isArray(el) ? el.forEach((e) => console.log(dom.prettyDOM(e, maxLength, options2))) : console.log(
82
- dom.prettyDOM(el, maxLength, { ...options2, filterNode: () => true })
83
- ),
84
- unmount: cleanup2,
85
- ...dom.getQueriesForElement(container, queries)
86
- };
23
+ const qwik = await import("@builder.io/qwik");
24
+ let { container, baseElement = container } = options;
25
+ const { wrapper: Wrapper } = options;
26
+ const { queries, serverData } = options;
27
+ if (!baseElement) baseElement = document.body;
28
+ if (!container) container = baseElement.insertBefore(document.createElement("host"), baseElement.firstChild);
29
+ const wrappedUi = !Wrapper ? ui : /* @__PURE__ */ (0, _builder_io_qwik_jsx_runtime.jsx)(Wrapper, { children: ui });
30
+ const doc = baseElement.ownerDocument;
31
+ const win = doc.defaultView;
32
+ new Function("document", "window", (0, _builder_io_qwik_server.getQwikLoaderScript)())(doc, win);
33
+ const { cleanup } = await qwik.render(container, wrappedUi, { serverData });
34
+ mountedContainers.add({
35
+ container,
36
+ componentCleanup: cleanup
37
+ });
38
+ return {
39
+ container,
40
+ baseElement,
41
+ asFragment: () => {
42
+ if (typeof document.createRange === "function") return document.createRange().createContextualFragment(container.innerHTML);
43
+ else {
44
+ const template = document.createElement("template");
45
+ template.innerHTML = container.innerHTML;
46
+ return template.content;
47
+ }
48
+ },
49
+ debug: (el = baseElement, maxLength, options) => Array.isArray(el) ? el.forEach((e) => console.log((0, _testing_library_dom.prettyDOM)(e, maxLength, options))) : console.log((0, _testing_library_dom.prettyDOM)(el, maxLength, {
50
+ ...options,
51
+ filterNode: () => true
52
+ })),
53
+ unmount: cleanup,
54
+ ...(0, _testing_library_dom.getQueriesForElement)(container, queries)
55
+ };
87
56
  }
88
57
  function cleanupAtContainer(ref) {
89
- const { container, componentCleanup } = ref;
90
- componentCleanup();
91
- if (container?.parentNode === document.body) {
92
- document.body.removeChild(container);
93
- }
94
- mountedContainers.delete(ref);
58
+ const { container, componentCleanup } = ref;
59
+ componentCleanup();
60
+ if (container?.parentNode === document.body) document.body.removeChild(container);
61
+ mountedContainers.delete(ref);
95
62
  }
96
63
  function cleanup() {
97
- mountedContainers.forEach(cleanupAtContainer);
64
+ mountedContainers.forEach(cleanupAtContainer);
98
65
  }
66
+ async function renderHook(callback, options = {}) {
67
+ const { component$, noSerialize } = await import("@builder.io/qwik");
68
+ const callbackRef = noSerialize(callback);
69
+ const resultRef = noSerialize({ current: void 0 });
70
+ const { unmount } = await render(/* @__PURE__ */ (0, _builder_io_qwik_jsx_runtime.jsx)(component$(() => {
71
+ resultRef.current = callbackRef();
72
+ return /* @__PURE__ */ (0, _builder_io_qwik_jsx_runtime.jsx)(_builder_io_qwik_jsx_runtime.Fragment, {});
73
+ }), {}), { wrapper: options.wrapper });
74
+ return {
75
+ result: resultRef.current,
76
+ unmount
77
+ };
78
+ }
79
+ //#endregion
99
80
  exports.cleanup = cleanup;
100
81
  exports.render = render;
101
- Object.keys(dom).forEach((k) => {
102
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
103
- enumerable: true,
104
- get: () => dom[k]
105
- });
82
+ exports.renderHook = renderHook;
83
+ Object.keys(_testing_library_dom).forEach(function(k) {
84
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
85
+ enumerable: true,
86
+ get: function() {
87
+ return _testing_library_dom[k];
88
+ }
89
+ });
106
90
  });
@@ -1,79 +1,81 @@
1
- import { jsx } from "@builder.io/qwik/jsx-runtime";
1
+ import "../_virtual/_rolldown/runtime.qwik.mjs";
2
2
  import { getQueriesForElement, prettyDOM } from "@testing-library/dom";
3
- export * from "@testing-library/dom";
4
3
  import { getQwikLoaderScript } from "@builder.io/qwik/server";
4
+ import { Fragment, jsx } from "@builder.io/qwik/jsx-runtime";
5
+ export * from "@testing-library/dom";
6
+ //#region src/lib/qwik-testing-library.tsx
5
7
  if (typeof HTMLTemplateElement !== "undefined") {
6
- const testTemplate = document.createElement("template");
7
- const testNode = document.createComment("test");
8
- testTemplate.insertBefore(testNode, null);
9
- const needsPatch = testTemplate.childNodes.length === 0;
10
- testNode.remove();
11
- if (needsPatch) {
12
- Object.defineProperty(HTMLTemplateElement.prototype, "childNodes", {
13
- get() {
14
- return this.content.childNodes;
15
- }
16
- });
17
- }
8
+ const testTemplate = document.createElement("template");
9
+ const testNode = document.createComment("test");
10
+ testTemplate.insertBefore(testNode, null);
11
+ const needsPatch = testTemplate.childNodes.length === 0;
12
+ testNode.remove();
13
+ if (needsPatch) Object.defineProperty(HTMLTemplateElement.prototype, "childNodes", { get() {
14
+ return this.content.childNodes;
15
+ } });
18
16
  }
19
17
  if (typeof process === "undefined" || !process.env?.QTL_SKIP_AUTO_CLEANUP) {
20
- if (typeof afterEach === "function") {
21
- afterEach(() => {
22
- cleanup();
23
- });
24
- }
18
+ if (typeof afterEach === "function") afterEach(() => {
19
+ cleanup();
20
+ });
25
21
  }
26
- const mountedContainers = /* @__PURE__ */ new Set();
22
+ var mountedContainers = /* @__PURE__ */ new Set();
27
23
  async function render(ui, options = {}) {
28
- const qwik = await import("@builder.io/qwik");
29
- let { container, baseElement = container, wrapper: Wrapper } = options;
30
- const { queries, serverData } = options;
31
- if (!baseElement) {
32
- baseElement = document.body;
33
- }
34
- if (!container) {
35
- container = baseElement.insertBefore(
36
- document.createElement("host"),
37
- baseElement.firstChild
38
- );
39
- }
40
- const wrappedUi = !Wrapper ? ui : /* @__PURE__ */ jsx(Wrapper, { children: ui });
41
- const doc = baseElement.ownerDocument;
42
- const win = doc.defaultView;
43
- new Function("document", "window", getQwikLoaderScript())(doc, win);
44
- const { cleanup: cleanup2 } = await qwik.render(container, wrappedUi, { serverData });
45
- mountedContainers.add({ container, componentCleanup: cleanup2 });
46
- return {
47
- container,
48
- baseElement,
49
- asFragment: () => {
50
- if (typeof document.createRange === "function") {
51
- return document.createRange().createContextualFragment(container.innerHTML);
52
- } else {
53
- const template = document.createElement("template");
54
- template.innerHTML = container.innerHTML;
55
- return template.content;
56
- }
57
- },
58
- debug: (el = baseElement, maxLength, options2) => Array.isArray(el) ? el.forEach((e) => console.log(prettyDOM(e, maxLength, options2))) : console.log(
59
- prettyDOM(el, maxLength, { ...options2, filterNode: () => true })
60
- ),
61
- unmount: cleanup2,
62
- ...getQueriesForElement(container, queries)
63
- };
24
+ const qwik = await import("@builder.io/qwik");
25
+ let { container, baseElement = container } = options;
26
+ const { wrapper: Wrapper } = options;
27
+ const { queries, serverData } = options;
28
+ if (!baseElement) baseElement = document.body;
29
+ if (!container) container = baseElement.insertBefore(document.createElement("host"), baseElement.firstChild);
30
+ const wrappedUi = !Wrapper ? ui : /* @__PURE__ */ jsx(Wrapper, { children: ui });
31
+ const doc = baseElement.ownerDocument;
32
+ const win = doc.defaultView;
33
+ new Function("document", "window", getQwikLoaderScript())(doc, win);
34
+ const { cleanup } = await qwik.render(container, wrappedUi, { serverData });
35
+ mountedContainers.add({
36
+ container,
37
+ componentCleanup: cleanup
38
+ });
39
+ return {
40
+ container,
41
+ baseElement,
42
+ asFragment: () => {
43
+ if (typeof document.createRange === "function") return document.createRange().createContextualFragment(container.innerHTML);
44
+ else {
45
+ const template = document.createElement("template");
46
+ template.innerHTML = container.innerHTML;
47
+ return template.content;
48
+ }
49
+ },
50
+ debug: (el = baseElement, maxLength, options) => Array.isArray(el) ? el.forEach((e) => console.log(prettyDOM(e, maxLength, options))) : console.log(prettyDOM(el, maxLength, {
51
+ ...options,
52
+ filterNode: () => true
53
+ })),
54
+ unmount: cleanup,
55
+ ...getQueriesForElement(container, queries)
56
+ };
64
57
  }
65
58
  function cleanupAtContainer(ref) {
66
- const { container, componentCleanup } = ref;
67
- componentCleanup();
68
- if (container?.parentNode === document.body) {
69
- document.body.removeChild(container);
70
- }
71
- mountedContainers.delete(ref);
59
+ const { container, componentCleanup } = ref;
60
+ componentCleanup();
61
+ if (container?.parentNode === document.body) document.body.removeChild(container);
62
+ mountedContainers.delete(ref);
72
63
  }
73
64
  function cleanup() {
74
- mountedContainers.forEach(cleanupAtContainer);
65
+ mountedContainers.forEach(cleanupAtContainer);
66
+ }
67
+ async function renderHook(callback, options = {}) {
68
+ const { component$, noSerialize } = await import("@builder.io/qwik");
69
+ const callbackRef = noSerialize(callback);
70
+ const resultRef = noSerialize({ current: void 0 });
71
+ const { unmount } = await render(/* @__PURE__ */ jsx(component$(() => {
72
+ resultRef.current = callbackRef();
73
+ return /* @__PURE__ */ jsx(Fragment, {});
74
+ }), {}), { wrapper: options.wrapper });
75
+ return {
76
+ result: resultRef.current,
77
+ unmount
78
+ };
75
79
  }
76
- export {
77
- cleanup,
78
- render
79
- };
80
+ //#endregion
81
+ export { cleanup, render, renderHook };
@@ -1,8 +1,9 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/setup.ts
3
3
  globalThis.qTest = false;
4
4
  globalThis.qRuntimeQrl = true;
5
5
  globalThis.qDev = true;
6
6
  globalThis.qInspector = false;
7
- const __qwikSetupComplete = true;
7
+ var __qwikSetupComplete = true;
8
+ //#endregion
8
9
  exports.__qwikSetupComplete = __qwikSetupComplete;
@@ -1,8 +1,8 @@
1
+ //#region src/setup.ts
1
2
  globalThis.qTest = false;
2
3
  globalThis.qRuntimeQrl = true;
3
4
  globalThis.qDev = true;
4
5
  globalThis.qInspector = false;
5
- const __qwikSetupComplete = true;
6
- export {
7
- __qwikSetupComplete
8
- };
6
+ var __qwikSetupComplete = true;
7
+ //#endregion
8
+ export { __qwikSetupComplete };
@@ -1,6 +1,7 @@
1
1
  import type { JSXOutput } from "@builder.io/qwik";
2
- import type { Options, Result } from "./types";
3
- declare function render(ui: JSXOutput, options?: Options): Promise<Result>;
2
+ import type { RenderOptions, RenderHookOptions, RenderHookResult, Result } from "./types";
3
+ declare function render(ui: JSXOutput, options?: RenderOptions): Promise<Result>;
4
4
  declare function cleanup(): void;
5
+ declare function renderHook<Result>(callback: () => Result, options?: RenderHookOptions): Promise<RenderHookResult<Result>>;
5
6
  export * from "@testing-library/dom";
6
- export { cleanup, render };
7
+ export { cleanup, render, renderHook };
@@ -1,7 +1,7 @@
1
1
  import type { BoundFunctions, prettyFormat, Queries } from "@testing-library/dom";
2
2
  import { queries } from "@testing-library/dom";
3
- import type { Component, RenderOptions } from "@builder.io/qwik";
4
- export interface Options extends RenderOptions {
3
+ import type { Component, RenderOptions as QwikRenderOptions } from "@builder.io/qwik";
4
+ export interface RenderOptions extends QwikRenderOptions {
5
5
  container?: HTMLElement;
6
6
  baseElement?: HTMLElement;
7
7
  queries?: Queries & typeof queries;
@@ -19,3 +19,8 @@ export type ComponentRef = {
19
19
  container: HTMLElement;
20
20
  componentCleanup: () => void;
21
21
  };
22
+ export type RenderHookOptions = Pick<RenderOptions, 'wrapper'>;
23
+ export interface RenderHookResult<Result> {
24
+ result: Result;
25
+ unmount: () => void;
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noma.to/qwik-testing-library",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Simple and complete Qwik testing utilities that encourage good testing practices.",
5
5
  "repository": "https://github.com/ianlet/qwik-testing-library",
6
6
  "homepage": "https://github.com/ianlet/qwik-testing-library",
@@ -39,18 +39,18 @@
39
39
  "validate": "pnpm build"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/eslint": "8.56.10",
43
- "@types/node": "25.1.0",
44
- "@typescript-eslint/eslint-plugin": "8.54.0",
45
- "@typescript-eslint/parser": "8.54.0",
46
- "eslint": "8.57.1",
47
- "eslint-plugin-qwik": "1.19.0",
48
- "prettier": "3.8.1",
49
- "typescript": "5.9.3",
42
+ "@eslint/js": "10",
43
+ "@types/node": "25.6.0",
44
+ "eslint": "10",
45
+ "eslint-plugin-qwik": "1.19.2",
46
+ "globals": "^17.4.0",
47
+ "prettier": "3.8.2",
48
+ "typescript": "6.0.2",
49
+ "typescript-eslint": "^8.58.1",
50
50
  "undici": "*",
51
- "vite": "7.3.1",
52
- "vite-tsconfig-paths": "^6.0.5",
53
- "vitest": "^4.0.18"
51
+ "vite": "8.0.8",
52
+ "vite-tsconfig-paths": "^6.1.1",
53
+ "vitest": "^4.1.4"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@builder.io/qwik": ">= 1.12.0 < 2",