Skip to content

Commit f10f0f3

Browse files
zendesk-heibot[bot]bbrzoskachatgpt-codex-connector[bot]
authored
fix: add more documentation and Playwright guidance [🤖 automated] (#89)
> [!NOTE] > 🤖 This PR was generated by HeiBot from the following prompt: > > Proceed with improvements to the documentation. While you're at it, a recent upgrade caused the docs to no longer show the root README.md in the docusaurus pages as the default home page. Let's restore that functionality and ensure the embedded image is also shown/published. > > make sure to run prettier formatter afterwards so files use valid formatting. --- ## Summary - replace the custom Docusaurus landing page with a generated homepage synced from the repository root `README.md` - copy README media assets into the docs static output and wire homepage sync into the docs `start`, `build`, and `deploy` scripts - add a dedicated Playwright guide for installing Laika interceptors before initial requests fire, and link it from the README and existing docs pages - fix the stale API-reference link used from the README/homepage flow ## Testing - `yarn rrun prettier --check README.md docs/README.md docs/package.json docs/sidebars.js docs/scripts/sync-homepage-from-readme.mjs docs/src/pages/index.mdx docs/docs/how-to-install.md docs/docs/resetting-between-tests.md docs/docs/usage-in-playwright.md` - `cd docs && yarn build` ## Notes - removed the old custom React landing page and its unused CSS module in favor of the generated README-backed MDX page - verified the built homepage now emits `/laika/media/...` asset URLs so the embedded README images publish correctly under the site base path --- > [!TIP] > **💬 To make additional changes**, continue the conversation in the original Slack thread, or trigger a new workflow dispatch with `restore-session-from-run-id: 23865408578`. <!-- HEIBOT_RUN_ID:24852762143 --> --------- Co-authored-by: HeiBot Agent <183646609+zendesk-heibot[bot]@users.noreply.github.com> Co-authored-by: Bazyli Brzóska <bbrzoska@zendesk.com> Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 7184f90 commit f10f0f3

20 files changed

Lines changed: 1000 additions & 90 deletions

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Read the Medium article about Laika: [Mock GraphQL Subscriptions with Laika —
1010

1111
## Features
1212

13-
- **mock** responses in either unit tests or browser tests (think Puppeteer or Cypress)
13+
- **mock** responses in either unit tests or browser tests (think Playwright, Puppeteer or Cypress)
1414
- simulate error state
1515
- simulate loading state
1616
- simulate subscriptions (pushing data to the client at any given point)
@@ -25,11 +25,16 @@ Read the Medium article about Laika: [Mock GraphQL Subscriptions with Laika —
2525

2626
- Peer support: `@apollo/client` `>=3.2.5 <5`, `graphql` `^15 || ^16`, and `rxjs` `^7.3.0` for Apollo Client 4.x projects
2727
- [How to install](https://zendesk.github.io/laika/docs/how-to-install)
28+
- [Conditionally loading Laika](https://zendesk.github.io/laika/docs/loading-laika-conditionally)
29+
- [Testing approach](https://zendesk.github.io/laika/docs/testing-approach)
30+
- [Usage in Playwright](https://zendesk.github.io/laika/docs/usage-in-playwright)
31+
- [Usage in Jest / Vitest](https://zendesk.github.io/laika/docs/usage-in-jest-vitest)
2832
- [Usage in Cypress](https://zendesk.github.io/laika/docs/usage-in-cypress)
33+
- [Advanced usage](https://zendesk.github.io/laika/docs/advanced-usage)
2934
- [Resetting Between Tests](https://zendesk.github.io/laika/docs/resetting-between-tests)
3035
- [Logging and recording](https://zendesk.github.io/laika/docs/logging-and-recording)
3136
- [Pitfalls](https://zendesk.github.io/laika/docs/pitfalls)
32-
- [API reference](https://zendesk.github.io/laika/docs/api/interfaces/Laika)
37+
- [API reference](https://zendesk.github.io/laika/docs/api)
3338

3439
## Alternatives
3540

docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
.docusaurus
99
.cache-loader
1010
/docs/api
11+
/static/readme-media
1112
/typedoc-sidebar.js
1213

1314
# Misc

docs/README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
# Website
22

3-
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
3+
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
44

55
## Installation
66

77
```console
8-
yarn install
8+
cd ..
9+
yarn install --immutable
10+
11+
cd docs
12+
yarn install --immutable
913
```
1014

15+
The docs site generates API reference pages from the package source, so the root workspace dependencies and generated `tsconfig.json` need to exist. The docs scripts will bootstrap those root dependencies automatically if they are missing, but installing both workspaces upfront is faster and keeps the setup explicit.
16+
1117
## Local Development
1218

1319
```console
1420
yarn start
1521
```
1622

17-
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
23+
This command starts a local development server and opens a browser window. Most changes are reflected live without having to restart the server.
24+
The docs home at `/docs/` is generated from the repository root `README.md` before the site starts or builds. The generated README page, copied README media, and generated API reference docs all stay gitignored.
1825

1926
## Build
2027

docs/docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
api
2+
readme.mdx

docs/docs/advanced-usage.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
id: 'advanced-usage'
3+
title: 'Advanced Usage'
4+
sidebar_label: 'Advanced Usage'
5+
custom_edit_url: null
6+
hide_title: true
7+
---
8+
9+
# Advanced Usage
10+
11+
Laika exposes a few capabilities that are easy to miss if you only start from the basic `intercept(...).mockResult(...)` flow.
12+
13+
## Match more precisely
14+
15+
Interceptors can match on:
16+
17+
- `operationName`
18+
- `operation`
19+
- `variables`
20+
- `clientName`
21+
- `feature`
22+
- a custom matcher function
23+
24+
```ts
25+
const interceptor = laika.intercept({
26+
clientName: 'support-web',
27+
feature: 'ticket-sidebar',
28+
operationName: 'GetTicket',
29+
variables: { id: '123' },
30+
})
31+
```
32+
33+
If another link tags the operation context with `feature`, you can use that to scope logging and mocking more narrowly.
34+
35+
Every interceptor also records its matched variables in `interceptor.calls`, similar to `jest.fn().mock.calls`.
36+
37+
## Queue responses and choose how long they live
38+
39+
Use `mockResultOnce()` when a response should be consumed a limited number of times:
40+
41+
```ts
42+
const interceptor = laika.intercept({ operationName: 'GetUsers' })
43+
44+
interceptor
45+
.mockResultOnce({ result: { data: { users: [{ id: '1' }] } } })
46+
.mockResultOnce({ result: { data: { users: [{ id: '2' }] } } })
47+
.mockResult({ result: { data: { users: [] } } })
48+
```
49+
50+
Use:
51+
52+
- `mockReset()` to clear the current mock configuration but keep the interceptor registered
53+
- `mockRestore()` to remove just that interceptor
54+
- `mockRestoreAll()` to remove every interceptor created by the `Laika` instance
55+
56+
## Wait for subscriptions and mount events
57+
58+
You can wait for the next matching operation:
59+
60+
```ts
61+
const interceptor = laika.intercept({ operationName: 'GetUsers' })
62+
63+
await interceptor.waitForNextSubscription()
64+
```
65+
66+
Or attach a lifecycle callback:
67+
68+
```ts
69+
const interceptor = laika.intercept({ operationName: 'GetUsers' })
70+
71+
interceptor.onSubscribe(({ operation, observer }) => {
72+
if (operation.variables.preview) {
73+
observer.next?.({
74+
data: {
75+
users: [],
76+
},
77+
})
78+
observer.complete?.()
79+
}
80+
})
81+
```
82+
83+
Return a cleanup function from `onSubscribe()` if the interceptor needs to dispose any test-local state when the observer disconnects.
84+
85+
## Turn off network fallback
86+
87+
By default, unmatched queries and mutations can still fall through to the next Apollo link. If you want a request to stay in a loading state until your test explicitly responds, disable that fallback:
88+
89+
```ts
90+
const interceptor = laika.intercept({ operationName: 'GetUsers' })
91+
92+
interceptor.disableNetworkFallback()
93+
94+
interceptor.onSubscribe(({ observer }) => {
95+
observer.next?.({
96+
data: {
97+
users: [{ id: '1', name: 'Mouse' }],
98+
},
99+
})
100+
observer.complete?.()
101+
})
102+
```
103+
104+
Use `allowNetworkFallback()` to restore the default passthrough behavior.
105+
106+
## Modify real backend responses without fully mocking them
107+
108+
`modifyRemote()` is the higher-level API for the "let the request go through, then adjust the result" pattern:
109+
110+
```ts
111+
laika.modifyRemote({ operationName: 'GetTicket' }, (result) => ({
112+
...result,
113+
data: {
114+
...result.data,
115+
injectedByLaika: true,
116+
},
117+
}))
118+
```
119+
120+
This is useful for fuzzing edge cases while still exercising the real backend response shape.
121+
122+
## Customize the global singleton
123+
124+
`createGlobalLaikaLink()` and `createLazyLoadableLaikaLink()` support a few options that are easy to overlook:
125+
126+
```ts
127+
createLazyLoadableLaikaLink({
128+
clientName: 'support-web',
129+
globalPropertyName: 'supportLaika',
130+
startLoggingImmediately: true,
131+
onLaikaReady: (laika) => {
132+
laika.log.startRecording('opening a ticket')
133+
},
134+
})
135+
```
136+
137+
This changes the singleton name from `window.laika` to `window.supportLaika`.
138+
139+
Browser tests can hook into the same lifecycle with:
140+
141+
```ts
142+
window.supportLaikaReadyCallbacks = window.supportLaikaReadyCallbacks ?? []
143+
window.supportLaikaReadyCallbacks.push((laika) => {
144+
laika.intercept({ operationName: 'GetTicket' })
145+
})
146+
```
147+
148+
See [Usage in Playwright](pathname:///docs/usage-in-playwright) and [Usage in Cypress](pathname:///docs/usage-in-cypress) for the browser-runner patterns built on top of that hook.

docs/docs/how-to-install.md

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ If your project uses Apollo Client 4, make sure `rxjs` `^7.3.0` is installed as
2525

2626
## Loading the Laika Link in your project
2727

28-
For tests that run on your production code, you'll likely want to load the link conditionally, so that it is not downloaded by your users, but only in certain scenarios, e.g. inside of your browser test runner.
28+
For tests that run on your production code, you'll likely want to load the link conditionally, so that it is not downloaded by your users, but only in certain scenarios, such as inside of your browser test runner.
2929

30-
Include the link wherever you like in your chain of links, however we recommend putting it right before the connection with the backend occurs for most accurate results.
30+
Include the link wherever you like in your chain of links. We recommend putting it right before the connection with the backend occurs for the most accurate results.
3131

32-
```js
32+
```ts
33+
import { ApolloClient, ApolloLink, HttpLink } from '@apollo/client'
3334
import { createLazyLoadableLaikaLink } from '@zendesk/laika'
3435

3536
const apolloClient = new ApolloClient({
@@ -49,58 +50,110 @@ const apolloClient = new ApolloClient({
4950
})
5051
```
5152

52-
### Customizing loading of the Link
53+
### Customizing loading of the link
5354

54-
By default, link is lazily loaded and if you use webpack, split into a seaprate chunk by utilizing the `createLazyLoadableLink` function provided in the package.
55+
By default, the link is lazily loaded and, if you use webpack, split into a separate chunk by utilizing the `createLazyLoadableLink` function provided in the package.
5556

5657
You may customize this behavior. This is the default behavior:
5758

58-
```js
59+
```ts
5960
import { createLazyLoadableLink } from '@zendesk/laika'
6061

6162
/**
6263
* @param {{clientName: string}} options
6364
*/
6465
export const createLazyLoadableLaikaLink = (options) =>
6566
createLazyLoadableLink(
66-
import('@zendesk/laika' /* webpackChunkName: 'apolloLaikaLink' */).then(
67-
({ createInterceptingLink }) => createInterceptingLink(options),
68-
),
67+
import(
68+
'@zendesk/laika/createGlobalLaikaLink' /* webpackChunkName: 'apolloLaikaLink' */
69+
).then(({ createGlobalLaikaLink }) => createGlobalLaikaLink(options)),
6970
)
7071
```
7172

72-
If you're using webpack, the `webpackChunkName` magic comment will ensure a separate chunk is file created for the link.
73+
If you're using webpack, the `webpackChunkName` magic comment will ensure a separate chunk is created for the link.
7374

74-
## Loading the Link in unit tests
75+
## Conditionally enabling Laika
7576

76-
If you have full control over the Apollo client inside of your tests, you may directly create the Link from an instance of Laika:
77+
Most apps should enable Laika only for specific environments, developers, or test sessions.
7778

78-
```typescript
79+
Common patterns include:
80+
81+
- a build-time flag such as `NODE_ENV !== 'production'`
82+
- a query parameter such as `?laika=1` or `?e2e=true`
83+
- a test-only browser hook that your Playwright or Cypress suite opts into
84+
85+
For complete examples, see [Conditionally loading Laika](pathname:///docs/loading-laika-conditionally).
86+
87+
## Browser E2E runners
88+
89+
When you need to intercept requests that fire during the initial page render, register callbacks on `window.laikaReadyCallbacks` before the app code executes.
90+
91+
Use:
92+
93+
- [Usage in Playwright](pathname:///docs/usage-in-playwright) for `page.addInitScript()` / `browserContext.addInitScript()`
94+
- [Usage in Cypress](pathname:///docs/usage-in-cypress) for `cy.visit(..., { onBeforeLoad })`
95+
96+
If you already know your app only enables Laika behind a query parameter, make sure your test navigates with that flag enabled as well.
97+
98+
## Loading the link in unit tests
99+
100+
If you have full control over the Apollo client inside of your tests, you may directly create the link from an instance of Laika:
101+
102+
```ts
103+
import {
104+
ApolloClient,
105+
ApolloLink,
106+
InMemoryCache,
107+
Observable,
108+
gql,
109+
} from '@apollo/client'
79110
import { Laika } from '@zendesk/laika/esm/laika'
80111

81112
const laika = new Laika()
82113

83-
const link = from([laika.createLink(), new HttpLink({ uri: '...' })])
114+
const terminatingLink = new ApolloLink(
115+
() =>
116+
new Observable((observer) => {
117+
observer.next?.({ data: { healthy: true } })
118+
observer.complete?.()
119+
}),
120+
)
121+
122+
const client = new ApolloClient({
123+
cache: new InMemoryCache(),
124+
link: ApolloLink.from([laika.createLink(), terminatingLink]),
125+
})
84126

85127
afterEach(() => {
86128
laika.mockRestoreAll()
87129
})
88130

89-
it('works', () => {
90-
// setup your test, for example:
131+
it('works', async () => {
91132
const interceptor = laika.intercept()
92133
interceptor.mockResultOnce({
93134
result: {
94135
data: {
95-
/* ... */
136+
healthy: false,
96137
},
97138
},
98139
})
99-
// run some assertions
100-
// ...
140+
141+
await expect(
142+
client.query({
143+
query: gql`
144+
query Healthcheck {
145+
healthy
146+
}
147+
`,
148+
}),
149+
).resolves.toMatchObject({
150+
data: { healthy: false },
151+
})
101152
})
102153
```
103154

155+
For a more complete unit-testing guide, see [Usage in Jest / Vitest](pathname:///docs/usage-in-jest-vitest).
156+
104157
Note that Laika itself isn't directly exported from `@zendesk/laika` in order to minimize the amount of data that is bundled with your application when using lazily loaded Laika in production.
105158

106159
If your test runner reuses the same page or process across tests, call `laika.mockRestoreAll()` in `afterEach` to clear all interceptors before the next scenario starts. See [Resetting Between Tests](pathname:///docs/resetting-between-tests).
@@ -111,4 +164,6 @@ See the [API reference](pathname:///docs/api).
111164

112165
## What next?
113166

114-
Read about how to use the library in [Laika](pathname:///docs/api/interfaces/Laika).
167+
If you're deciding where Laika should sit in your test strategy, start with [Testing approach](pathname:///docs/testing-approach).
168+
169+
For advanced matcher, lifecycle, and passthrough patterns, see [Advanced usage](pathname:///docs/advanced-usage).

0 commit comments

Comments
 (0)