Skip to content

Commit ba7a5b5

Browse files
committed
feat(QwikCityMockProvider): custom actions mocks
1 parent 3840f02 commit ba7a5b5

File tree

7 files changed

+314
-68
lines changed

7 files changed

+314
-68
lines changed

.changeset/seven-poets-repeat.md

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,5 @@
11
---
2-
'@builder.io/qwik-city': patch
2+
'@builder.io/qwik-city': minor
33
---
44

5-
feat: allow mocking loaders in QwikCityMockProvider
6-
7-
8-
> 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
9-
10-
```ts title="src/loaders/product.loader.ts"
11-
import { routeLoader$ } from '@builder.io/qwik-city';
12-
13-
export const useProductData = routeLoader$(async () => {
14-
const res = await fetch('https://.../product');
15-
const product = (await res.json()) as Product;
16-
return product;
17-
});
18-
```
19-
20-
```tsx title="src/components/product.tsx"
21-
import { component$ } from '@builder.io/qwik';
22-
23-
import { useProductData } from '../loaders/product.loader';
24-
25-
export const Footer = component$(() => {
26-
const signal = useProductData();
27-
return <footer>Product name: {signal.value.product.name}</footer>;
28-
});
29-
```
30-
31-
```tsx title="src/components/product.spec.tsx"
32-
import { createDOM } from '@builder.io/qwik/testing';
33-
import { test, expect } from 'vitest';
34-
35-
import { Footer } from './product';
36-
import { useProductData } from '../loaders/product.loader';
37-
38-
test('should render footer with product name', async () => {
39-
const { screen, render } = await createDOM();
40-
const loadersMock = [
41-
{
42-
loader: useProductData,
43-
data: { product: { name: 'Test Product' } },
44-
},
45-
];
46-
await render(
47-
<QwikCityMockProvider loaders={loadersMock}>
48-
<Footer />
49-
</QwikCityMockProvider>,
50-
);
51-
expect(screen.innerHTML).toMatchSnapshot();
52-
});
53-
```
5+
feat: allow mocking route loaders & actions in `QwikCityMockProvider`

packages/docs/src/routes/api/qwik-city/api.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,34 @@
520520
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
521521
"mdFile": "qwik-city.qwik_city_scroller.md"
522522
},
523+
{
524+
"name": "QwikCityMockActionProp",
525+
"id": "qwikcitymockactionprop",
526+
"hierarchy": [
527+
{
528+
"name": "QwikCityMockActionProp",
529+
"id": "qwikcitymockactionprop"
530+
}
531+
],
532+
"kind": "Interface",
533+
"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>",
534+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
535+
"mdFile": "qwik-city.qwikcitymockactionprop.md"
536+
},
537+
{
538+
"name": "QwikCityMockLoaderProp",
539+
"id": "qwikcitymockloaderprop",
540+
"hierarchy": [
541+
{
542+
"name": "QwikCityMockLoaderProp",
543+
"id": "qwikcitymockloaderprop"
544+
}
545+
],
546+
"kind": "Interface",
547+
"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>",
548+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
549+
"mdFile": "qwik-city.qwikcitymockloaderprop.md"
550+
},
523551
{
524552
"name": "QwikCityMockProps",
525553
"id": "qwikcitymockprops",
@@ -530,7 +558,7 @@
530558
}
531559
],
532560
"kind": "Interface",
533-
"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>",
561+
"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>",
534562
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx",
535563
"mdFile": "qwik-city.qwikcitymockprops.md"
536564
},

packages/docs/src/routes/api/qwik-city/index.mdx

Lines changed: 156 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,120 @@ QWIK_CITY_SCROLLER = "_qCityScroller";
17391739
17401740
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)
17411741
1742+
## QwikCityMockActionProp
1743+
1744+
```typescript
1745+
export interface QwikCityMockActionProp<T = any>
1746+
```
1747+
1748+
<table><thead><tr><th>
1749+
1750+
Property
1751+
1752+
</th><th>
1753+
1754+
Modifiers
1755+
1756+
</th><th>
1757+
1758+
Type
1759+
1760+
</th><th>
1761+
1762+
Description
1763+
1764+
</th></tr></thead>
1765+
<tbody><tr><td>
1766+
1767+
[action](#)
1768+
1769+
</td><td>
1770+
1771+
</td><td>
1772+
1773+
[Action](#action)&lt;T&gt;
1774+
1775+
</td><td>
1776+
1777+
The action function to mock.
1778+
1779+
</td></tr>
1780+
<tr><td>
1781+
1782+
[handler](#)
1783+
1784+
</td><td>
1785+
1786+
</td><td>
1787+
1788+
QRL&lt;(data: T) =&gt; RouteActionResolver&gt;
1789+
1790+
</td><td>
1791+
1792+
The QRL function that will be called when the action is submitted.
1793+
1794+
</td></tr>
1795+
</tbody></table>
1796+
1797+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)
1798+
1799+
## QwikCityMockLoaderProp
1800+
1801+
```typescript
1802+
export interface QwikCityMockLoaderProp<T = any>
1803+
```
1804+
1805+
<table><thead><tr><th>
1806+
1807+
Property
1808+
1809+
</th><th>
1810+
1811+
Modifiers
1812+
1813+
</th><th>
1814+
1815+
Type
1816+
1817+
</th><th>
1818+
1819+
Description
1820+
1821+
</th></tr></thead>
1822+
<tbody><tr><td>
1823+
1824+
[data](#)
1825+
1826+
</td><td>
1827+
1828+
</td><td>
1829+
1830+
T
1831+
1832+
</td><td>
1833+
1834+
The data to return when the loader is called.
1835+
1836+
</td></tr>
1837+
<tr><td>
1838+
1839+
[loader](#)
1840+
1841+
</td><td>
1842+
1843+
</td><td>
1844+
1845+
[Loader](#loader_2)&lt;T&gt;
1846+
1847+
</td><td>
1848+
1849+
The loader function to mock.
1850+
1851+
</td></tr>
1852+
</tbody></table>
1853+
1854+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/qwik-city-component.tsx)
1855+
17421856
## QwikCityMockProps
17431857
17441858
```typescript
@@ -1764,6 +1878,32 @@ Description
17641878
</th></tr></thead>
17651879
<tbody><tr><td>
17661880
1881+
[actions?](#)
1882+
1883+
</td><td>
1884+
1885+
</td><td>
1886+
1887+
Array&lt;[QwikCityMockActionProp](#qwikcitymockactionprop)&lt;any&gt;&gt;
1888+
1889+
</td><td>
1890+
1891+
_(Optional)_ Allow mocking actions defined with `routeAction$` function.
1892+
1893+
```
1894+
[
1895+
{
1896+
action: useAddUser,
1897+
handler: $(async (data) => {
1898+
console.log('useAddUser action called with data:', data);
1899+
}),
1900+
},
1901+
];
1902+
```
1903+
1904+
</td></tr>
1905+
<tr><td>
1906+
17671907
[goto?](#)
17681908
17691909
</td><td>
@@ -1774,7 +1914,7 @@ Description
17741914
17751915
</td><td>
17761916
1777-
_(Optional)_
1917+
_(Optional)_ Allow mocking the `goto` function returned by `useNavigate` hook.
17781918
17791919
</td></tr>
17801920
<tr><td>
@@ -1785,11 +1925,20 @@ _(Optional)_
17851925
17861926
</td><td>
17871927
1788-
Array&lt;\{ loader: [Loader](#loader_2)&lt;any&gt;; data: any; }&gt;
1928+
Array&lt;[QwikCityMockLoaderProp](#qwikcitymockloaderprop)&lt;any&gt;&gt;
17891929
17901930
</td><td>
17911931
1792-
_(Optional)_
1932+
_(Optional)_ Allow mocking data for loaders defined with `routeLoader$` function.
1933+
1934+
```
1935+
[
1936+
{
1937+
loader: useProductData,
1938+
data: { product: { name: 'Test Product' } },
1939+
},
1940+
];
1941+
```
17931942
17941943
</td></tr>
17951944
<tr><td>
@@ -1804,7 +1953,7 @@ Record&lt;string, string&gt;
18041953
18051954
</td><td>
18061955
1807-
_(Optional)_
1956+
_(Optional)_ Allow mocking the route params returned by `useLocation` hook.
18081957
18091958
</td></tr>
18101959
<tr><td>
@@ -1819,7 +1968,9 @@ string
18191968
18201969
</td><td>
18211970
1822-
_(Optional)_
1971+
_(Optional)_ Allow mocking the url returned by `useLocation` hook.
1972+
1973+
Default: `http://localhost/`
18231974

18241975
</td></tr>
18251976
</tbody></table>

packages/docs/src/routes/docs/(qwikcity)/api/index.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,35 @@ test('should render footer with product name', async () => {
430430
});
431431
```
432432

433+
> The same approach can be used to mock `routeAction$`s by providing an `actions` prop to the `QwikCityMockProvider`
434+
435+
```tsx title="src/components/user-form.spec.tsx"
436+
import { $ } from '@builder.io/qwik';
437+
import { createDOM } from '@builder.io/qwik/testing';
438+
import { test, expect } from 'vitest';
439+
440+
import { UserForm } from './user-form';
441+
import { useAddUser } from '../loaders/add-user.action';
442+
443+
test('should call addUser action with correct data', async () => {
444+
const { screen, render } = await createDOM();
445+
const actionsMock = [
446+
{
447+
action: useAddUser,
448+
handler: $(async (data) => {
449+
console.log('useAddUser action called with data:', data);
450+
}),
451+
},
452+
];
453+
await render(
454+
<QwikCityMockProvider actions={actionsMock}>
455+
<UserForm />
456+
</QwikCityMockProvider>,
457+
);
458+
expect(screen.innerHTML).toMatchSnapshot();
459+
});
460+
```
461+
433462
## `<RouterOutlet>`
434463

435464
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.

packages/qwik-city/src/runtime/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export {
5050
type QwikCityProps,
5151
QwikCityProvider,
5252
type QwikCityMockProps,
53+
type QwikCityMockLoaderProp,
54+
type QwikCityMockActionProp,
5355
QwikCityMockProvider,
5456
QWIK_CITY_SCROLLER,
5557
} from './qwik-city-component';

0 commit comments

Comments
 (0)