@servicetitan/docs-anvil-uikit-contrib 25.4.1 → 25.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## MockApiCalls
|
|
6
|
+
|
|
7
|
+
When it isn't practical to [mock the API service classes](frontend/unit-testing#migrating-from-autogenerated-api-mocks), use `MockApiCalls` to configure responses for API endpoints.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
describe('[admin] AppRoutes', () => {
|
|
11
|
+
// Create helper to mock API calls
|
|
12
|
+
const apiResponses = MockApiCalls();
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
apiResponses.reset();
|
|
16
|
+
// Configure /Admin/isGoEnvironment to return false
|
|
17
|
+
apiResponses.add(/Admin\/isGoEnvironment/, { body: () => false });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('when user is not authorized', () => {
|
|
21
|
+
// Configure /Admin/GetClientData to return 401 Unauthorized
|
|
22
|
+
beforeEach(() => apiResponses.add(/Admin\/GetClientData/, { status: 401 }));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It exposes the following methods:
|
|
28
|
+
|
|
29
|
+
- [add](#add): Configure an API response
|
|
30
|
+
- [replace](#replace): Replace a previously configured response
|
|
31
|
+
- [reset](#reset): Remove all previously configured responses
|
|
32
|
+
|
|
33
|
+
### add
|
|
34
|
+
|
|
35
|
+
Configure API response.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
add(
|
|
39
|
+
path: RegExp,
|
|
40
|
+
response: {
|
|
41
|
+
body?: (url: string) => any,
|
|
42
|
+
status?: number,
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `path`: regular expression to match against GET request URLs. If a URL matches more than one path, the first one wins.
|
|
48
|
+
- `body`: function that returns the API response. If omitted, or if it returns `undefined`, the mock returns an empty object (`{}`).
|
|
49
|
+
- `status`: HTTP status code for API response. Defaults to 200.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// Configure GET /desktop/version.json to return test clientVersion
|
|
53
|
+
apiResponses.add(/desktop\/version\.json/, { body: () => ({ clientVersion }) });
|
|
54
|
+
|
|
55
|
+
// Configure GET /Admin/GetClientData to return 401 status
|
|
56
|
+
apiResponses.add(/Admin\/GetClientData/, { status: 401 });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Note:** `MockApiCalls` passes the request URL to the `body` function so that it can customize the return value based on query params or other attributes. This also facilitates logging API calls for debugging purposes.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// Log all API calls
|
|
63
|
+
apiResponses.add(/.*/, { body: (url: string) => console.log(`GET ${url}`) });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### replace
|
|
67
|
+
|
|
68
|
+
Replace a previously configured API response.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
replace(
|
|
72
|
+
path: RegExp,
|
|
73
|
+
response: {
|
|
74
|
+
body?: (url: string) => any,
|
|
75
|
+
status?: number,
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `path`: the exact regular expression to replace
|
|
81
|
+
- `response`: see [add](#add)
|
|
82
|
+
|
|
83
|
+
The `path` must exactly match a previously configured API response. Otherwise, the function does nothing.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// Change GET /Admin/GetClientData to return 403 status
|
|
87
|
+
apiResponses.replace(/Admin\/GetClientData/, { status: 403 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### reset
|
|
91
|
+
|
|
92
|
+
Remove all previously configured responses.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
apiResponses.reset();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## mockComponent
|
|
99
|
+
|
|
100
|
+
Use `mockComponent` with `jest.mock` to replace a React component with a stub that renders a placeholder instead of the actual component. Test cans then use the [`toContainComponent`](./matchers/#tocontaincomponent) matcher to confirm the component was rendered correctly.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
mockComponent(
|
|
104
|
+
name: string,
|
|
105
|
+
options?: {
|
|
106
|
+
renderChildren?: boolean = false,
|
|
107
|
+
renderProps?: boolean = true,
|
|
108
|
+
forwardRef?: boolean = false,
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- `renderChildren`: Set to `true` to render both the component and it's children Defaults to `false`.
|
|
114
|
+
- `renderProps`: Set to `true` to render the component's props. Defaults to `true`.
|
|
115
|
+
- `forwardRef`: Set to `true` when mocking a component that is wrapped within `React.forwardRef`. Defaults to `false`.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Replace <BillingNewAdmin> component with placeholder
|
|
119
|
+
jest.mock('../modules/billing/components/billing-new-admin', () =>
|
|
120
|
+
mockComponent('BillingNewAdmin')
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Check that placeholder was rendered
|
|
124
|
+
expect(screen).toContainComponent('BillingNewAdmin');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## mockLocation
|
|
128
|
+
|
|
129
|
+
Use `mockLocation` to change or mock `window.location`.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
mockLocation(
|
|
133
|
+
newLocation: string | {
|
|
134
|
+
origin?: string,
|
|
135
|
+
pathname?: string,
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
When called with a string, it sets `location.pathname`.
|
|
141
|
+
|
|
142
|
+
Use the object signature to change either or both `location.origin` and `location.pathname`.
|
|
143
|
+
|
|
144
|
+
- `origin`: new `location.origin`. Defaults to preserving previous value.
|
|
145
|
+
- `pathname`: new `location.pathname`. Defaults to preserving previous value.
|
|
146
|
+
|
|
147
|
+
To mock `window.location` without changing the value, pass an empty object.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// Change location.pathname to "/login" (location.origin remains unchanged)
|
|
151
|
+
mockLocation('/login');
|
|
152
|
+
|
|
153
|
+
// Change location.origin to "https//jest.st.dev" (location.pathname remains unchanged)
|
|
154
|
+
mockLocation({ origin: 'https://jest.st.dev' });
|
|
155
|
+
|
|
156
|
+
// Enable spying on window.location
|
|
157
|
+
mockLocation({});
|
|
158
|
+
// ...
|
|
159
|
+
expect(window.location.reload).toHaveBeenCalled();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## mockStyles
|
|
163
|
+
|
|
164
|
+
Use `mockStyles` with `jest.mock` to mock imported CSS styles.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// Mock CSS styles
|
|
168
|
+
jest.mock('../header.module.less', () => mockStyles());
|
|
169
|
+
import * as Styles from '../header.module.less';
|
|
170
|
+
|
|
171
|
+
// Check that style was rendered
|
|
172
|
+
expect(screen).toContainComponent('Header', { className: Styles.header });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## mockWithDefaultLayout
|
|
176
|
+
|
|
177
|
+
Use `mockWithDefaultLayout` with `jest.mock` to mock `withDefaultLayout`.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
jest.mock('../modules/common/components/default-layout', () => ({
|
|
181
|
+
withDefaultLayout: mockWithDefaultLayout(),
|
|
182
|
+
...mockComponent('DefaultLayout'),
|
|
183
|
+
}));
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## renderHookWithProvider
|
|
187
|
+
|
|
188
|
+
Use `renderHookWithProvider` to [render a hook](https://react-hooks-testing-library.com/reference/api#renderhook) within a `<Provider>` wrapper with the specified singletons.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
renderHookWithProvider<TProps, TResult>(
|
|
192
|
+
singletons: ProviderProps['singletons'],
|
|
193
|
+
callback: (props: TProps) => TResult,
|
|
194
|
+
options?: RenderHookOptions<TProps>
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const subject = () => {
|
|
200
|
+
const { result } = renderHookWithProvider(
|
|
201
|
+
[
|
|
202
|
+
{ provide: MainAppStore, useValue: mainAppStore },
|
|
203
|
+
{ provide: AppUserStore, useValue: appUserStore },
|
|
204
|
+
{ provide: FeatureStore, useValue: featureStore },
|
|
205
|
+
],
|
|
206
|
+
() => useTenantName()
|
|
207
|
+
);
|
|
208
|
+
return result.current;
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## setupApp
|
|
213
|
+
|
|
214
|
+
Use `setupApp` to mock the Monolith's global `window.App` state. It restores `window.App` to a clean state before applying the specified values.
|
|
215
|
+
|
|
216
|
+
To preserve the previous state, spread `window.App` into the argument.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
setupApp({
|
|
220
|
+
[key: string]: Record<string, any> | undefined;
|
|
221
|
+
AppUser?: Record<string, any>;
|
|
222
|
+
Data?: Record<string, any>;
|
|
223
|
+
Enum?: Record<string, any>;
|
|
224
|
+
Features?: Record<string, any>;
|
|
225
|
+
Permissions?: Record<string, any>;
|
|
226
|
+
Services?: Record<string, any>;
|
|
227
|
+
UserNotifications?: Record<string, any>;
|
|
228
|
+
Utils?: Record<string, any>;
|
|
229
|
+
})
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// Mock window.App.IsSandboxedProductionInstance and window.App.IsStageInstance
|
|
234
|
+
// (resets other attributes to initial state)
|
|
235
|
+
setupApp({ IsSandboxedProductionInstance: () => false, IsStageInstance: () => false });
|
|
236
|
+
|
|
237
|
+
// Mock window.App.Features (resets other attributes to initial state)
|
|
238
|
+
beforeEach(() => {
|
|
239
|
+
setupApp({
|
|
240
|
+
Features: {
|
|
241
|
+
EnableDispatchCenter: true,
|
|
242
|
+
DispatchCenterApiUrl: 'DispatchCenterApiUrl',
|
|
243
|
+
DispatchCenterMicroFrontendUrl: 'DispatchCenterMicroFrontendUrl',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Mock window.App.LogService and keep previous state of other attributes
|
|
249
|
+
setupApp({ ...window.App, LogService: { error: jest.fn() } });
|
|
250
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Queries
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Testing Library includes custom [queries](https://testing-library.com/docs/queries/about) that find elements with ServiceTitan specific attributes.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
To use custom queries, import them from Testing Library and merge them with the default set of queries attached to rendered components.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { queries, render } from '@testing-library/react'; // Import default queries
|
|
13
|
+
import { queries as customQueries } from '@servicetitan/testing-library'; // Import custom queries
|
|
14
|
+
|
|
15
|
+
// ...
|
|
16
|
+
|
|
17
|
+
const subject = () => {
|
|
18
|
+
return render(
|
|
19
|
+
<MyComponent />,
|
|
20
|
+
// Merge custom queries with defaults
|
|
21
|
+
{ queries: { ...queries, ...customQueries } }
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## ByAnvilComponentName
|
|
27
|
+
|
|
28
|
+
> getByAnvilComponentName, queryByAnvilComponentName, getAllByAnvilComponentName, queryAllByAnvilComponentName, findByAnvilComponentName, findAllByAnvilComponentName
|
|
29
|
+
|
|
30
|
+
Searches for all elements that have a node with the `data-anvil-component` attribute and content matching the given `Matcher`.
|
|
31
|
+
|
|
32
|
+
**Use with caution.** Using Anvil attributes does not resemble how users see the software and should be avoided. Use this only when queries for user-visible attributes (e.g., **ByRole**, **ByText**) don't work.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
ByAnvilComponentName(
|
|
36
|
+
container: HTMLElement,
|
|
37
|
+
id: Matcher,
|
|
38
|
+
options?: MatcherOptions,
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// Custom queries are bound to render result
|
|
44
|
+
const { getByAnvilComponentName } = subject();
|
|
45
|
+
|
|
46
|
+
// Check that subject() rendered Anvil icon with specified class
|
|
47
|
+
expect(getByAnvilComponentName('Icon')).toHaveClass('foo');
|
|
48
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Matchers
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## toBeWithinRange
|
|
6
|
+
|
|
7
|
+
Checks whether a value equals another value, plus or minus the specified delta.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
toBeWithinRange(
|
|
11
|
+
value: number,
|
|
12
|
+
delta: number
|
|
13
|
+
)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// Check whether actual equals expected ± 0.01
|
|
18
|
+
expect(actual).toBeWithinRange(expect, 0.01);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## toContainAnvilComponent
|
|
22
|
+
|
|
23
|
+
Checks whether an element contains an Anvil component (see [ByAnvilComponentName](./queries/#byanvilcomponentname)) with the specified name.
|
|
24
|
+
|
|
25
|
+
**Use with caution.** Using Anvil attributes does not resemble how users see the software and should be avoided. Use this only when queries for user-visible attributes (e.g., **ByRole**, **ByText**) don't work.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
toContainAnvilComponent(
|
|
29
|
+
name: string,
|
|
30
|
+
options?: SelectorMatcherOptions
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// Check that Anvil Icon component was rendered
|
|
36
|
+
expect(screen).toContainAnvilComponent('Icon');
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## toContainClassName
|
|
40
|
+
|
|
41
|
+
Checks whether an element contains another element with the specified class.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
toContainClassName(
|
|
45
|
+
className: string
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// Check that element with CSS class .in-partner-portal was rendered
|
|
51
|
+
expect(screen).toContainClassName('in-partner-portal');
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## toContainComponent
|
|
55
|
+
|
|
56
|
+
Checks whether an element contains a [mocked component](./api/#mockcomponent) with the specified name and React props.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
toContainComponent(
|
|
60
|
+
name: string,
|
|
61
|
+
props?: Record<string, any>,
|
|
62
|
+
options?: {
|
|
63
|
+
exact?: boolean = true,
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `name`: the mocked component's name
|
|
69
|
+
- `props`: props to check were passed to the component
|
|
70
|
+
- `exact`: controls whether only specified the props are allowed. Defaults to `true`.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// Check that <MarketingModule /> was rendered (with no props)
|
|
74
|
+
expect(screen).toContainComponent('MarketingModule');
|
|
75
|
+
|
|
76
|
+
// Check that <Redirect to="/login" /> was rendered
|
|
77
|
+
expect(screen).toContainComponent('Redirect', { to: '/login' });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To allow partial matches, set `exact` to `false`. For example,
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// Check that exactly <Tag color="critical" /> was render
|
|
84
|
+
expect(screen).toContainComponent('Tag', { color: 'critical' });
|
|
85
|
+
|
|
86
|
+
// Check that <Tag /> was rendered with at least color="critical", and possibly other options
|
|
87
|
+
expect(screen).toContainComponent('Tag', { color: 'critical' }, { exact: false });
|
|
88
|
+
|
|
89
|
+
// Check that <Tag /> was rendered with or without any options
|
|
90
|
+
expect(screen).toContainComponent('Tag', {}, { exact: false });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## toContainText
|
|
94
|
+
|
|
95
|
+
Checks whether an element contains the specified text.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
toContainText(
|
|
99
|
+
text?: string | RegExp,
|
|
100
|
+
options?: SelectorMatcherOptions,
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Check that screen contains instructions to download Chrome
|
|
106
|
+
expect(screen).toContainText(/download.*Chrome/i);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
To check whether the element contains any text at all, omit the `text` argument. For example,
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// Check that any text was rendered
|
|
113
|
+
expect(screen).toContainText();
|
|
114
|
+
|
|
115
|
+
// Check that no text was rendered
|
|
116
|
+
expect(screen).not.toContainText();
|
|
117
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Testing Library
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
`@servicetitan/testing-library` is a light-weight solution for testing ServiceTitan components that builds on top of [`@testing-library/react`](https://testing-library.com/docs/react-testing-library/intro/) and encourages testing best practices.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
This project should be installed as one of your project's `devDependencies`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @servicetitan/testing-library
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This library has `peerDependencies` for:
|
|
16
|
+
|
|
17
|
+
- `@servicetitan/react-ioc`: >=22.0.0
|
|
18
|
+
- `@testing-library/react`: ^12.0.0
|
|
19
|
+
- `@testing-library/react-hooks`: ^7.0.0
|
|
20
|
+
- `react`: >=17.0.0
|
|
21
|
+
|
|
22
|
+
**Note:** This library is not compatible with React Testing Library versions 13+.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/docs-anvil-uikit-contrib",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,5 +16,5 @@
|
|
|
16
16
|
"cli": {
|
|
17
17
|
"webpack": false
|
|
18
18
|
},
|
|
19
|
-
"gitHead": "
|
|
19
|
+
"gitHead": "2adbff52245b96041ba194c5b904a9cb05f0e0e3"
|
|
20
20
|
}
|