From 287f18dd4b2bae50d28028234795989fec57b02c Mon Sep 17 00:00:00 2001 From: Alexis Michiels Date: Wed, 29 Oct 2025 22:56:07 +0100 Subject: [PATCH 1/6] feat(QwikCityMockProvider): custom loaders mocks --- .../src/runtime/src/qwik-city-component.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/qwik-city/src/runtime/src/qwik-city-component.tsx b/packages/qwik-city/src/runtime/src/qwik-city-component.tsx index 79ca6c3cb16..7b468678c9b 100644 --- a/packages/qwik-city/src/runtime/src/qwik-city-component.tsx +++ b/packages/qwik-city/src/runtime/src/qwik-city-component.tsx @@ -39,6 +39,7 @@ import type { Editable, EndpointResponse, LoadedRoute, + LoaderConstructor, MutableRouteLocation, PageModule, PreventNavigateCallback, @@ -661,6 +662,10 @@ export interface QwikCityMockProps { url?: string; params?: Record; goto?: RouteNavigate; + loaders?: { + loader: LoaderConstructor; + data: any; + }[]; } /** @public */ @@ -677,7 +682,13 @@ export const QwikCityMockProvider = component$((props) => { { deep: false } ); - const loaderState = useSignal({}); + const loaderState = useStore( + props.loaders?.reduce( + (acc, { loader, data }) => ({ ...acc, [(loader as any).__id]: data }), + {} as Record + ) ?? {}, + { deep: false } + ); const routeInternal = useSignal({ type: 'initial', dest: url }); const goto: RouteNavigate = From e6a6463da6e0c937e87ce6c8c6c7ad673067be17 Mon Sep 17 00:00:00 2001 From: Alexis Michiels Date: Wed, 29 Oct 2025 22:57:07 +0100 Subject: [PATCH 2/6] docs(qwik-city): loaders mocking --- .../src/routes/docs/(qwikcity)/api/index.mdx | 49 ++++++++++++++++++- .../src/runtime/src/qwik-city.runtime.api.md | 5 ++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx index ebfadd516cb..a2213f00438 100644 --- a/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx +++ b/packages/docs/src/routes/docs/(qwikcity)/api/index.mdx @@ -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'; @@ -383,6 +383,53 @@ 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
Product name: {signal.value.product.name}
; +}); +``` + +```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( + +