Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
Loading