├── .tool-versions
├── apps
└── qwik-testing-library-e2e-tests
│ ├── src
│ ├── index.ts
│ ├── root.tsx
│ ├── components
│ │ ├── qwik-core
│ │ │ ├── qwik-signal.tsx
│ │ │ ├── qwik-serialize.spec.tsx
│ │ │ ├── qwik-computed.tsx
│ │ │ ├── qwik-task.tsx
│ │ │ ├── qwik-slot.tsx
│ │ │ ├── qwik-store.tsx
│ │ │ ├── qwik-visible-task.tsx
│ │ │ ├── qwik-resource.tsx
│ │ │ ├── qwik-context.tsx
│ │ │ ├── qwik-serialize.tsx
│ │ │ ├── qwik-store.spec.tsx
│ │ │ ├── qwik-task.spec.tsx
│ │ │ ├── qwik-signal.spec.tsx
│ │ │ ├── qwik-context.spec.tsx
│ │ │ ├── qwik-computed.spec.tsx
│ │ │ ├── qwik-visible-task.spec.tsx
│ │ │ ├── qwik-resource.spec.tsx
│ │ │ ├── qwik-render.tsx
│ │ │ ├── qwik-render.spec.tsx
│ │ │ └── qwik-slot.spec.tsx
│ │ ├── wrapper
│ │ │ └── wrapper.spec.tsx
│ │ ├── counter.tsx
│ │ └── counter.spec.tsx
│ ├── entry.dev.tsx
│ └── entry.ssr.tsx
│ ├── .prettierignore
│ ├── .eslintignore
│ ├── .gitignore
│ ├── vitest.setup.ts
│ ├── tsconfig.json
│ ├── .eslintrc.cjs
│ ├── vite.config.ts
│ ├── README.md
│ └── package.json
├── packages
├── qwik-mock
│ ├── src
│ │ ├── index.ts
│ │ └── lib
│ │ │ └── qwik-mock.ts
│ ├── .prettierignore
│ ├── README.md
│ ├── .eslintignore
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── .eslintrc.cjs
│ └── package.json
└── qwik-testing-library
│ ├── src
│ ├── index.ts
│ └── lib
│ │ ├── types.ts
│ │ └── qwik-testing-library.tsx
│ ├── .prettierignore
│ ├── .eslintignore
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── .eslintrc.cjs
│ └── package.json
├── pnpm-workspace.yaml
├── high-voltage.png
├── .husky
├── pre-commit
└── prepare-commit-msg
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_suggestion.yml
│ ├── docs_suggestion.yml
│ └── bug.yml
├── workflows
│ ├── lint-pr.yml
│ └── ci.yml
├── PULL_REQUEST_TEMPLATE.md
└── dependabot.yml
├── .gitignore
├── scripts
└── preview-release
├── LICENSE
├── release.config.mjs
├── .all-contributorsrc
├── package.json
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── README.md
/.tool-versions:
--------------------------------------------------------------------------------
1 | pnpm 9.11.0
2 | nodejs 22.9.0
3 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/qwik-mock/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./lib/qwik-mock";
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'apps/*'
3 | - 'packages/*'
--------------------------------------------------------------------------------
/packages/qwik-testing-library/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./lib/qwik-testing-library";
2 |
--------------------------------------------------------------------------------
/high-voltage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ianlet/qwik-testing-library/HEAD/high-voltage.png
--------------------------------------------------------------------------------
/packages/qwik-mock/.prettierignore:
--------------------------------------------------------------------------------
1 | # Files Prettier should not format
2 | **/*.log
3 | **/.DS_Store
4 | *.
5 | dist
6 | node_modules
7 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/.prettierignore:
--------------------------------------------------------------------------------
1 | # Files Prettier should not format
2 | **/*.log
3 | **/.DS_Store
4 | *.
5 | dist
6 | node_modules
7 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/.prettierignore:
--------------------------------------------------------------------------------
1 | # Files Prettier should not format
2 | **/*.log
3 | **/.DS_Store
4 | *.
5 | dist
6 | node_modules
7 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | BRANCH_NAME=$(git branch | grep '*' | sed 's/* //')
2 | if [[ $BRANCH_NAME =~ "no branch" ]]; then
3 | echo "You are rebasing, skipping hook"
4 | exit 0
5 | fi
6 |
7 | pnpm validate
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: 🤔 Support & Questions
3 | url: https://qwik.dev/chat
4 | about: This issue tracker is not for support questions. Please post your question on the Qwik Discord.
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | BRANCH_NAME=$(git branch | grep '*' | sed 's/* //')
2 | if [[ $BRANCH_NAME =~ "no branch" ]]; then
3 | echo "You are rebasing, skipping hook"
4 | exit 0
5 | fi
6 |
7 | exec < /dev/tty && pnpm exec cz --hook || true
8 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/root.tsx:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return (
3 | <>
4 |
5 |
6 | Qwik Blank App
7 |
8 |
9 | >
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/qwik-mock/README.md:
--------------------------------------------------------------------------------
1 | # Qwik Mock
2 |
3 | Qwik Mock is a small utility to mock Qwik QRLs. It is currently experimental and should be used with caution.
4 |
5 | Head over to [qtl-docs-repo] to learn how to use it.
6 |
7 | [qtl-docs-repo]: https://github.com/ianlet/qwik-testing-library/blob/main/README.md
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/lint-pr.yml:
--------------------------------------------------------------------------------
1 | name: 'Lint PR'
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | name: Validate PR title
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: amannn/action-semantic-pull-request@v6
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-signal.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useSignal } from "@builder.io/qwik";
2 |
3 | export const QwikSignal = component$(() => {
4 | const sig = useSignal("my-signal");
5 |
6 | return (
7 |
8 | {sig.value}
9 | (sig.value = "updated-signal")}>update
10 |
11 | );
12 | });
13 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-serialize.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@noma.to/qwik-testing-library";
2 | import { QwikSerialize } from "./qwik-serialize";
3 |
4 | describe(" ", () => {
5 | it("should render non-serializable value", async () => {
6 | await render( );
7 |
8 | expect(screen.getByText("no-serialize-foo")).toBeInTheDocument();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_suggestion.yml:
--------------------------------------------------------------------------------
1 | name: ✨ Suggestion or Feature Request
2 | description: Suggestions on how we can improve the library.
3 | title: '[✨]'
4 | labels: [ 'enhancement', 'STATUS-1: needs triage' ]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | description: 'A clear and concise description of your suggestion or feature request'
10 | label: Suggestion
11 | validations:
12 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/docs_suggestion.yml:
--------------------------------------------------------------------------------
1 | name: 📖 Documentation Suggestion
2 | description: Suggestions on how we can improve the documentation.
3 | title: '[📖]'
4 | labels: [ 'documentation', 'STATUS-1: needs triage' ]
5 | body:
6 | - type: textarea
7 | id: description
8 | attributes:
9 | description: 'A clear and concise description of your suggestion to improve the docs.'
10 | label: Suggestion
11 | validations:
12 | required: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Development
2 | node_modules
3 |
4 | # Cache
5 | .cache
6 | .mf
7 | .vscode
8 | .rollup.cache
9 | tsconfig.tsbuildinfo
10 |
11 | # Logs
12 | logs
13 | *.log
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 | pnpm-debug.log*
18 | lerna-debug.log*
19 |
20 | # Editor
21 | !.vscode/extensions.json
22 | .idea
23 | .DS_Store
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
30 | # Yarn
31 | .yarn/*
32 | !.yarn/releases
33 |
--------------------------------------------------------------------------------
/packages/qwik-mock/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | **/.DS_Store
3 | *.
4 | .vscode/settings.json
5 | .history
6 | .yarn
7 | bazel-*
8 | bazel-bin
9 | bazel-out
10 | bazel-qwik
11 | bazel-testlogs
12 | dist
13 | dist-dev
14 | lib
15 | lib-types
16 | etc
17 | external
18 | node_modules
19 | temp
20 | tsc-out
21 | tsdoc-metadata.json
22 | target
23 | output
24 | rollup.config.js
25 | build
26 | .cache
27 | .vscode
28 | .rollup.cache
29 | dist
30 | tsconfig.tsbuildinfo
31 | vite.config.ts
32 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | **/.DS_Store
3 | *.
4 | .vscode/settings.json
5 | .history
6 | .yarn
7 | bazel-*
8 | bazel-bin
9 | bazel-out
10 | bazel-qwik
11 | bazel-testlogs
12 | dist
13 | dist-dev
14 | lib
15 | lib-types
16 | etc
17 | external
18 | node_modules
19 | temp
20 | tsc-out
21 | tsdoc-metadata.json
22 | target
23 | output
24 | rollup.config.js
25 | build
26 | .cache
27 | .vscode
28 | .rollup.cache
29 | dist
30 | tsconfig.tsbuildinfo
31 | vite.config.ts
32 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | **/.DS_Store
3 | *.
4 | .vscode/settings.json
5 | .history
6 | .yarn
7 | bazel-*
8 | bazel-bin
9 | bazel-out
10 | bazel-qwik
11 | bazel-testlogs
12 | dist
13 | dist-dev
14 | lib
15 | lib-types
16 | etc
17 | external
18 | node_modules
19 | temp
20 | tsc-out
21 | tsdoc-metadata.json
22 | target
23 | output
24 | rollup.config.js
25 | build
26 | .cache
27 | .vscode
28 | .rollup.cache
29 | dist
30 | tsconfig.tsbuildinfo
31 | vite.config.ts
32 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-computed.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useComputed$, useSignal } from "@builder.io/qwik";
2 |
3 | export const QwikComputed = component$(() => {
4 | const sig = useSignal(0);
5 |
6 | const computed = useComputed$(() => {
7 | return "computed-value-" + sig.value;
8 | });
9 |
10 | return (
11 |
12 | {computed.value}
13 | sig.value++}>update
14 |
15 | );
16 | });
17 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-task.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useSignal, useTask$ } from "@builder.io/qwik";
2 |
3 | export const QwikTask = component$(() => {
4 | const sig = useSignal(0);
5 | const foo = useSignal();
6 |
7 | useTask$(({ track }) => {
8 | const value = track(sig);
9 |
10 | foo.value = "foo-" + value;
11 | });
12 |
13 | return (
14 |
15 | {foo.value}
16 | sig.value++}>update
17 |
18 | );
19 | });
20 |
--------------------------------------------------------------------------------
/packages/qwik-mock/.gitignore:
--------------------------------------------------------------------------------
1 | # Build
2 | /dist
3 | /lib
4 | /lib-types
5 | /server
6 |
7 | # Development
8 | node_modules
9 |
10 | # Cache
11 | .cache
12 | .mf
13 | .vscode
14 | .rollup.cache
15 | tsconfig.tsbuildinfo
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 | lerna-debug.log*
25 |
26 | # Editor
27 | !.vscode/extensions.json
28 | .idea
29 | .DS_Store
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 | *.sw?
35 |
36 | # Yarn
37 | .yarn/*
38 | !.yarn/releases
39 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/.gitignore:
--------------------------------------------------------------------------------
1 | # Build
2 | /dist
3 | /lib
4 | /lib-types
5 | /server
6 |
7 | # Development
8 | node_modules
9 |
10 | # Cache
11 | .cache
12 | .mf
13 | .vscode
14 | .rollup.cache
15 | tsconfig.tsbuildinfo
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 | lerna-debug.log*
25 |
26 | # Editor
27 | !.vscode/extensions.json
28 | .idea
29 | .DS_Store
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 | *.sw?
35 |
36 | # Yarn
37 | .yarn/*
38 | !.yarn/releases
39 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/.gitignore:
--------------------------------------------------------------------------------
1 | # Build
2 | /dist
3 | /lib
4 | /lib-types
5 | /server
6 |
7 | # Development
8 | node_modules
9 |
10 | # Cache
11 | .cache
12 | .mf
13 | .vscode
14 | .rollup.cache
15 | tsconfig.tsbuildinfo
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 | lerna-debug.log*
25 |
26 | # Editor
27 | !.vscode/extensions.json
28 | .idea
29 | .DS_Store
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 | *.sw?
35 |
36 | # Yarn
37 | .yarn/*
38 | !.yarn/releases
39 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-slot.tsx:
--------------------------------------------------------------------------------
1 | import { component$, Slot, useSignal } from "@builder.io/qwik";
2 |
3 | export const QwikSlot = component$(() => {
4 | const active = useSignal(false);
5 |
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
{active.value && }
15 |
(active.value = !active.value)}>update
16 |
17 | );
18 | });
19 |
--------------------------------------------------------------------------------
/scripts/preview-release:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Preview the next release from a branch
3 | #
4 | # Prerequisites:
5 | # - You must have push access to repository at the `origin` URL
6 | # - The branch you are on must exist on `origin`
7 |
8 | set -euxo pipefail
9 |
10 | branch="$(git rev-parse --abbrev-ref HEAD)"
11 | repository_url="$(git remote get-url origin)"
12 |
13 | pnpm semantic-release \
14 | --plugins="@semantic-release/commit-analyzer,@semantic-release/release-notes-generator" \
15 | --dry-run \
16 | --branches="$branch" \
17 | --repository-url="$repository_url"
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-store.tsx:
--------------------------------------------------------------------------------
1 | import { $, component$, useStore } from "@builder.io/qwik";
2 |
3 | export const QwikStore = component$(() => {
4 | const store = useStore<{ foo: string; bar: string }>({
5 | foo: "foo",
6 | bar: "bar",
7 | });
8 |
9 | return (
10 |
11 | {store.foo} - {store.bar}
12 | {
14 | store.foo = "bar";
15 | store.bar = "baz";
16 | })}
17 | >
18 | update
19 |
20 |
21 | );
22 | });
23 |
--------------------------------------------------------------------------------
/packages/qwik-mock/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "ES2017",
5 | "module": "ES2020",
6 | "lib": ["es2020", "DOM"],
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@builder.io/qwik",
9 | "strict": true,
10 | "declaration": true,
11 | "declarationDir": "lib-types",
12 | "resolveJsonModule": true,
13 | "moduleResolution": "Bundler",
14 | "esModuleInterop": true,
15 | "skipLibCheck": true,
16 | "incremental": true,
17 | "isolatedModules": true,
18 | "types": ["vite/client"]
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-visible-task.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
2 |
3 | export const QwikVisibleTask = component$(() => {
4 | const sig = useSignal(0);
5 | const foo = useSignal();
6 |
7 | // eslint-disable-next-line qwik/no-use-visible-task
8 | useVisibleTask$(({ track }) => {
9 | const value = track(sig);
10 |
11 | foo.value = "foo-" + value;
12 | });
13 |
14 | return (
15 |
16 | {foo.value}
17 | sig.value++}>update
18 |
19 | );
20 | });
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # What is it?
2 |
3 | - [ ] Feature / enhancement
4 | - [ ] Bug
5 | - [ ] Docs / tests / types / typos
6 | - [ ] Infra
7 |
8 | # Description
9 |
10 |
14 |
15 | # Checklist:
16 |
17 | - [ ] My code follows
18 | the [developer guidelines of this project](https://github.com/ianlet/qwik-testing-library/blob/main/CONTRIBUTING.md)
19 | - [ ] I have performed a self-review of my own code
20 | - [ ] I have made corresponding changes to the docs
21 | - [ ] Added new tests to cover the fix / functionality
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/wrapper/wrapper.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "@noma.to/qwik-testing-library";
2 | import { component$, Slot } from "@builder.io/qwik";
3 |
4 | const MyComponent = component$(() => my-component
);
5 |
6 | const Wrapper = component$(() => {
7 | return (
8 |
9 |
10 |
11 | );
12 | });
13 |
14 | describe("Wrapper", () => {
15 | it("should wrap my component inside the wrapper", async () => {
16 | await render( , { wrapper: Wrapper });
17 |
18 | expect(screen.getByTestId("wrapper")).toHaveTextContent("my-component");
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/vitest.setup.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/jest-dom/vitest";
2 | import { beforeEach, vi } from "vitest";
3 |
4 | // This has to run before qdev.ts loads. `beforeAll` is too late
5 | globalThis.qTest = false; // Forces Qwik to run as if it was in a Browser
6 | globalThis.qRuntimeQrl = true;
7 | globalThis.qDev = true;
8 | globalThis.qInspector = false;
9 |
10 | beforeEach(() => {
11 | const mockIntersectionObserver = vi.fn();
12 | mockIntersectionObserver.mockReturnValue({
13 | observe: () => null,
14 | unobserve: () => null,
15 | disconnect: () => null,
16 | });
17 | window.IntersectionObserver = mockIntersectionObserver;
18 | });
19 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/entry.dev.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * WHAT IS THIS FILE?
3 | *
4 | * Development entry point using only client-side modules:
5 | * - Do not use this mode in production!
6 | * - No SSR
7 | * - No portion of the application is pre-rendered on the server.
8 | * - All of the application is running eagerly in the browser.
9 | * - More code is transferred to the browser than in SSR mode.
10 | * - Optimizer/Serialization/Deserialization code is not exercised!
11 | */
12 | import { render, type RenderOptions } from "@builder.io/qwik";
13 | import Root from "./root";
14 |
15 | export default function (opts: RenderOptions) {
16 | return render(document, , opts);
17 | }
18 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/entry.ssr.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * WHAT IS THIS FILE?
3 | *
4 | * SSR entry point, in all cases the application is rendered outside the browser, this
5 | * entry point will be the common one.
6 | *
7 | * - Server (express, cloudflare...)
8 | * - npm run start
9 | * - npm run preview
10 | * - npm run build
11 | *
12 | */
13 | import {
14 | renderToStream,
15 | type RenderToStreamOptions,
16 | } from "@builder.io/qwik/server";
17 | import { manifest } from "@qwik-client-manifest";
18 | import Root from "./root";
19 |
20 | export default function (opts: RenderToStreamOptions) {
21 | return renderToStream( , {
22 | manifest,
23 | ...opts,
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "ES2017",
5 | "module": "ES2020",
6 | "lib": ["es2020", "DOM"],
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@builder.io/qwik",
9 | "strict": true,
10 | "declaration": true,
11 | "declarationDir": "lib-types",
12 | "resolveJsonModule": true,
13 | "moduleResolution": "Bundler",
14 | "esModuleInterop": true,
15 | "skipLibCheck": true,
16 | "incremental": true,
17 | "isolatedModules": true,
18 | "types": [
19 | "vite/client",
20 | "vitest/globals",
21 | "@testing-library/jest-dom/vitest"
22 | ]
23 | },
24 | "include": ["src"]
25 | }
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-resource.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | component$,
3 | Resource,
4 | useResource$,
5 | useSignal,
6 | } from "@builder.io/qwik";
7 |
8 | function getResource(value: number): Promise {
9 | return Promise.resolve("resource-" + value);
10 | }
11 |
12 | export const QwikResource = component$(() => {
13 | const sig = useSignal(0);
14 |
15 | const resource = useResource$(({ track }) => {
16 | const value = track(sig);
17 | return getResource(value);
18 | });
19 |
20 | return (
21 |
22 | <>{value}>} />
23 | sig.value++}>update
24 |
25 | );
26 | });
27 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "target": "ES2017",
5 | "module": "ES2020",
6 | "lib": [
7 | "es2020",
8 | "DOM"
9 | ],
10 | "jsx": "react-jsx",
11 | "jsxImportSource": "@builder.io/qwik",
12 | "strict": true,
13 | "declaration": true,
14 | "declarationDir": "lib-types",
15 | "resolveJsonModule": true,
16 | "moduleResolution": "Bundler",
17 | "esModuleInterop": true,
18 | "skipLibCheck": true,
19 | "incremental": true,
20 | "isolatedModules": true,
21 | "types": [
22 | "node",
23 | "vite/client",
24 | "vitest/globals"
25 | ]
26 | },
27 | "include": [
28 | "src"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-context.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | component$,
3 | createContextId,
4 | useContext,
5 | useContextProvider,
6 | useStore,
7 | } from "@builder.io/qwik";
8 |
9 | const MyContext = createContextId<{ foo: string }>("my-context");
10 |
11 | export const QwikContext = component$(() => {
12 | useContextProvider(MyContext, useStore({ foo: "foo" }));
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | });
20 |
21 | const Inner = component$(() => {
22 | const context = useContext(MyContext);
23 |
24 | return (
25 |
26 | context-{context.foo}
27 | (context.foo = "bar")}>update
28 |
29 | );
30 | });
31 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-serialize.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | component$,
3 | noSerialize,
4 | NoSerialize,
5 | useSignal,
6 | useVisibleTask$,
7 | } from "@builder.io/qwik";
8 |
9 | class MyClass {
10 | constructor(private _foo: string) {}
11 |
12 | get foo(): string {
13 | return this._foo;
14 | }
15 |
16 | set foo(value: string) {
17 | this._foo = value;
18 | }
19 | }
20 |
21 | export const QwikSerialize = component$(() => {
22 | const sig = useSignal>();
23 |
24 | // eslint-disable-next-line qwik/no-use-visible-task
25 | useVisibleTask$(() => {
26 | sig.value = noSerialize(new MyClass("no-serialize-foo"));
27 | });
28 |
29 | return {sig.value?.foo}
;
30 | });
31 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/counter.tsx:
--------------------------------------------------------------------------------
1 | import { $, component$, QRL, useSignal } from "@builder.io/qwik";
2 |
3 | interface CounterProps {
4 | onChange$: QRL<(value: number) => void>;
5 | }
6 |
7 | export const Counter = component$(({ onChange$ }) => {
8 | const count = useSignal(0);
9 |
10 | const handleIncrement = $(() => {
11 | count.value++;
12 | return onChange$(count.value);
13 | });
14 |
15 | const handleDecrement = $(() => {
16 | count.value--;
17 | return onChange$(count.value);
18 | });
19 |
20 | return (
21 |
22 |
Counter: {count}
23 | Increment
24 | Decrement
25 |
26 | );
27 | });
28 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Update npm dependencies
4 | - package-ecosystem: "npm"
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 | groups:
9 | env:
10 | patterns:
11 | - "*jsdom*"
12 | - "*happy-dom*"
13 | all:
14 | dependency-type: "all"
15 | ignore:
16 | - dependency-name: "eslint"
17 | versions: [">=9"]
18 | - dependency-name: "eslint-plugin-n"
19 | versions: [">=17"]
20 | - dependency-name: "eslint-plugin-promise"
21 | versions: [">=7"]
22 |
23 | # Update GitHub Actions dependencies
24 | - package-ecosystem: "github-actions"
25 | directory: "/"
26 | schedule:
27 | interval: "monthly"
28 | groups:
29 | actions:
30 | patterns:
31 | - "*"
32 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-store.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { QwikStore } from "./qwik-store";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | it("should render", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("foo - bar")).toBeInTheDocument();
10 | });
11 |
12 | describe("on update", () => {
13 | it("should render updated store value", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("bar - baz")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-task.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { QwikTask } from "./qwik-task";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | it("should render value from task", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("foo-0")).toBeInTheDocument();
10 | });
11 |
12 | describe("on update", () => {
13 | it("should render value updated from task", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("foo-1")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-signal.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { QwikSignal } from "./qwik-signal";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | it("should render signal value", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("my-signal")).toBeInTheDocument();
10 | });
11 |
12 | describe("on update", () => {
13 | it("should render updated signal value", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("updated-signal")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-context.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { userEvent } from "@testing-library/user-event";
3 | import { QwikContext } from "./qwik-context";
4 |
5 | describe(" ", () => {
6 | it("should render context value", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("context-foo")).toBeInTheDocument();
10 | });
11 |
12 | describe("on update", () => {
13 | it("should render updated context value", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("context-bar")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-computed.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { QwikComputed } from "./qwik-computed";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | it("should render computed value", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("computed-value-0")).toBeInTheDocument();
10 | });
11 |
12 | describe("on tracked value update", () => {
13 | it("should render updated computed value", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("computed-value-1")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-visible-task.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { userEvent } from "@testing-library/user-event";
3 | import { QwikVisibleTask } from "./qwik-visible-task";
4 |
5 | describe(" ", () => {
6 | it("should render value from visible task", async () => {
7 | await render( );
8 |
9 | expect(screen.getByText("foo-0")).toBeInTheDocument();
10 | });
11 |
12 | describe("on update", () => {
13 | it("should render value updated from visible task", async () => {
14 | const user = userEvent.setup();
15 | await render( );
16 |
17 | const button = screen.getByRole("button", { name: "update" });
18 | await user.click(button);
19 |
20 | await waitFor(() =>
21 | expect(screen.getByText("foo-1")).toBeInTheDocument(),
22 | );
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | BoundFunctions,
3 | prettyFormat,
4 | Queries,
5 | } from "@testing-library/dom";
6 | import { queries } from "@testing-library/dom";
7 | import type { Component, RenderOptions } from "@builder.io/qwik";
8 |
9 | export interface Options extends RenderOptions {
10 | container?: HTMLElement;
11 | baseElement?: HTMLElement;
12 | queries?: Queries & typeof queries;
13 | wrapper?: Component;
14 | }
15 |
16 | export type DebugFn = (
17 | baseElement?: HTMLElement | HTMLElement[],
18 | maxLength?: number,
19 | options?: prettyFormat.OptionsReceived,
20 | ) => void;
21 |
22 | export type Result = BoundFunctions & {
23 | asFragment: () => DocumentFragment;
24 | container: HTMLElement;
25 | baseElement: HTMLElement;
26 | debug: DebugFn;
27 | unmount: () => void;
28 | };
29 |
30 | export type ComponentRef = {
31 | container: HTMLElement;
32 | componentCleanup: () => void;
33 | };
34 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-resource.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { QwikResource } from "./qwik-resource";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | it("should render the resource", async () => {
7 | await render( );
8 |
9 | await waitFor(() =>
10 | expect(screen.getByText("resource-0")).toBeInTheDocument(),
11 | );
12 | });
13 |
14 | describe("on tracked value update", () => {
15 | it("should render updated resource value", async () => {
16 | const user = userEvent.setup();
17 | await render( );
18 |
19 | const button = screen.getByRole("button", { name: "update" });
20 | await user.click(button);
21 |
22 | await waitFor(() =>
23 | expect(screen.getByText("resource-1")).toBeInTheDocument(),
24 | );
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-render.tsx:
--------------------------------------------------------------------------------
1 | import { $, component$, PropsOf, Slot, useSignal } from "@builder.io/qwik";
2 |
3 | interface QwikRenderProps extends PropsOf<"div"> {
4 | myProp?: string;
5 | items?: string[];
6 | }
7 |
8 | export const QwikRender = component$(
9 | ({ myProp = "qwik-prop", items = [] }) => {
10 | const propSig = useSignal(myProp);
11 |
12 | const changePropValue = $(() => {
13 | propSig.value = "changed-prop";
14 | });
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | {items.map((item) => (
22 | {item}
23 | ))}
24 |
25 |
26 |
change prop
27 |
28 | );
29 | },
30 | );
31 |
32 | const ChildComponent = component$(({ myProp }) => {
33 | return (
34 | <>
35 | {myProp}
36 |
37 |
38 |
39 |
40 |
41 |
42 | >
43 | );
44 | });
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ian Letourneau (https://github.com/ianlet)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/qwik-mock/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import pkg from "./package.json";
3 | import { qwikVite } from "@builder.io/qwik/optimizer";
4 | import tsconfigPaths from "vite-tsconfig-paths";
5 |
6 | const { dependencies = {}, peerDependencies = {} } = pkg as any;
7 | const makeRegex = (dep) => new RegExp(`^${dep}(/.*)?$`);
8 | const excludeAll = (obj) => Object.keys(obj).map(makeRegex);
9 |
10 | export default defineConfig(() => {
11 | return {
12 | build: {
13 | target: "es2020",
14 | lib: {
15 | entry: "./src/index.ts",
16 | formats: ["es", "cjs"],
17 | fileName: (format, entryName) =>
18 | `${entryName}.qwik.${format === "es" ? "mjs" : "cjs"}`,
19 | },
20 | rollupOptions: {
21 | output: {
22 | preserveModules: true,
23 | preserveModulesRoot: "src",
24 | },
25 | // externalize deps that shouldn't be bundled into the library
26 | external: [
27 | /^node:.*/,
28 | ...excludeAll(dependencies),
29 | ...excludeAll(peerDependencies),
30 | ],
31 | },
32 | },
33 | plugins: [qwikVite(), tsconfigPaths()],
34 | };
35 | });
36 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import pkg from "./package.json";
3 | import { qwikVite } from "@builder.io/qwik/optimizer";
4 | import tsconfigPaths from "vite-tsconfig-paths";
5 |
6 | const { dependencies = {}, peerDependencies = {} } = pkg as any;
7 | const makeRegex = (dep) => new RegExp(`^${dep}(/.*)?$`);
8 | const excludeAll = (obj) => Object.keys(obj).map(makeRegex);
9 |
10 | export default defineConfig(() => {
11 | return {
12 | build: {
13 | target: "es2020",
14 | lib: {
15 | entry: "./src/index.ts",
16 | formats: ["es", "cjs"],
17 | fileName: (format, entryName) =>
18 | `${entryName}.qwik.${format === "es" ? "mjs" : "cjs"}`,
19 | },
20 | rollupOptions: {
21 | output: {
22 | preserveModules: true,
23 | preserveModulesRoot: "src",
24 | },
25 | // externalize deps that shouldn't be bundled into the library
26 | external: [
27 | /^node:.*/,
28 | ...excludeAll(dependencies),
29 | ...excludeAll(peerDependencies),
30 | ],
31 | },
32 | },
33 | plugins: [qwikVite(), tsconfigPaths()],
34 | };
35 | });
36 |
--------------------------------------------------------------------------------
/release.config.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('semantic-release').GlobalConfig}
3 | */
4 | export default {
5 | branches: ["main"],
6 | extends: "semantic-release-monorepo",
7 | plugins: [
8 | [
9 | "@semantic-release/commit-analyzer",
10 | {
11 | preset: "conventionalcommits",
12 | },
13 | ],
14 | [
15 | "@semantic-release/release-notes-generator",
16 | {
17 | preset: "conventionalcommits",
18 | },
19 | ],
20 | [
21 | "@semantic-release/npm",
22 | {
23 | npmPublish: false,
24 | },
25 | ],
26 | [
27 | "@semantic-release/exec",
28 | {
29 | prepareCmd: "yarn run build",
30 | },
31 | ],
32 | "@semantic-release/changelog",
33 | [
34 | "@semantic-release/git",
35 | {
36 | assets: ["package.json", "changelog"],
37 | message:
38 | "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
39 | },
40 | ],
41 | [
42 | "@semantic-release/github",
43 | {
44 | assets: [
45 | {
46 | path: "dist/atomic-calendar-revive.js",
47 | },
48 | ],
49 | },
50 | ],
51 | ],
52 | };
53 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-render.spec.tsx:
--------------------------------------------------------------------------------
1 | import { QwikRender } from "./qwik-render";
2 | import { render, screen } from "@noma.to/qwik-testing-library";
3 | import { userEvent } from "@testing-library/user-event";
4 |
5 | describe(" ", () => {
6 | const aProp = "my-prop";
7 | const initialProp = "qwik-prop";
8 | const changedProp = "changed-prop";
9 |
10 | it("should render prop value", async () => {
11 | await render( );
12 |
13 | expect(screen.getByText(aProp)).toBeInTheDocument();
14 | });
15 |
16 | describe("when props change", () => {
17 | it("should re-render", async () => {
18 | const user = userEvent.setup();
19 | await render( );
20 |
21 | const changePropBtn = screen.getByRole("button", {
22 | name: /change prop/,
23 | });
24 | await user.click(changePropBtn);
25 |
26 | expect(await screen.findByText(changedProp)).toBeInTheDocument();
27 | });
28 | });
29 |
30 | it("should render a list of elements", async () => {
31 | await render( );
32 |
33 | expect(screen.findAllByRole("listitem")).resolves.toHaveLength(3);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/packages/qwik-mock/src/lib/qwik-mock.ts:
--------------------------------------------------------------------------------
1 | import { $, implicit$FirstArg } from "@builder.io/qwik";
2 | import { vi } from "vitest";
3 |
4 | export const mockQrl = () => {
5 | return $(vi.fn());
6 | };
7 |
8 | /**
9 | * @experimental
10 | *
11 | * Create a QRL mock that can be used in tests to verify interactions
12 | *
13 | * As Qwik is an async framework, you need to `resolve()` the mock before making your verifications.
14 | * And remember to clear the mocks before each test to start with a clean slate!
15 | *
16 | * @example
17 | * ```tsx
18 | * describe(' ', () => {
19 | * const onClickMock = mock$(() => {});
20 | *
21 | * beforeEach(() => {
22 | * clearAllMocks();
23 | * });
24 | *
25 | * it('should call onClick$', async () => {
26 | * await render( );
27 | *
28 | * await userEvent.click(screen.getByRole('button'));
29 | *
30 | * await waitFor(() => expect(onClickMock.resolve()).resolves.toHaveBeenCalled());
31 | * });
32 | * });
33 | * ```
34 | */
35 | export const mock$ = implicit$FirstArg(mockQrl);
36 |
37 | /**
38 | * Will call `.mockClear()` on all spies. This will clear mock history, but not reset its implementation to the
39 | * default one.
40 | */
41 | export function clearAllMocks() {
42 | vi.clearAllMocks();
43 | }
44 |
--------------------------------------------------------------------------------
/packages/qwik-mock/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | extends: [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:qwik/recommended",
12 | ],
13 | parser: "@typescript-eslint/parser",
14 | parserOptions: {
15 | tsconfigRootDir: __dirname,
16 | project: ["./tsconfig.json"],
17 | ecmaVersion: 2021,
18 | sourceType: "module",
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | },
23 | plugins: ["@typescript-eslint"],
24 | rules: {
25 | "@typescript-eslint/no-explicit-any": "off",
26 | "@typescript-eslint/explicit-module-boundary-types": "off",
27 | "@typescript-eslint/no-inferrable-types": "off",
28 | "@typescript-eslint/no-non-null-assertion": "off",
29 | "@typescript-eslint/no-empty-interface": "off",
30 | "@typescript-eslint/no-namespace": "off",
31 | "@typescript-eslint/no-empty-function": "off",
32 | "@typescript-eslint/no-this-alias": "off",
33 | "@typescript-eslint/ban-types": "off",
34 | "@typescript-eslint/ban-ts-comment": "off",
35 | "prefer-spread": "off",
36 | "no-case-declarations": "off",
37 | "no-console": "off",
38 | "@typescript-eslint/no-unused-vars": ["error"],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "qwik-testing-library",
3 | "projectOwner": "ianlet",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "none",
12 | "contributors": [
13 | {
14 | "login": "ianlet",
15 | "name": "Ian Létourneau",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/6018732?v=4",
17 | "profile": "https://noma.to/",
18 | "contributions": [
19 | "code",
20 | "test",
21 | "ideas",
22 | "doc",
23 | "example"
24 | ]
25 | },
26 | {
27 | "login": "brandonpittman",
28 | "name": "Brandon Pittman",
29 | "avatar_url": "https://avatars.githubusercontent.com/u/967145?v=4",
30 | "profile": "https://brandonpittman.com",
31 | "contributions": [
32 | "doc"
33 | ]
34 | },
35 | {
36 | "login": "thejackshelton",
37 | "name": "Jack Shelton",
38 | "avatar_url": "https://avatars.githubusercontent.com/u/104264123?v=4",
39 | "profile": "http://jackshelton.com",
40 | "contributions": [
41 | "review"
42 | ]
43 | }
44 | ],
45 | "contributorsPerLine": 7,
46 | "linkToUsage": true,
47 | "commitType": "docs"
48 | }
49 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | extends: [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:qwik/recommended",
12 | ],
13 | parser: "@typescript-eslint/parser",
14 | parserOptions: {
15 | tsconfigRootDir: __dirname,
16 | project: ["./tsconfig.json"],
17 | ecmaVersion: 2021,
18 | sourceType: "module",
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | },
23 | plugins: ["@typescript-eslint"],
24 | rules: {
25 | "@typescript-eslint/no-explicit-any": "off",
26 | "@typescript-eslint/explicit-module-boundary-types": "off",
27 | "@typescript-eslint/no-inferrable-types": "off",
28 | "@typescript-eslint/no-non-null-assertion": "off",
29 | "@typescript-eslint/no-empty-interface": "off",
30 | "@typescript-eslint/no-namespace": "off",
31 | "@typescript-eslint/no-empty-function": "off",
32 | "@typescript-eslint/no-this-alias": "off",
33 | "@typescript-eslint/ban-types": "off",
34 | "@typescript-eslint/ban-ts-comment": "off",
35 | "prefer-spread": "off",
36 | "no-case-declarations": "off",
37 | "no-console": "off",
38 | "@typescript-eslint/no-unused-vars": ["error"],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | extends: [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:qwik/recommended",
12 | ],
13 | parser: "@typescript-eslint/parser",
14 | parserOptions: {
15 | tsconfigRootDir: __dirname,
16 | project: ["./tsconfig.json"],
17 | ecmaVersion: 2021,
18 | sourceType: "module",
19 | ecmaFeatures: {
20 | jsx: true,
21 | },
22 | },
23 | plugins: ["@typescript-eslint"],
24 | rules: {
25 | "@typescript-eslint/no-explicit-any": "off",
26 | "@typescript-eslint/explicit-module-boundary-types": "off",
27 | "@typescript-eslint/no-inferrable-types": "off",
28 | "@typescript-eslint/no-non-null-assertion": "off",
29 | "@typescript-eslint/no-empty-interface": "off",
30 | "@typescript-eslint/no-namespace": "off",
31 | "@typescript-eslint/no-empty-function": "off",
32 | "@typescript-eslint/no-this-alias": "off",
33 | "@typescript-eslint/ban-types": "off",
34 | "@typescript-eslint/ban-ts-comment": "off",
35 | "prefer-spread": "off",
36 | "no-case-declarations": "off",
37 | "no-console": "off",
38 | "@typescript-eslint/no-unused-vars": ["error"],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/qwik-core/qwik-slot.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { userEvent } from "@testing-library/user-event";
3 | import { QwikSlot } from "./qwik-slot";
4 |
5 | describe(" ", () => {
6 | it("should render slot content", async () => {
7 | await render(default-slot );
8 |
9 | expect(screen.getByText("default-slot")).toBeInTheDocument();
10 | });
11 |
12 | it("should render named slot content", async () => {
13 | await render(
14 |
15 | named-slot
16 | ,
17 | );
18 |
19 | expect(screen.getByText("named-slot")).toBeInTheDocument();
20 | });
21 |
22 | it("should render conditional slot value", async () => {
23 | const user = userEvent.setup();
24 | await render(
25 |
26 | conditional-slot
27 | ,
28 | );
29 |
30 | expect(
31 | screen.queryByRole("button", { name: "conditional-slot" }),
32 | ).not.toBeInTheDocument();
33 |
34 | const button = screen.getByRole("button", { name: "update" });
35 | await user.click(button);
36 |
37 | await waitFor(() =>
38 | expect(
39 | screen.getByRole("button", { name: "conditional-slot" }),
40 | ).toBeInTheDocument(),
41 | );
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import pkg from "./package.json";
3 | import { qwikVite } from "@builder.io/qwik/optimizer";
4 | import tsconfigPaths from "vite-tsconfig-paths";
5 |
6 | const { dependencies = {}, peerDependencies = {} } = pkg as any;
7 | const makeRegex = (dep) => new RegExp(`^${dep}(/.*)?$`);
8 | const excludeAll = (obj) => Object.keys(obj).map(makeRegex);
9 |
10 | // Support both jsdom and happy-dom via TEST_DOM env variable
11 | const testEnvironment = process.env.TEST_DOM || "happy-dom";
12 |
13 | export default defineConfig(() => {
14 | return {
15 | build: {
16 | target: "es2020",
17 | lib: {
18 | entry: "./src/index.ts",
19 | formats: ["es", "cjs"],
20 | fileName: (format, entryName) =>
21 | `${entryName}.qwik.${format === "es" ? "mjs" : "cjs"}`,
22 | },
23 | rollupOptions: {
24 | output: {
25 | preserveModules: true,
26 | preserveModulesRoot: "src",
27 | },
28 | // externalize deps that shouldn't be bundled into the library
29 | external: [
30 | /^node:.*/,
31 | ...excludeAll(dependencies),
32 | ...excludeAll(peerDependencies),
33 | ],
34 | },
35 | },
36 | plugins: [qwikVite(), tsconfigPaths()],
37 | test: {
38 | environment: testEnvironment,
39 | setupFiles: ["./vitest.setup.ts"],
40 | globals: true,
41 | },
42 | };
43 | });
44 |
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/README.md:
--------------------------------------------------------------------------------
1 | # Qwik Library ⚡️
2 |
3 | - [Qwik Docs](https://qwik.dev/)
4 | - [Discord](https://qwik.dev/chat)
5 | - [Qwik on GitHub](https://github.com/QwikDev/qwik)
6 | - [@QwikDev](https://twitter.com/QwikDev)
7 | - [Vite](https://vitejs.dev/)
8 | - [Partytown](https://partytown.builder.io/)
9 | - [Mitosis](https://github.com/BuilderIO/mitosis)
10 | - [Builder.io](https://www.builder.io/)
11 |
12 | ---
13 |
14 | ## Project Structure
15 |
16 | Inside your project, you'll see the following directories and files:
17 |
18 | ```
19 | ├── public/
20 | │ └── ...
21 | └── src/
22 | ├── components/
23 | │ └── ...
24 | └── index.ts
25 | ```
26 |
27 | - `src/components`: Recommended directory for components.
28 |
29 | - `index.ts`: The entry point of your component library, make sure all the public components are exported from this file.
30 |
31 | ## Development
32 |
33 | Development mode uses [Vite's development server](https://vitejs.dev/). For Qwik during development, the `dev` command will also server-side render (SSR) the output. The client-side development modules are loaded by the browser.
34 |
35 | ```
36 | pnpm dev
37 | ```
38 |
39 | > Note: during dev mode, Vite will request many JS files, which does not represent a Qwik production build.
40 |
41 | ## Production
42 |
43 | The production build should generate the production build of your component library in (./lib) and the typescript type definitions in (./lib-types).
44 |
45 | ```
46 | pnpm build
47 | ```
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@noma.to/qwik-testing-library-root",
3 | "version": "0.0.0-semantically-released",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "toc": "doctoc README.md",
8 | "test": "pnpm -r build && pnpm -r test",
9 | "fmt": "pnpm -r fmt",
10 | "lint": "pnpm -r lint",
11 | "validate": "pnpm -r lint && pnpm -r validate",
12 | "contributors:add": "all-contributors add",
13 | "contributors:generate": "all-contributors generate",
14 | "build": "pnpm --filter '{packages/**}' run build",
15 | "release:prepare": "pnpm --filter '{packages/**}' run build && cp README.md packages/qwik-testing-library/README.md && cp LICENSE packages/qwik-testing-library/LICENSE && cp LICENSE packages/qwik-mock/LICENSE",
16 | "release": "pnpm --filter '{packages/**}' --workspace-concurrency=1 exec -- npx --no-install semantic-release -e semantic-release-monorepo",
17 | "prepare": "husky"
18 | },
19 | "keywords": [
20 | "testing",
21 | "testing-library",
22 | "qwik",
23 | "ui",
24 | "dom",
25 | "jsdom",
26 | "unit",
27 | "integration",
28 | "functional",
29 | "end-to-end",
30 | "e2e",
31 | "mock",
32 | "mocking",
33 | "vitest"
34 | ],
35 | "license": "MIT",
36 | "devDependencies": {
37 | "all-contributors-cli": "^6.26.1",
38 | "commitizen": "^4.3.1",
39 | "cz-conventional-changelog": "^3.3.0",
40 | "doctoc": "^2.2.1",
41 | "husky": "^9.1.7",
42 | "semantic-release": "^24.2.8",
43 | "semantic-release-monorepo": "^8.0.2"
44 | },
45 | "config": {
46 | "commitizen": {
47 | "path": "./node_modules/cz-conventional-changelog"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/qwik-mock/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@noma.to/qwik-mock",
3 | "version": "0.0.0-semantically-released",
4 | "description": "Small utility to mock Qwik QRLs",
5 | "repository": "https://github.com/ianlet/qwik-testing-library",
6 | "homepage": "https://github.com/ianlet/qwik-testing-library",
7 | "license": "MIT",
8 | "main": "./lib/index.qwik.mjs",
9 | "qwik": "./lib/index.qwik.mjs",
10 | "types": "./lib-types/index.d.ts",
11 | "exports": {
12 | ".": {
13 | "types": "./lib-types/index.d.ts",
14 | "import": "./lib/index.qwik.mjs",
15 | "require": "./lib/index.qwik.cjs"
16 | }
17 | },
18 | "files": [
19 | "lib",
20 | "lib-types"
21 | ],
22 | "engines": {
23 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
24 | },
25 | "private": false,
26 | "type": "module",
27 | "scripts": {
28 | "build": "pnpm build.lib && pnpm build.types",
29 | "build.lib": "vite build --mode lib",
30 | "build.types": "tsc --emitDeclarationOnly",
31 | "fmt": "prettier --write .",
32 | "fmt.check": "prettier --check .",
33 | "lint": "eslint \"src/**/*.ts*\"",
34 | "validate": "pnpm build"
35 | },
36 | "devDependencies": {
37 | "@types/eslint": "8.56.10",
38 | "@types/node": "24.5.0",
39 | "@typescript-eslint/eslint-plugin": "8.44.0",
40 | "@typescript-eslint/parser": "8.45.0",
41 | "eslint": "8.57.1",
42 | "eslint-plugin-qwik": "1.16.0",
43 | "prettier": "3.6.2",
44 | "typescript": "5.9.2",
45 | "undici": "*",
46 | "vite": "7.1.7",
47 | "vite-tsconfig-paths": "^5.1.4",
48 | "vitest": "^3.2.4"
49 | },
50 | "peerDependencies": {
51 | "@builder.io/qwik": ">= 1.12.0 < 2"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@noma.to/qwik-testing-library",
3 | "version": "0.0.0-semantically-released",
4 | "description": "Simple and complete Qwik testing utilities that encourage good testing practices.",
5 | "repository": "https://github.com/ianlet/qwik-testing-library",
6 | "homepage": "https://github.com/ianlet/qwik-testing-library",
7 | "license": "MIT",
8 | "main": "./lib/index.qwik.mjs",
9 | "qwik": "./lib/index.qwik.mjs",
10 | "types": "./lib-types/index.d.ts",
11 | "exports": {
12 | ".": {
13 | "types": "./lib-types/index.d.ts",
14 | "import": "./lib/index.qwik.mjs",
15 | "require": "./lib/index.qwik.cjs"
16 | }
17 | },
18 | "files": [
19 | "lib",
20 | "lib-types"
21 | ],
22 | "engines": {
23 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
24 | },
25 | "private": false,
26 | "type": "module",
27 | "scripts": {
28 | "build": "pnpm build.lib && pnpm build.types",
29 | "build.lib": "vite build --mode lib",
30 | "build.types": "tsc --emitDeclarationOnly",
31 | "fmt": "prettier --write .",
32 | "fmt.check": "prettier --check .",
33 | "lint": "eslint \"src/**/*.ts*\"",
34 | "validate": "pnpm build"
35 | },
36 | "devDependencies": {
37 | "@types/eslint": "8.56.10",
38 | "@types/node": "24.5.0",
39 | "@typescript-eslint/eslint-plugin": "8.44.0",
40 | "@typescript-eslint/parser": "8.45.0",
41 | "eslint": "8.57.1",
42 | "eslint-plugin-qwik": "1.16.0",
43 | "prettier": "3.6.2",
44 | "typescript": "5.9.2",
45 | "undici": "*",
46 | "vite": "7.1.7",
47 | "vite-tsconfig-paths": "^5.1.4",
48 | "vitest": "^3.2.4"
49 | },
50 | "peerDependencies": {
51 | "@builder.io/qwik": ">= 1.12.0 < 2",
52 | "@testing-library/dom": ">= 10.1.0 < 11"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing guide
2 |
3 | ## Pull requests
4 |
5 | - Consider opening an issue before submitting a pull-request to avoid unnecessary work
6 | - Ensure pull request titles adhere to the [Conventional Commits][] specification
7 |
8 | [conventional commits]: https://www.conventionalcommits.org/
9 |
10 | ## Release
11 |
12 | The module is released automatically from the `main` and `next` branches using [semantic-release-action][]. Version
13 | bumps and change logs are generated from the commit messages.
14 |
15 | [semantic-release-action]: https://github.com/cycjimmy/semantic-release-action
16 |
17 | ### Preview release
18 |
19 | If you would like to preview the release from a given branch, and...
20 |
21 | - You have push access to the repository
22 | - The branch exists in GitHub
23 |
24 | ...you can preview the next release version and changelog using:
25 |
26 | ```shell
27 | pnpm run preview-release
28 | ```
29 |
30 | ## Development setup
31 |
32 | After cloning the repository, install the project's dependencies and run the `validate` script to run all checks and
33 | tests to verify your setup.
34 |
35 | ```shell
36 | pnpm install
37 | pnpm validate
38 | ```
39 |
40 | ### Lint and format
41 |
42 | Run auto-formatting to ensure any changes adhere to the code style of the repository:
43 |
44 | ```shell
45 | pnpm -r fmt
46 | ```
47 |
48 | To run lint and format checks without making any changes:
49 |
50 | ```shell
51 | pnpm -r fmt.check
52 | ```
53 |
54 | ### Test
55 |
56 | Run unit tests once:
57 |
58 | ```shell
59 | pnpm -r test
60 | ```
61 |
62 | ### Docs
63 |
64 | Use the `toc` script to ensure the README's table of contents is up to date:
65 |
66 | ```shell
67 | pnpm toc
68 | ```
69 |
70 | Use `contributors:add` to add a contributor to the README:
71 |
72 | ```shell
73 | pnpm contributors:add
74 | ```
75 |
76 | Use `contributors:generate` to ensure the README's contributor list is up to date:
77 |
78 | ```shell
79 | pnpm contributors:generate
80 | ```
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-qwik-library-name",
3 | "version": "0.0.0-semantically-released",
4 | "description": "Create a Qwik library",
5 | "main": "./lib/index.qwik.mjs",
6 | "qwik": "./lib/index.qwik.mjs",
7 | "types": "./lib-types/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "types": "./lib-types/index.d.ts",
11 | "import": "./lib/index.qwik.mjs",
12 | "require": "./lib/index.qwik.cjs"
13 | }
14 | },
15 | "files": [
16 | "lib",
17 | "lib-types"
18 | ],
19 | "engines": {
20 | "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
21 | },
22 | "private": false,
23 | "type": "module",
24 | "scripts": {
25 | "build": "qwik build",
26 | "build.lib": "vite build --mode lib",
27 | "build.types": "tsc --emitDeclarationOnly",
28 | "dev": "vite --mode ssr",
29 | "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
30 | "fmt": "prettier --write .",
31 | "fmt.check": "prettier --check .",
32 | "lint": "eslint \"src/**/*.ts*\"",
33 | "start": "vite --open --mode ssr",
34 | "test": "vitest run components",
35 | "validate": "pnpm test",
36 | "qwik": "qwik"
37 | },
38 | "devDependencies": {
39 | "@builder.io/qwik": "^1.16.0",
40 | "@noma.to/qwik-mock": "workspace:*",
41 | "@noma.to/qwik-testing-library": "workspace:*",
42 | "@testing-library/jest-dom": "^6.9.0",
43 | "@testing-library/user-event": "^14.6.1",
44 | "@types/eslint": "8.56.10",
45 | "@types/node": "24.5.0",
46 | "@typescript-eslint/eslint-plugin": "8.44.0",
47 | "@typescript-eslint/parser": "8.45.0",
48 | "@vitest/ui": "^3.2.4",
49 | "eslint": "8.57.1",
50 | "eslint-plugin-qwik": "1.16.0",
51 | "happy-dom": "^20.0.11",
52 | "jsdom": "^26.1.0",
53 | "prettier": "3.6.2",
54 | "typescript": "5.9.2",
55 | "undici": "*",
56 | "vite": "7.1.7",
57 | "vite-tsconfig-paths": "^5.1.4",
58 | "vitest": "^3.2.4"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: 🐞 Bug Report
2 | description: Something does not work or is flaky! let us know!
3 | labels: [ 'bug', 'STATUS-1: needs triage' ]
4 | title: '[🐞]'
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 |
11 | - type: textarea
12 | id: description
13 | attributes:
14 | description: 'A clear and concise description of what you expected to happen instead. If you intend to submit a PR for this issue, tell us in the description. Thanks!'
15 | label: Describe the bug
16 | placeholder: I am doing ... What I expect is ... What actually happening is ...
17 |
18 | validations:
19 | required: true
20 |
21 | - type: input
22 | id: reproduction
23 | attributes:
24 | label: Reproduction
25 | description: Please provide a link via [qwik.new](https://qwik.new/) or a link to a repo that can reproduce the problem you ran into. `npm create qwik@latest` can be used as a starter template. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required ([Why?](https://antfu.me/posts/why-reproductions-are-required)). If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided after 3 days, it will be auto-closed.
26 | placeholder: Reproduction URL
27 | validations:
28 | required: true
29 |
30 | - type: textarea
31 | id: reproduction-steps
32 | attributes:
33 | label: Steps to reproduce
34 | description: Please provide any reproduction steps that may need to be described. E.g. if it happens only when running the dev or build script make sure it's clear which one to use.
35 | placeholder: Run `npm install` followed by `npm run dev`
36 |
37 | - type: textarea
38 | id: system-info
39 | attributes:
40 | label: System Info
41 | description: Output of `npx envinfo --system --npmPackages '{vite,undici,typescript,@builder.io/*}' --binaries --browsers`
42 | render: shell
43 | placeholder: System, Binaries, Browsers
44 | validations:
45 | required: true
46 |
47 | - type: textarea
48 | id: additional_information
49 | attributes:
50 | label: Additional Information
51 | validations:
52 | required: false
--------------------------------------------------------------------------------
/apps/qwik-testing-library-e2e-tests/src/components/counter.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from "@noma.to/qwik-testing-library";
2 | import { clearAllMocks, mock$ } from "@noma.to/qwik-mock";
3 | import { userEvent } from "@testing-library/user-event";
4 | import { Counter } from "./counter";
5 |
6 | describe(" ", () => {
7 | const onChangeMock = mock$(() => {});
8 |
9 | beforeEach(() => {
10 | clearAllMocks();
11 | });
12 |
13 | it("should start at 0 by default", async () => {
14 | await render( );
15 |
16 | expect(screen.getByText("Counter: 0")).toBeInTheDocument();
17 | });
18 |
19 | describe("on increment", () => {
20 | it("should increase by 1", async () => {
21 | const user = userEvent.setup();
22 | await render( );
23 |
24 | const incrementBtn = screen.getByRole("button", { name: "Increment" });
25 | await user.click(incrementBtn);
26 |
27 | expect(await screen.findByText("Counter: 1")).toBeInTheDocument();
28 | });
29 |
30 | it("should call onChange$", async () => {
31 | const user = userEvent.setup();
32 | await render( );
33 |
34 | const incrementBtn = screen.getByRole("button", { name: "Increment" });
35 | await user.click(incrementBtn);
36 |
37 | await waitFor(() =>
38 | expect(onChangeMock.resolve()).resolves.toHaveBeenCalledWith(1),
39 | );
40 | });
41 | });
42 |
43 | describe("on decrement", () => {
44 | it("should decrease by 1", async () => {
45 | const user = userEvent.setup();
46 | await render( );
47 |
48 | const decrementBtn = screen.getByRole("button", { name: "Decrement" });
49 | await user.click(decrementBtn);
50 |
51 | expect(await screen.findByText("Counter: -1")).toBeInTheDocument();
52 | });
53 |
54 | it("should call onChange$", async () => {
55 | const user = userEvent.setup();
56 | await render( );
57 |
58 | const decrementBtn = screen.getByRole("button", { name: "Decrement" });
59 | await user.click(decrementBtn);
60 |
61 | await waitFor(() =>
62 | expect(onChangeMock.resolve()).resolves.toHaveBeenCalledWith(-1),
63 | );
64 | });
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of
9 | experience, nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or reject
41 | comments, commits, code, wiki edits, issues, and other contributions that are
42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any
43 | contributor for other behaviors that they deem inappropriate, threatening,
44 | offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hello@noma.to. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an
62 | incident. Further details of specific enforcement policies may be posted
63 | separately.
64 |
65 | Project maintainers who do not follow or enforce the Code of Conduct in good
66 | faith may face temporary or permanent repercussions as determined by other
67 | members of the project's leadership.
68 |
69 | ## Attribution
70 |
71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
72 | version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
73 |
74 | [homepage]: http://contributor-covenant.org
75 |
76 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | schedule:
9 | # Tuesdays at 14:45 UTC (10:45 EST)
10 | - cron: 45 14 * * 1
11 |
12 | permissions:
13 | contents: read # for checkout
14 |
15 | concurrency:
16 | group: ${{ github.workflow }}-${{ github.ref }}
17 | cancel-in-progress: true
18 |
19 | jobs:
20 | main:
21 | # ignore all-contributors PRs
22 | if: ${{ !contains(github.head_ref, 'all-contributors') }}
23 | name: Node ${{ matrix.node }}, Qwik ${{ matrix.qwik }}, ${{ matrix.dom }}, ${{ matrix.check }}
24 | runs-on: ubuntu-latest
25 |
26 | strategy:
27 | fail-fast: false
28 | matrix:
29 | node: ["18", "20", "22"]
30 | qwik: ["1.12", "1.14.1", "1.15", "1.16", "1.17"]
31 | dom: ["jsdom", "happy-dom"]
32 | check: ["test"]
33 | include:
34 | - { node: "22", qwik: "1.17", check: "lint" }
35 |
36 | steps:
37 | - name: ⬇️ Checkout repo
38 | uses: actions/checkout@v6
39 | with:
40 | fetch-depth: 0
41 |
42 | - name: 🧱 Setup pnpm
43 | uses: pnpm/action-setup@v4
44 | with:
45 | version: 9
46 | run_install: false
47 |
48 | - name: ⎔ Setup node
49 | uses: actions/setup-node@v6
50 | with:
51 | node-version: ${{ matrix.node }}
52 | cache: "pnpm"
53 |
54 | - name: 📥 Download deps
55 | run: |
56 | pnpm install
57 | pnpm --filter "{packages/qwik-testing-library}" install @builder.io/qwik@${{ matrix.qwik }}
58 | pnpm --filter "{packages/qwik-mock}" install @builder.io/qwik@${{ matrix.qwik }}
59 | pnpm --filter "{apps/qwik-testing-library-e2e-tests}" install @builder.io/qwik@${{ matrix.qwik }}
60 |
61 | - name: ▶️ Run ${{ matrix.check }}
62 | run: pnpm ${{ matrix.check }}
63 | env:
64 | TEST_DOM: ${{ matrix.dom }}
65 |
66 | build:
67 | runs-on: ubuntu-latest
68 | steps:
69 | - name: ⬇️ Checkout repo
70 | uses: actions/checkout@v6
71 | with:
72 | fetch-depth: 0
73 |
74 | - name: 🧱 Setup pnpm
75 | uses: pnpm/action-setup@v4
76 | with:
77 | version: 9
78 | run_install: false
79 |
80 | - name: ⎔ Setup node
81 | uses: actions/setup-node@v6
82 | with:
83 | node-version: 20
84 | cache: "pnpm"
85 |
86 | - name: 📥 Download deps
87 | run: pnpm install
88 |
89 | - name: 🏗️ Build packages
90 | run: pnpm build
91 |
92 | release:
93 | name: Release
94 | needs: [main, build]
95 | runs-on: ubuntu-latest
96 | permissions:
97 | contents: write # to be able to publish a GitHub release
98 | issues: write # to be able to comment on released issues
99 | pull-requests: write # to be able to comment on released pull requests
100 | id-token: write # to enable use of OIDC for npm provenance
101 | steps:
102 | - name: ⬇️ Checkout repo
103 | uses: actions/checkout@v6
104 | with:
105 | fetch-depth: 0
106 |
107 | - name: 🧱 Setup pnpm
108 | uses: pnpm/action-setup@v4
109 | with:
110 | version: 9
111 | run_install: false
112 |
113 | - name: ⎔ Setup node
114 | uses: actions/setup-node@v6
115 | with:
116 | node-version: 20
117 | cache: "pnpm"
118 |
119 | - name: 📥 Download deps
120 | run: pnpm install
121 |
122 | - name: 🏗️ Build packages
123 | run: pnpm release:prepare
124 |
125 | - name: 🚀 Release
126 | env:
127 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
128 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
129 | run: pnpm release
130 |
--------------------------------------------------------------------------------
/packages/qwik-testing-library/src/lib/qwik-testing-library.tsx:
--------------------------------------------------------------------------------
1 | import { getQueriesForElement, prettyDOM } from "@testing-library/dom";
2 | import { JSXOutput } from "@builder.io/qwik";
3 | import { getQwikLoaderScript } from "@builder.io/qwik/server";
4 | import type { ComponentRef, Options, Result } from "./types";
5 |
6 | // Patch HTMLTemplateElement.childNodes for happy-dom compatibility
7 | //
8 | // Qwik's VirtualElementImpl uses a element as temporary storage for detached content.
9 | // It calls `template.insertBefore(node)` to store nodes and reads them back via `template.childNodes`.
10 | //
11 | // In real browsers and jsdom, `insertBefore` on a template adds direct children.
12 | // However, happy-dom redirects insertions to `template.content`, leaving `childNodes` empty.
13 | //
14 | // This patch makes `template.childNodes` return `template.content.childNodes` for happy-dom.
15 | if (typeof HTMLTemplateElement !== "undefined") {
16 | // Detect if this DOM implementation needs the patch by testing behavior
17 | const testTemplate = document.createElement("template");
18 | const testNode = document.createComment("test");
19 | testTemplate.insertBefore(testNode, null);
20 | const needsPatch = testTemplate.childNodes.length === 0;
21 | testNode.remove();
22 |
23 | if (needsPatch) {
24 | Object.defineProperty(HTMLTemplateElement.prototype, "childNodes", {
25 | get() {
26 | return this.content.childNodes;
27 | },
28 | });
29 | }
30 | }
31 |
32 | // if we're running in a test runner that supports afterEach
33 | // then we'll automatically run cleanup afterEach test
34 | // this ensures that tests run in isolation from each other
35 | // if you don't like this, set the QTL_SKIP_AUTO_CLEANUP env variable to 'true'
36 | if (typeof process === "undefined" || !process.env?.QTL_SKIP_AUTO_CLEANUP) {
37 | if (typeof afterEach === "function") {
38 | afterEach(() => {
39 | cleanup();
40 | });
41 | }
42 | }
43 |
44 | const mountedContainers = new Set();
45 |
46 | async function render(ui: JSXOutput, options: Options = {}): Promise {
47 | const qwik = await import("@builder.io/qwik");
48 |
49 | let { container, baseElement = container, wrapper: Wrapper } = options;
50 | const { queries, serverData } = options;
51 |
52 | if (!baseElement) {
53 | // Default to document.body instead of documentElement to avoid output of potentially large
54 | // head elements (such as JSS style blocks) in debug output.
55 | baseElement = document.body;
56 | }
57 |
58 | if (!container) {
59 | container = baseElement.insertBefore(
60 | document.createElement("host"),
61 | baseElement.firstChild,
62 | );
63 | }
64 |
65 | // Wrap the component under test if a wrapper is provided
66 | const wrappedUi = !Wrapper ? ui : ;
67 |
68 | // Load the Qwik loader into the target document
69 | const doc = baseElement.ownerDocument;
70 | const win = doc.defaultView;
71 | new Function("document", "window", getQwikLoaderScript())(doc, win);
72 |
73 | const { cleanup } = await qwik.render(container, wrappedUi, { serverData });
74 | mountedContainers.add({ container, componentCleanup: cleanup });
75 |
76 | return {
77 | container,
78 | baseElement,
79 | asFragment: () => {
80 | if (typeof document.createRange === "function") {
81 | return document
82 | .createRange()
83 | .createContextualFragment(container.innerHTML);
84 | } else {
85 | const template = document.createElement("template");
86 | template.innerHTML = container.innerHTML;
87 | return template.content;
88 | }
89 | },
90 | debug: (el = baseElement, maxLength, options) =>
91 | Array.isArray(el)
92 | ? el.forEach((e) => console.log(prettyDOM(e, maxLength, options)))
93 | : console.log(
94 | prettyDOM(el, maxLength, { ...options, filterNode: () => true }),
95 | ),
96 | unmount: cleanup,
97 | ...getQueriesForElement(container, queries),
98 | };
99 | }
100 |
101 | function cleanupAtContainer(ref: ComponentRef) {
102 | const { container, componentCleanup } = ref;
103 |
104 | componentCleanup();
105 |
106 | if (container?.parentNode === document.body) {
107 | document.body.removeChild(container);
108 | }
109 |
110 | mountedContainers.delete(ref);
111 | }
112 |
113 | function cleanup() {
114 | mountedContainers.forEach(cleanupAtContainer);
115 | }
116 |
117 | export * from "@testing-library/dom";
118 | export { cleanup, render };
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Qwik Testing Library
3 |
4 |
10 |
11 |
Simple and complete Qwik testing utilities that encourage good testing practices.
12 |
13 | [**Read The Docs**][qtl-docs] | [Edit the docs][qtl-docs-repo]
14 |
15 |
16 | [![Build Status][build-badge]][build]
17 | [![version][version-badge]][package]
18 | [![downloads][downloads-badge]][downloads]
19 | [![MIT License][license-badge]][license]
20 |
21 |
22 | [](#contributors-)
23 |
24 | [![PRs Welcome][prs-badge]][prs]
25 | [![Code of Conduct][coc-badge]][coc]
26 | [![Discord][discord-badge]][discord]
27 |
28 | [![Watch on GitHub][github-watch-badge]][github-watch]
29 | [![Star on GitHub][github-star-badge]][github-star]
30 | [![Tweet][twitter-badge]][twitter]
31 |
32 |
33 |
34 |
35 |
36 | [qtl-docs]: #installation
37 |
38 | [qtl-docs-repo]: https://github.com/ianlet/qwik-testing-library/blob/main/README.md
39 |
40 | [build-badge]: https://img.shields.io/github/actions/workflow/status/ianlet/qwik-testing-library/ci.yml?style=flat-square
41 |
42 | [build]: https://github.com/ianlet/qwik-testing-library/actions
43 |
44 | [coverage-badge]: https://img.shields.io/codecov/c/github/ianlet/qwik-testing-library.svg?style=flat-square
45 |
46 | [coverage]: https://codecov.io/github/ianlet/qwik-testing-library
47 |
48 | [version-badge]: https://img.shields.io/npm/v/@noma.to/qwik-testing-library.svg?style=flat-square
49 |
50 | [package]: https://www.npmjs.com/package/@noma.to/qwik-testing-library
51 |
52 | [downloads-badge]: https://img.shields.io/npm/dm/@noma.to/qwik-testing-library.svg?style=flat-square
53 |
54 | [downloads]: http://www.npmtrends.com/@noma.to/qwik-testing-library
55 |
56 | [license-badge]: https://img.shields.io/github/license/ianlet/qwik-testing-library?color=b&style=flat-square
57 |
58 | [license]: https://github.com/ianlet/qwik-testing-library/blob/main/LICENSE
59 |
60 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
61 |
62 | [prs]: http://makeapullrequest.com
63 |
64 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
65 |
66 | [coc]: https://github.com/ianlet/qwik-testing-library/blob/main/CODE_OF_CONDUCT.md
67 |
68 | [discord-badge]: https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square
69 |
70 | [discord]: https://qwik.dev/chat
71 |
72 | [github-watch-badge]: https://img.shields.io/github/watchers/ianlet/qwik-testing-library.svg?style=social
73 |
74 | [github-watch]: https://github.com/ianlet/qwik-testing-library/watchers
75 |
76 | [github-star-badge]: https://img.shields.io/github/stars/ianlet/qwik-testing-library.svg?style=social
77 |
78 | [github-star]: https://github.com/ianlet/qwik-testing-library/stargazers
79 |
80 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20qwik-testing-library%20by%20%40noma_hq%20https%3A%2F%2Fgithub.com%2Fianlet%2Fqwik-testing-library%20%F0%9F%91%8D
81 |
82 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/ianlet/qwik-testing-library.svg?style=social
83 |
84 | ## Table of Contents
85 |
86 |
87 |
88 |
89 | - [The Problem](#the-problem)
90 | - [This Solution](#this-solution)
91 | - [Installation](#installation)
92 | - [Setup](#setup)
93 | - [Examples](#examples)
94 | - [Qwikstart](#qwikstart)
95 | - [Mocking Component Callbacks (experimental)](#mocking-component-callbacks-experimental)
96 | - [Qwik City - `server$` calls](#qwik-city---server-calls)
97 | - [Gotchas](#gotchas)
98 | - [Issues](#issues)
99 | - [🐛 Bugs](#-bugs)
100 | - [💡 Feature Requests](#-feature-requests)
101 | - [❓ Questions](#-questions)
102 | - [Contributors](#contributors)
103 | - [Acknowledgements](#acknowledgements)
104 |
105 |
106 |
107 | ## The Problem
108 |
109 | You want to write maintainable tests for your [Qwik][qwik] components.
110 |
111 | [qwik]: https://qwik.dev/
112 |
113 | ## This Solution
114 |
115 | `@noma.to/qwik-testing-library` is a lightweight library for testing Qwik
116 | components. It provides functions on top of `qwik` and
117 | `@testing-library/dom` so you can mount Qwik components and query their
118 | rendered output in the DOM. Its primary guiding principle is:
119 |
120 | > [The more your tests resemble the way your software is used, the more
121 | > confidence they can give you.][guiding-principle]
122 |
123 | [guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106
124 |
125 | ## Installation
126 |
127 | This module is distributed via [npm][npm] which is bundled with [node][node] and
128 | should be installed as one of your project's `devDependencies`:
129 |
130 | ```shell
131 | npm install --save-dev @noma.to/qwik-testing-library @testing-library/dom
132 | ```
133 |
134 | This library supports `qwik` versions `1.7.2` and above and `@testing-library/dom` versions `10.1.0` and above.
135 |
136 | You may also be interested in installing `@testing-library/jest-dom` and `@testing-library/user-event` so you can
137 | use [the custom jest matchers][jest-dom] and [the user event library][user-event] to test interactions with the DOM.
138 |
139 | ```shell
140 | npm install --save-dev @testing-library/jest-dom @testing-library/user-event
141 | ```
142 |
143 | Finally, we need a DOM environment to run the tests in.
144 | This library is tested with both `jsdom` and `happy-dom`:
145 |
146 | ```shell
147 | npm install --save-dev jsdom
148 | # or
149 | npm install --save-dev happy-dom
150 | ```
151 |
152 | [npm]: https://www.npmjs.com/
153 |
154 | [node]: https://nodejs.org
155 |
156 | [jest-dom]: https://github.com/testing-library/jest-dom
157 |
158 | [user-event]: https://github.com/testing-library/user-event
159 |
160 | ## Setup
161 |
162 | We recommend using `@noma.to/qwik-testing-library` with [Vitest][vitest] as your test
163 | runner.
164 |
165 | If you haven't done so already, add vitest to your project using Qwik CLI:
166 |
167 | ```shell
168 | npm run qwik add vitest
169 | ```
170 |
171 | After that, we need to configure Vitest so it can run your tests.
172 | For this, create a _separate_ `vitest.config.ts` so you don't have to modify your project's `vite.config.ts`:
173 |
174 | ```ts
175 | // vitest.config.ts
176 |
177 | import {defineConfig, mergeConfig} from "vitest/config";
178 | import viteConfig from "./vite.config";
179 |
180 | export default defineConfig((configEnv) =>
181 | mergeConfig(
182 | viteConfig(configEnv),
183 | defineConfig({
184 | // qwik-testing-library needs to consider your project as a Qwik lib
185 | // if it's already a Qwik lib, you can remove this section
186 | build: {
187 | target: "es2020",
188 | lib: {
189 | entry: "./src/index.ts",
190 | formats: ["es", "cjs"],
191 | fileName: (format, entryName) =>
192 | `${entryName}.qwik.${format === "es" ? "mjs" : "cjs"}`,
193 | },
194 | },
195 | // configure your test environment
196 | test: {
197 | environment: "jsdom", // or "happy-dom"
198 | setupFiles: ["./vitest.setup.ts"],
199 | globals: true,
200 | },
201 | }),
202 | ),
203 | );
204 | ```
205 |
206 | For now, `qwik-testing-library` needs to consider your project as a lib ([PR welcomed][prs] to simplify this).
207 | Hence, the `build.lib` section in the config above.
208 |
209 | As the build will try to use `./src/index.ts` as the entry point, we need to create it:
210 |
211 | ```ts
212 | // ./src/index.ts
213 |
214 | /**
215 | * DO NOT DELETE THIS FILE
216 | *
217 | * This entrypoint is needed by @noma.to/qwik-testing-library to run your tests
218 | */
219 | ```
220 |
221 | Then, create the `vitest.setup.ts` file:
222 |
223 | ```ts
224 | // vitest.setup.ts
225 |
226 | // Configure DOM matchers to work in Vitest
227 | import "@testing-library/jest-dom/vitest";
228 |
229 | // This has to run before qdev.ts loads. `beforeAll` is too late
230 | globalThis.qTest = false; // Forces Qwik to run as if it was in a Browser
231 | globalThis.qRuntimeQrl = true;
232 | globalThis.qDev = true;
233 | globalThis.qInspector = false;
234 | ```
235 |
236 | This setup will make sure that Qwik is properly configured.
237 | It also loads `@testing-library/jest-dom/vitest` in your test runner
238 | so you can use matchers like `expect(...).toBeInTheDocument()`.
239 |
240 | By default, Qwik Testing Library cleans everything up automatically for you.
241 | You can opt out of this by setting the environment variable `QTL_SKIP_AUTO_CLEANUP` to `true`.
242 | Then in your tests, you can call the `cleanup` function when needed.
243 | For example:
244 |
245 | ```ts
246 | import {cleanup} from "@noma.to/qwik-testing-library";
247 | import {afterEach} from "vitest";
248 |
249 | afterEach(cleanup);
250 | ```
251 |
252 | Finally, edit your `tsconfig.json` to declare the following global types:
253 |
254 | ```diff
255 | // tsconfig.json
256 |
257 | {
258 | "compilerOptions": {
259 | "types": [
260 | + "vite/client",
261 | + "vitest/globals",
262 | + "@testing-library/jest-dom/vitest"
263 | ]
264 | },
265 | "include": ["src"]
266 | }
267 | ```
268 |
269 | [vitest]: https://vitest.dev/
270 |
271 | ## Examples
272 |
273 | Below are some examples of how to use `@noma.to/qwik-testing-library` to tests your Qwik components.
274 |
275 | You can also learn more about the [**queries**][tl-queries-docs] and [**user events**][tl-user-events-docs] over at the
276 | Testing Library website.
277 |
278 | [tl-queries-docs]: https://testing-library.com/docs/queries/about
279 |
280 | [tl-user-events-docs]: https://testing-library.com/docs/user-event/intro
281 |
282 | ### Qwikstart
283 |
284 | This is a minimal setup to get you started, with line-by-line explanations.
285 |
286 | ```tsx
287 | // counter.spec.tsx
288 |
289 | // import qwik-testing methods
290 | import {screen, render, waitFor} from "@noma.to/qwik-testing-library";
291 | // import the userEvent methods to interact with the DOM
292 | import {userEvent} from "@testing-library/user-event";
293 | // import the component to be tested
294 | import {Counter} from "./counter";
295 |
296 | // describe the test suite
297 | describe(" ", () => {
298 | // describe the test case
299 | it("should increment the counter", async () => {
300 | // setup user event
301 | const user = userEvent.setup();
302 | // render the component into the DOM
303 | await render( );
304 |
305 | // retrieve the 'increment count' button
306 | const incrementBtn = screen.getByRole("button", {name: /increment count/});
307 | // click the button twice
308 | await user.click(incrementBtn);
309 | await user.click(incrementBtn);
310 |
311 | // assert that the counter is now 2
312 | await waitFor(() => expect(screen.findByText(/count:2/)).toBeInTheDocument());
313 | });
314 | })
315 | ```
316 |
317 | ### Mocking Component Callbacks (experimental)
318 |
319 | > [!WARNING]
320 | > This feature is under a testing phase and thus experimental.
321 | > Its API may change in the future, so use it at your own risk.
322 |
323 | #### Setup
324 |
325 | Optionally, you can install `@noma.to/qwik-mock` to mock callbacks of Qwik components. It provides a `mock$` function
326 | that can be used to create a mock of a QRL and verify interactions on your Qwik components.
327 |
328 | ```shell
329 | npm install --save-dev @noma.to/qwik-mock
330 | ```
331 |
332 | It is _not_ a replacement of regular mocking functions (such as `vi.fn` and `vi.mock`) as its intended use is only for
333 | testing callbacks of Qwik components.
334 |
335 | #### Usage
336 |
337 | Here's an example on how to use the `mock$` function:
338 |
339 | ```tsx title="counter.spec.tsx"
340 | // import qwik-testing methods
341 | import {render, screen, waitFor} from "@noma.to/qwik-testing-library";
342 | // import qwik-mock methods
343 | import {mock$, clearAllMock} from "@noma.to/qwik-mock";
344 | // import the userEvent methods to interact with the DOM
345 | import {userEvent} from "@testing-library/user-event";
346 |
347 | // import the component to be tested
348 | import {Counter} from "./counter";
349 |
350 | // describe the test suite
351 | describe(" ", () => {
352 | // initialize a mock
353 | // note: the empty callback is required but currently unused
354 | const onChangeMock = mock$(() => {
355 | });
356 |
357 | // setup beforeEach block to run before each test
358 | beforeEach(() => {
359 | // remember to always clear all mocks before each test
360 | clearAllMocks();
361 | });
362 |
363 | // describe the 'on increment' test cases
364 | describe("on increment", () => {
365 | // describe the test case
366 | it("should call onChange$", async () => {
367 | // setup user event
368 | const user = userEvent.setup();
369 | // render the component into the DOM
370 | await render( );
371 |
372 | // retrieve the 'decrement' button
373 | const decrementBtn = screen.getByRole("button", {name: "Decrement"});
374 | // click the button
375 | await user.click(decrementBtn);
376 |
377 | // assert that the onChange$ callback was called with the right value
378 | // note: QRLs are async in Qwik, so we need to resolve them to verify interactions
379 | await waitFor(() =>
380 | expect(onChangeMock.resolve()).resolves.toHaveBeenCalledWith(-1),
381 | );
382 | });
383 | });
384 | })
385 | ```
386 |
387 | ### Qwik City - `server$` calls
388 |
389 | If one of your Qwik components uses `server$` calls, your tests might fail with a rather cryptic message (e.g. `QWIK
390 | ERROR __vite_ssr_import_0__.myServerFunctionQrl is not a function` or
391 | `QWIK ERROR Failed to parse URL from ?qfunc=DNpotUma33o`).
392 |
393 | We're happy to discuss it on [Discord][discord], but we consider this failure to be a good thing:
394 | your components should be tested in isolation, so you will be forced to mock your server functions.
395 |
396 | Here is an example of how to test a component that uses `server$` calls:
397 |
398 | ```ts
399 | // ~/server/blog-posts.ts
400 |
401 | import {server$} from "@builder.io/qwik-city";
402 | import {BlogPost} from "~/lib/blog-post";
403 |
404 | export const getLatestPosts$ = server$(function (): Promise {
405 | // get the latest posts
406 | return Promise.resolve([]);
407 | });
408 | ```
409 |
410 | ```tsx
411 | // ~/components/latest-post-list.spec.tsx
412 |
413 | import {render, screen, waitFor} from "@noma.to/qwik-testing-library";
414 | import {LatestPostList} from "./latest-post-list";
415 |
416 | vi.mock('~/server/blog-posts', () => ({
417 | // the mocked function should end with `Qrl` instead of `$`
418 | getLatestPostsQrl: () => {
419 | return Promise.resolve([{id: 'post-1', title: 'Post 1'}, {id: 'post-2', title: 'Post 2'}]);
420 | },
421 | }));
422 |
423 | describe(' ', () => {
424 | it('should render the latest posts', async () => {
425 | await render( );
426 |
427 | await waitFor(() => expect(screen.queryAllByRole('listitem')).toHaveLength(2));
428 | });
429 | });
430 | ```
431 |
432 | Notice how the mocked function is ending with `Qrl` instead of `$`, despite being named as `getLatestPosts$`.
433 | This is caused by the Qwik optimizer renaming it to `Qrl`.
434 | So, we need to mock the `Qrl` function instead of the original `$` one.
435 |
436 | If your function doesn't end with `$`, the Qwik optimizer will not rename it to `Qrl`.
437 |
438 | ## Gotchas
439 |
440 | * Watch mode (at least in Webstorm) doesn't seem to work well: components are not being updated with your latest changes
441 |
442 | ## Issues
443 |
444 | _Looking to contribute? Look for the [Good First Issue][good-first-issue]
445 | label._
446 |
447 | [good-first-issue]: https://github.com/ianlet/qwik-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+
448 |
449 | ### 🐛 Bugs
450 |
451 | Please file an issue for bugs, missing documentation, or unexpected behavior.
452 |
453 | [**See Bugs**][bugs]
454 |
455 | [bugs]: https://github.com/ianlet/qwik-testing-library/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc
456 |
457 | ### 💡 Feature Requests
458 |
459 | Please file an issue to suggest new features. Vote on feature requests by adding
460 | a 👍. This helps maintainers prioritize what to work on.
461 |
462 | [**See Feature Requests**][requests]
463 |
464 | [requests]: https://github.com/ianlet/qwik-testing-library/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen
465 |
466 | ### ❓ Questions
467 |
468 | For questions related to using the library, please visit a support community
469 | instead of filing an issue on GitHub.
470 |
471 | - [Discord][discord]
472 | - [Stack Overflow][stackoverflow]
473 |
474 | [stackoverflow]: https://stackoverflow.com/questions/tagged/qwik-testing-library
475 |
476 | ## Contributors
477 |
478 | Thanks goes to these people ([emoji key][emojis]):
479 |
480 |
481 |
482 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | This project follows the [all-contributors][all-contributors] specification.
507 | Contributions of any kind welcome!
508 |
509 | [emojis]: https://github.com/all-contributors/all-contributors#emoji-key
510 |
511 | [all-contributors]: https://github.com/all-contributors/all-contributors
512 |
513 | ## Acknowledgements
514 |
515 | Massive thanks to the [Qwik Team][qwik-team] and the whole community for their efforts to build Qwik and for
516 | the [inspiration][qwik-baseline] on how to create a testing library for Qwik.
517 |
518 | Thanks to the [Testing Library Team][tl-team] for a great set of tools to build better products, confidently, and
519 | qwikly.
520 |
521 | [qwik-team]: https://github.com/QwikDev/qwik/
522 |
523 | [qwik-baseline]: https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/testing
524 |
525 | [tl-team]: https://testing-library.com
526 |
--------------------------------------------------------------------------------