Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/seven-poets-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/qwik-city': minor
---

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[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
172 changes: 169 additions & 3 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,31 @@ Description

</td><td>

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

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

[loaders?](#)

</td><td>

</td><td>

Array&lt;[QwikCityMockLoaderProp](#qwikcitymockloaderprop)&lt;any&gt;&gt;

</td><td>

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

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

</td></tr>
<tr><td>
Expand All @@ -1789,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 @@ -1804,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
78 changes: 77 additions & 1 deletion packages/docs/src/routes/docs/(qwikcity)/api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ test.each(cases)('should render card with %s %s', async ({text, link}) => {
});
```

> a `goto` prop can be passed to customize the `navigate` behavior during tests
> A `goto` prop can be passed to customize the `navigate` behavior during tests

```tsx title="src/components/button.spec.tsx"
import { $ } from '@builder.io/qwik';
Expand Down Expand Up @@ -383,6 +383,82 @@ test('should render the button and navigate', async () => {
});
```

> 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();
});
```

> 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