Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(QwikCityMockProvider): custom actions mocks
  • Loading branch information
alexismch committed Oct 31, 2025
commit ba7a5b5b58d7bf44ba870e372569ff8e85d2e5c5
52 changes: 2 additions & 50 deletions .changeset/seven-poets-repeat.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,5 @@
---
'@builder.io/qwik-city': patch
'@builder.io/qwik-city': minor
---

feat: allow mocking loaders in QwikCityMockProvider


> If you are using `routeLoader$`s in a Qwik Component that you want to test, you can provide a `loaders` prop to mock the data returned by the loaders

```ts title="src/loaders/product.loader.ts"
import { routeLoader$ } from '@builder.io/qwik-city';

export const useProductData = routeLoader$(async () => {
const res = await fetch('https://.../product');
const product = (await res.json()) as Product;
return product;
});
```

```tsx title="src/components/product.tsx"
import { component$ } from '@builder.io/qwik';

import { useProductData } from '../loaders/product.loader';

export const Footer = component$(() => {
const signal = useProductData();
return <footer>Product name: {signal.value.product.name}</footer>;
});
```

```tsx title="src/components/product.spec.tsx"
import { createDOM } from '@builder.io/qwik/testing';
import { test, expect } from 'vitest';

import { Footer } from './product';
import { useProductData } from '../loaders/product.loader';

test('should render footer with product name', async () => {
const { screen, render } = await createDOM();
const loadersMock = [
{
loader: useProductData,
data: { product: { name: 'Test Product' } },
},
];
await render(
<QwikCityMockProvider loaders={loadersMock}>
<Footer />
</QwikCityMockProvider>,
);
expect(screen.innerHTML).toMatchSnapshot();
});
```
feat: allow mocking route loaders & actions in `QwikCityMockProvider`
30 changes: 29 additions & 1 deletion packages/docs/src/routes/api/qwik-city/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,34 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
"mdFile": "qwik-city.qwik_city_scroller.md"
},
{
"name": "QwikCityMockActionProp",
"id": "qwikcitymockactionprop",
"hierarchy": [
{
"name": "QwikCityMockActionProp",
"id": "qwikcitymockactionprop"
}
],
"kind": "Interface",
"content": "```typescript\nexport interface QwikCityMockActionProp<T = any> \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[action](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[Action](#action)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\nThe action function to mock.\n\n\n</td></tr>\n<tr><td>\n\n[handler](#)\n\n\n</td><td>\n\n\n</td><td>\n\nQRL&lt;(data: T) =&gt; RouteActionResolver&gt;\n\n\n</td><td>\n\nThe QRL function that will be called when the action is submitted.\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
"mdFile": "qwik-city.qwikcitymockactionprop.md"
},
{
"name": "QwikCityMockLoaderProp",
"id": "qwikcitymockloaderprop",
"hierarchy": [
{
"name": "QwikCityMockLoaderProp",
"id": "qwikcitymockloaderprop"
}
],
"kind": "Interface",
"content": "```typescript\nexport interface QwikCityMockLoaderProp<T = any> \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[data](#)\n\n\n</td><td>\n\n\n</td><td>\n\nT\n\n\n</td><td>\n\nThe data to return when the loader is called.\n\n\n</td></tr>\n<tr><td>\n\n[loader](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[Loader](#loader_2)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\nThe loader function to mock.\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
"mdFile": "qwik-city.qwikcitymockloaderprop.md"
},
{
"name": "QwikCityMockProps",
"id": "qwikcitymockprops",
Expand All @@ -530,7 +558,7 @@
}
],
"kind": "Interface",
"content": "```typescript\nexport interface QwikCityMockProps \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[goto?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[RouteNavigate](#routenavigate)\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[loaders?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nArray&lt;{ loader: [Loader](#loader_2)<!-- -->&lt;any&gt;; data: any; }&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[params?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, string&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\n[url?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>",
"content": "```typescript\nexport interface QwikCityMockProps \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[actions?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nArray&lt;[QwikCityMockActionProp](#qwikcitymockactionprop)<!-- -->&lt;any&gt;&gt;\n\n\n</td><td>\n\n_(Optional)_ Allow mocking actions defined with `routeAction$` function.\n\n```\n[\n {\n action: useAddUser,\n handler: $(async (data) => {\n console.log('useAddUser action called with data:', data);\n }),\n },\n];\n```\n\n\n</td></tr>\n<tr><td>\n\n[goto?](#)\n\n\n</td><td>\n\n\n</td><td>\n\n[RouteNavigate](#routenavigate)\n\n\n</td><td>\n\n_(Optional)_ Allow mocking the `goto` function returned by `useNavigate` hook.\n\n\n</td></tr>\n<tr><td>\n\n[loaders?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nArray&lt;[QwikCityMockLoaderProp](#qwikcitymockloaderprop)<!-- -->&lt;any&gt;&gt;\n\n\n</td><td>\n\n_(Optional)_ Allow mocking data for loaders defined with `routeLoader$` function.\n\n```\n[\n {\n loader: useProductData,\n data: { product: { name: 'Test Product' } },\n },\n];\n```\n\n\n</td></tr>\n<tr><td>\n\n[params?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nRecord&lt;string, string&gt;\n\n\n</td><td>\n\n_(Optional)_ Allow mocking the route params returned by `useLocation` hook.\n\n\n</td></tr>\n<tr><td>\n\n[url?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nstring\n\n\n</td><td>\n\n_(Optional)_ Allow mocking the url returned by `useLocation` hook.\n\nDefault: `http://localhost/`\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
"mdFile": "qwik-city.qwikcitymockprops.md"
},
Expand Down
161 changes: 156 additions & 5 deletions packages/docs/src/routes/api/qwik-city/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,120 @@ QWIK_CITY_SCROLLER = "_qCityScroller";

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)

## QwikCityMockActionProp

```typescript
export interface QwikCityMockActionProp<T = any>
```

<table><thead><tr><th>

Property

</th><th>

Modifiers

</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

[action](#)

</td><td>

</td><td>

[Action](#action)&lt;T&gt;

</td><td>

The action function to mock.

</td></tr>
<tr><td>

[handler](#)

</td><td>

</td><td>

QRL&lt;(data: T) =&gt; RouteActionResolver&gt;

</td><td>

The QRL function that will be called when the action is submitted.

</td></tr>
</tbody></table>

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)

## QwikCityMockLoaderProp

```typescript
export interface QwikCityMockLoaderProp<T = any>
```

<table><thead><tr><th>

Property

</th><th>

Modifiers

</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

[data](#)

</td><td>

</td><td>

T

</td><td>

The data to return when the loader is called.

</td></tr>
<tr><td>

[loader](#)

</td><td>

</td><td>

[Loader](#loader_2)&lt;T&gt;

</td><td>

The loader function to mock.

</td></tr>
</tbody></table>

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)

## QwikCityMockProps

```typescript
Expand All @@ -1764,6 +1878,32 @@ Description
</th></tr></thead>
<tbody><tr><td>

[actions?](#)

</td><td>

</td><td>

Array&lt;[QwikCityMockActionProp](#qwikcitymockactionprop)&lt;any&gt;&gt;

</td><td>

_(Optional)_ Allow mocking actions defined with `routeAction$` function.

```
[
{
action: useAddUser,
handler: $(async (data) => {
console.log('useAddUser action called with data:', data);
}),
},
];
```

</td></tr>
<tr><td>

[goto?](#)

</td><td>
Expand All @@ -1774,7 +1914,7 @@ Description

</td><td>

_(Optional)_
_(Optional)_ Allow mocking the `goto` function returned by `useNavigate` hook.

</td></tr>
<tr><td>
Expand All @@ -1785,11 +1925,20 @@ _(Optional)_

</td><td>

Array&lt;\{ loader: [Loader](#loader_2)&lt;any&gt;; data: any; }&gt;
Array&lt;[QwikCityMockLoaderProp](#qwikcitymockloaderprop)&lt;any&gt;&gt;

</td><td>

_(Optional)_
_(Optional)_ Allow mocking data for loaders defined with `routeLoader$` function.

```
[
{
loader: useProductData,
data: { product: { name: 'Test Product' } },
},
];
```

</td></tr>
<tr><td>
Expand All @@ -1804,7 +1953,7 @@ Record&lt;string, string&gt;

</td><td>

_(Optional)_
_(Optional)_ Allow mocking the route params returned by `useLocation` hook.

</td></tr>
<tr><td>
Expand All @@ -1819,7 +1968,9 @@ string

</td><td>

_(Optional)_
_(Optional)_ Allow mocking the url returned by `useLocation` hook.

Default: `http://localhost/`

</td></tr>
</tbody></table>
Expand Down
29 changes: 29 additions & 0 deletions packages/docs/src/routes/docs/(qwikcity)/api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,35 @@ test('should render footer with product name', async () => {
});
```

> The same approach can be used to mock `routeAction$`s by providing an `actions` prop to the `QwikCityMockProvider`

```tsx title="src/components/user-form.spec.tsx"
import { $ } from '@builder.io/qwik';
import { createDOM } from '@builder.io/qwik/testing';
import { test, expect } from 'vitest';

import { UserForm } from './user-form';
import { useAddUser } from '../loaders/add-user.action';

test('should call addUser action with correct data', async () => {
const { screen, render } = await createDOM();
const actionsMock = [
{
action: useAddUser,
handler: $(async (data) => {
console.log('useAddUser action called with data:', data);
}),
},
];
await render(
<QwikCityMockProvider actions={actionsMock}>
<UserForm />
</QwikCityMockProvider>,
);
expect(screen.innerHTML).toMatchSnapshot();
});
```

## `<RouterOutlet>`

The `RouterOutlet` component is responsible for rendering the matched route at a given moment, it internally uses the [`useContent()`](/docs/(qwikcity)/api/index.mdx#usecontent) to render the current page, as well as all of the nested layouts.
Expand Down
2 changes: 2 additions & 0 deletions packages/qwik-city/src/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export {
type QwikCityProps,
QwikCityProvider,
type QwikCityMockProps,
type QwikCityMockLoaderProp,
type QwikCityMockActionProp,
QwikCityMockProvider,
QWIK_CITY_SCROLLER,
} from './qwik-city-component';
Expand Down
Loading
Loading