@burojs/test-helpers 0.3.0 → 0.4.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.
- package/README.md +13 -1
- package/dist/index.cjs +159 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +413 -170
- package/dist/index.d.ts +413 -170
- package/dist/index.js +158 -299
- package/dist/index.js.map +1 -1
- package/dist/provider-contract.cjs +273 -0
- package/dist/provider-contract.cjs.map +1 -0
- package/dist/provider-contract.d.cts +60 -0
- package/dist/provider-contract.d.ts +60 -0
- package/dist/provider-contract.js +272 -0
- package/dist/provider-contract.js.map +1 -0
- package/package.json +21 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,42 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/types.d.ts
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* The page surface the page objects compose testids on.
|
|
4
|
+
*
|
|
5
|
+
* **The locator type is a parameter, not a fixed shape.** Every factory in
|
|
6
|
+
* this package is written as `createX<L>(page: PlaywrightPage<L>, …): X<L>`,
|
|
7
|
+
* so `L` is INFERRED from whatever page is handed in: a real
|
|
8
|
+
* `@playwright/test` `Page` yields real `Locator`s — which is what makes
|
|
9
|
+
* `expect(view.row(id).cell('status')).toHaveText('paid')` type-check — while
|
|
10
|
+
* a hand-written double (see `tests/_mock-page.ts`) yields the double's own
|
|
11
|
+
* locator type and still exercises the string composition without a browser.
|
|
12
|
+
*
|
|
13
|
+
* It did not always work that way, and the reason is worth keeping.
|
|
14
|
+
* `PlaywrightPage` used to be a fixed structural subset whose members
|
|
15
|
+
* returned a fixed structural `PlaywrightLocator`. Under `strictFunctionTypes`
|
|
16
|
+
* a real `Page` was NOT assignable to it — its `getByRole` takes Playwright's
|
|
17
|
+
* `AriaRole` union, the subset declared the wider `string`, and a parameter
|
|
18
|
+
* may not widen — so `createBuroPage(realPage)` was a type error. Even had it
|
|
19
|
+
* been assignable, every locator came back as the structural type, which
|
|
20
|
+
* `expect()` does not accept, leaving a consumer with `isVisible()`-style
|
|
21
|
+
* boolean reads and no web-first (auto-waiting) assertion at all. Both defects
|
|
22
|
+
* were invisible for as long as the only caller was this package's own mock:
|
|
23
|
+
* the first real consumer (`examples/marketplace-ops/e2e`) hit them on its
|
|
24
|
+
* first line. Keep the parameter.
|
|
25
|
+
*
|
|
26
|
+
* Only the members the factories actually CALL are required. `getByRole`,
|
|
27
|
+
* `click`, `fill`, `textContent` and `isVisible` were declared here and used
|
|
28
|
+
* by nothing — the factories return locators and let the caller drive them —
|
|
29
|
+
* and `getByRole` in particular was the member that broke assignability, so
|
|
30
|
+
* requiring it bought nothing and cost everything.
|
|
6
31
|
*/
|
|
7
|
-
interface
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
interface PlaywrightPage<TLocator = unknown> {
|
|
33
|
+
goto: (url: string) => Promise<unknown>;
|
|
34
|
+
getByTestId: (id: string) => TLocator;
|
|
35
|
+
/**
|
|
36
|
+
* Attribute/CSS selection. Needed for the SET-shaped accessors — "every
|
|
37
|
+
* rendered row", "this column across every row" — which `getByTestId`
|
|
38
|
+
* cannot express: those testids embed a record id, so they can only be
|
|
39
|
+
* addressed by prefix.
|
|
40
|
+
*/
|
|
41
|
+
locator: (selector: string) => TLocator;
|
|
42
|
+
url: () => string;
|
|
16
43
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The one capability the page objects need from a locator THEMSELVES rather
|
|
46
|
+
* than handing it to the caller: {@link RowObject.open} clicks the row it
|
|
47
|
+
* addresses. Everything else in this package returns a locator and stops, so
|
|
48
|
+
* this is the whole constraint the locator parameter carries.
|
|
49
|
+
*/
|
|
50
|
+
interface ClickableLocator {
|
|
51
|
+
click: () => Promise<unknown>;
|
|
24
52
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A convenience shape for writing a page DOUBLE (`PlaywrightPage<PlaywrightLocator>`).
|
|
55
|
+
* Nothing in this package requires it — a real consumer passes a real `Page`
|
|
56
|
+
* and gets real `Locator`s back.
|
|
57
|
+
*/
|
|
58
|
+
interface PlaywrightLocator {
|
|
59
|
+
click: () => Promise<void>;
|
|
60
|
+
fill: (value: string) => Promise<void>;
|
|
61
|
+
textContent: () => Promise<string | null>;
|
|
62
|
+
getByTestId: (id: string) => PlaywrightLocator;
|
|
63
|
+
isVisible: () => Promise<boolean>;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/form-view.d.ts
|
|
67
|
+
interface FormFieldObject<L> {
|
|
68
|
+
input: () => L;
|
|
69
|
+
error: () => L;
|
|
70
|
+
help: () => L;
|
|
30
71
|
}
|
|
31
|
-
interface FormView {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
72
|
+
interface FormView<L extends ClickableLocator> {
|
|
73
|
+
field: (fieldName: string) => FormFieldObject<L>;
|
|
74
|
+
/**
|
|
75
|
+
* One declared field GROUP.
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ Renamed from `section()` at SP9 task 5: it asked for `form-section:…`
|
|
78
|
+
* and `<ResourceForm>` has always rendered `form-group:…`, so it had never
|
|
79
|
+
* matched an element. See `src/picker-object.ts` for the wider sweep and for
|
|
80
|
+
* why the cross-vocabulary test could not see any of it.
|
|
81
|
+
*/
|
|
82
|
+
group: (groupKey: string) => L;
|
|
83
|
+
submit: () => L;
|
|
84
|
+
cancel: () => L;
|
|
85
|
+
action: (actionName: string) => L;
|
|
37
86
|
}
|
|
38
|
-
declare function createFormView(page: PlaywrightPage
|
|
39
|
-
|
|
87
|
+
declare function createFormView<L extends ClickableLocator>(page: PlaywrightPage<L>, resource: string, view: 'create' | 'edit' | 'show'): FormView<L>;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/list-view.d.ts
|
|
40
90
|
/**
|
|
41
91
|
* A row's address inside the filter-builder draft: `[]` is the root group,
|
|
42
92
|
* `[0]` is its first row, `[1, 0]` is the first row of the group nested at
|
|
@@ -47,155 +97,348 @@ declare function createFormView(page: PlaywrightPage, resource: string, view: 'c
|
|
|
47
97
|
* `list-filter:<r>:group::add-row`).
|
|
48
98
|
*/
|
|
49
99
|
type FilterRowPath = readonly number[];
|
|
50
|
-
interface ListView {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
interface ListView<L extends ClickableLocator> {
|
|
101
|
+
goto: () => Promise<void>;
|
|
102
|
+
row: (recordId: string) => RowObject<L>;
|
|
103
|
+
/**
|
|
104
|
+
* EVERY rendered row of this resource, as one locator — the set, not a
|
|
105
|
+
* named member of it. `row(recordId)` cannot express it: a row's testid
|
|
106
|
+
* embeds its record id, so the set is only addressable by prefix.
|
|
107
|
+
*
|
|
108
|
+
* This is the accessor a list assertion is actually built on
|
|
109
|
+
* (`await expect(list.rows()).toHaveCount(n)`), and it was missing for as
|
|
110
|
+
* long as the only consumer of this package was a mock that never counted
|
|
111
|
+
* anything.
|
|
112
|
+
*/
|
|
113
|
+
rows: () => L;
|
|
114
|
+
/**
|
|
115
|
+
* One column across every rendered row, in row order — again a set, again
|
|
116
|
+
* prefix-addressed. Reading a whole column through ONE locator matters for
|
|
117
|
+
* more than convenience: a per-row loop awaits between reads and can meet a
|
|
118
|
+
* detached node mid-re-render, which turns a polling wait into a thrown
|
|
119
|
+
* error instead of another attempt.
|
|
120
|
+
*/
|
|
121
|
+
column: (fieldName: string) => L;
|
|
122
|
+
/** The column header's menu trigger (sort / pin / hide / move). */
|
|
123
|
+
columnMenu: (fieldName: string) => L;
|
|
124
|
+
/** The sort item inside an OPEN column header menu. */
|
|
125
|
+
columnSort: (fieldName: string, direction: 'asc' | 'desc') => L;
|
|
126
|
+
/**
|
|
127
|
+
* The reorder item inside an OPEN column header menu — moves the column one
|
|
128
|
+
* position left or right.
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ What it moves is **column layout**, which is a different persistence
|
|
131
|
+
* mechanism from everything else on this object: order, width and pinning
|
|
132
|
+
* live in raw `localStorage` under `buro:table-layout:<resource>:{order,
|
|
133
|
+
* sizing,pinning}` and are carried by neither the URL nor a saved view. See
|
|
134
|
+
* {@link ListView.savedViews}.
|
|
135
|
+
*/
|
|
136
|
+
columnMove: (fieldName: string, direction: 'left' | 'right') => L;
|
|
137
|
+
/** The Columns menu trigger — show/hide columns, reset widths, restore defaults. */
|
|
138
|
+
columnsToggle: () => L;
|
|
139
|
+
/**
|
|
140
|
+
* One column's row inside the OPEN Columns menu. A column the resource
|
|
141
|
+
* declares in `views.list.columns` is REQUIRED unless
|
|
142
|
+
* `views.list.requiredColumns` says otherwise, and renders `disabled` with
|
|
143
|
+
* `data-required="true"` — only `optionalColumns` are actually clickable.
|
|
144
|
+
*/
|
|
145
|
+
columnsOption: (columnId: string) => L;
|
|
146
|
+
/** The rows-per-page select trigger; its text is the active page size. */
|
|
147
|
+
pageSize: () => L;
|
|
148
|
+
search: () => L;
|
|
149
|
+
/** The filter region container — `list-filters:<resource>`. */
|
|
150
|
+
filtersContainer: () => L;
|
|
151
|
+
/** Opens/closes the filter-builder popover. */
|
|
152
|
+
filterButton: () => L;
|
|
153
|
+
/** The popover body once open. */
|
|
154
|
+
filterPanel: () => L;
|
|
155
|
+
/** "+ Filter" — appends a new predicate row to the group at `groupPath` (root: `[]`). */
|
|
156
|
+
filterAddRow: (groupPath?: FilterRowPath) => L;
|
|
157
|
+
/** Field-select trigger for the row at `path`. */
|
|
158
|
+
filterRowField: (path: FilterRowPath) => L;
|
|
159
|
+
/** One field option inside an open field-select. */
|
|
160
|
+
filterRowFieldOption: (path: FilterRowPath, field: string) => L;
|
|
161
|
+
/** Operator-select trigger for the row at `path`. */
|
|
162
|
+
filterRowOp: (path: FilterRowPath) => L;
|
|
163
|
+
/** One operator option inside an open operator-select. */
|
|
164
|
+
filterRowOpOption: (path: FilterRowPath, op: string) => L;
|
|
165
|
+
/** Value control trigger/input for the row at `path` — shape depends on the field kind (text box, enum/relation/boolean popover). */
|
|
166
|
+
filterRowValue: (path: FilterRowPath) => L;
|
|
167
|
+
/** Removes the row at `path` from the draft (inside the panel, before Apply). */
|
|
168
|
+
filterRowRemove: (path: FilterRowPath) => L;
|
|
169
|
+
/** Commits the draft — applies it and closes the panel. */
|
|
170
|
+
filterApply: () => L;
|
|
171
|
+
/** Clears the draft back to empty (still requires Apply to take effect). */
|
|
172
|
+
filterReset: () => L;
|
|
173
|
+
/** One applied-filter badge outside the panel, by its top-level index. */
|
|
174
|
+
filterBadge: (index: number) => L;
|
|
175
|
+
/** A badge's × — removes that row immediately, no Apply needed. */
|
|
176
|
+
filterBadgeRemove: (index: number) => L;
|
|
177
|
+
/** "Clear all" — empties both the pinned slot and the builder group. */
|
|
178
|
+
filterClearAll: () => L;
|
|
179
|
+
/** An always-visible pinned control (`views.list.filters.pinned`) for `field`, rendered outside the panel. */
|
|
180
|
+
filterPinned: (field: string) => L;
|
|
181
|
+
/**
|
|
182
|
+
* The floating bulk bar, which appears as soon as ANY row is selected —
|
|
183
|
+
* even for a resource that declares no bulk action, because it also carries
|
|
184
|
+
* the built-in Export entry and the selection count.
|
|
185
|
+
*/
|
|
186
|
+
bulkBar: () => L;
|
|
187
|
+
/** One declared bulk action's button inside the bar (`actions.bulk`). */
|
|
188
|
+
bulkAction: (actionName: string) => L;
|
|
189
|
+
paginationPrev: () => L;
|
|
190
|
+
paginationNext: () => L;
|
|
191
|
+
/**
|
|
192
|
+
* The load-more button a CURSOR-paginated list renders instead of the
|
|
193
|
+
* numbered pager (`views.list.pagination: 'cursor'` → `<CursorLoadMore>`).
|
|
194
|
+
*
|
|
195
|
+
* ⚠️ NOT resource-scoped, because the control itself is not: `CursorLoadMore`
|
|
196
|
+
* carries a bare `data-testid="cursor-load-more"` with no resource segment,
|
|
197
|
+
* unlike every other id above. A page rendering two cursor lists at once
|
|
198
|
+
* would need `.first()`/`.nth()` to tell them apart. Recorded rather than
|
|
199
|
+
* papered over — inventing a scoped id here would only make this object
|
|
200
|
+
* disagree with the component.
|
|
201
|
+
*
|
|
202
|
+
* ⚠️ `<CursorLoadMore>` renders NOTHING when there is no next page, so the
|
|
203
|
+
* end of a cursor list is "this locator resolves to no element", not a
|
|
204
|
+
* disabled button. That is what makes it usable as an end-of-list assertion.
|
|
205
|
+
*
|
|
206
|
+
* Like `columnMenu`/`columnSort`, this id has no `useTestIds` factory behind
|
|
207
|
+
* it, so `page-object-vocabulary` tests have nothing to check it against
|
|
208
|
+
* (the pre-existing F6 gap — see the saved-views block below for what that
|
|
209
|
+
* costs, measured).
|
|
210
|
+
*/
|
|
211
|
+
loadMore: () => L;
|
|
212
|
+
empty: () => L;
|
|
213
|
+
toolbar: () => L;
|
|
214
|
+
table: () => L;
|
|
215
|
+
/** The Views dropdown trigger in the list toolbar. */
|
|
216
|
+
savedViews: () => L;
|
|
217
|
+
/** The dropdown body once open. */
|
|
218
|
+
savedViewsMenu: () => L;
|
|
219
|
+
/**
|
|
220
|
+
* EVERY view entry in the open dropdown (adopter presets and user-saved
|
|
221
|
+
* alike), as one locator — the set, not a named member of it, because a
|
|
222
|
+
* user view's id is a uuid minted at save time and no test can know it in
|
|
223
|
+
* advance. Prefix-addressed for the same reason {@link ListView.rows} is.
|
|
224
|
+
*/
|
|
225
|
+
savedViewEntries: () => L;
|
|
226
|
+
/** One view entry by id — for an adopter preset declared in `views.list.savedViews`, whose id IS known. */
|
|
227
|
+
savedView: (viewId: string) => L;
|
|
228
|
+
/** "Save current view…" — opens the save dialog. */
|
|
229
|
+
savedViewSave: () => L;
|
|
230
|
+
/** The label input inside the open save dialog. */
|
|
231
|
+
savedViewSaveInput: () => L;
|
|
232
|
+
/** The save dialog's confirm button; disabled while the label is empty. */
|
|
233
|
+
savedViewSaveConfirm: () => L;
|
|
234
|
+
/**
|
|
235
|
+
* "Manage views…" — opens the manage dialog. Rendered only when at least
|
|
236
|
+
* one entry exists, so `toHaveCount(0)` is a usable "this browser has no
|
|
237
|
+
* saved views" assertion.
|
|
238
|
+
*/
|
|
239
|
+
savedViewManage: () => L;
|
|
240
|
+
/** One entry's Delete button inside the open manage dialog. */
|
|
241
|
+
savedViewManageDelete: (viewId: string) => L;
|
|
91
242
|
}
|
|
92
|
-
interface RowObject {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
243
|
+
interface RowObject<L extends ClickableLocator> {
|
|
244
|
+
/**
|
|
245
|
+
* The row element itself. Without it a caller cannot say "this row is on
|
|
246
|
+
* screen" or "this row is gone" — the two assertions a list test needs
|
|
247
|
+
* most, and the ones `cell()`/`action()` cannot stand in for (a row can be
|
|
248
|
+
* present while a given cell or action is not).
|
|
249
|
+
*/
|
|
250
|
+
root: () => L;
|
|
251
|
+
cell: (fieldName: string) => L;
|
|
252
|
+
action: (actionName: string) => L;
|
|
253
|
+
checkbox: () => L;
|
|
254
|
+
open: () => Promise<void>;
|
|
97
255
|
}
|
|
98
256
|
interface ListViewOptions {
|
|
99
|
-
|
|
257
|
+
/**
|
|
258
|
+
* What {@link ListView.goto} navigates to. Defaults to `/<resource>`, which
|
|
259
|
+
* assumes the route is named after the resource — often false (a `order`
|
|
260
|
+
* resource routed at `/orders`), and it carries no query string, so an app
|
|
261
|
+
* whose test isolation travels on the URL has to pass its whole launch URL
|
|
262
|
+
* here.
|
|
263
|
+
*/
|
|
264
|
+
basePath?: string;
|
|
100
265
|
}
|
|
101
|
-
declare function createListView(page: PlaywrightPage
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
266
|
+
declare function createListView<L extends ClickableLocator>(page: PlaywrightPage<L>, resource: string, options?: ListViewOptions): ListView<L>;
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/picker-object.d.ts
|
|
269
|
+
/**
|
|
270
|
+
* The relation picker's addresses, as a test drives them.
|
|
271
|
+
*
|
|
272
|
+
* ⚠️ **Every id here was wrong until SP9 task 5, and nothing could see it.**
|
|
273
|
+
* This object used to ask for `picker-dialog:` / `picker-popover:` /
|
|
274
|
+
* `picker-search:` / `picker-option:` / `picker-clear:` /
|
|
275
|
+
* `picker-quickcreate:`, and `@burojs/react`'s `useTestIds()` composed exactly
|
|
276
|
+
* the same six — while `@burojs/ui-shadcn`'s `<RelationPicker>` has always
|
|
277
|
+
* emitted `relation-picker:<target>:…`. Neither vocabulary had a single
|
|
278
|
+
* producer. The cross-check that exists to prevent this
|
|
279
|
+
* (`examples/marketplace-ops/tests/page-object-vocabulary.test.tsx`) was green
|
|
280
|
+
* throughout, because it compared these two copies with each other and neither
|
|
281
|
+
* of them with the component. The producer side is pinned now, in
|
|
282
|
+
* `packages/ui-shadcn/tests/relation-picker-testids.test.tsx`.
|
|
283
|
+
*
|
|
284
|
+
* `quickCreate` is not re-spelled but REMOVED: quick-create inside the picker
|
|
285
|
+
* is deferred to the picker's v2 and there is no element to address. `popover`
|
|
286
|
+
* is likewise gone — the popover content carries no id; {@link PickerObject.list}
|
|
287
|
+
* is the element inside it that does.
|
|
288
|
+
*
|
|
289
|
+
* **`trigger()` is the standalone spelling.** Inside a `<ResourceForm>` the
|
|
290
|
+
* form overrides the trigger's testid with its own
|
|
291
|
+
* `form-field-input:<resource>:<view>:<field>`, so a form field's picker is
|
|
292
|
+
* opened through `resource(r).create().field(f).input()` and only its popover
|
|
293
|
+
* children are addressed through this object.
|
|
294
|
+
*/
|
|
295
|
+
interface PickerObject<L> {
|
|
296
|
+
/** The standalone trigger button. In a form, use the form field's `input()`. */
|
|
297
|
+
trigger: () => L;
|
|
298
|
+
/** The popover's search box. */
|
|
299
|
+
search: () => L;
|
|
300
|
+
/** The popover's result list. */
|
|
301
|
+
list: () => L;
|
|
302
|
+
/** One result row, by the target record's id. */
|
|
303
|
+
option: (value: string) => L;
|
|
304
|
+
/** The "No results." row. */
|
|
305
|
+
empty: () => L;
|
|
306
|
+
/** The Clear link — present only when the picker is clearable and set. */
|
|
307
|
+
clear: () => L;
|
|
308
|
+
/** The "Advanced search" link that escalates to {@link PickerObject.dialog}. */
|
|
309
|
+
advanced: () => L;
|
|
310
|
+
/** The "Open list" link to the target's own list page. */
|
|
311
|
+
viewAll: () => L;
|
|
312
|
+
/** The escalation modal. */
|
|
313
|
+
dialog: () => L;
|
|
314
|
+
/** The escalation modal's own search box. */
|
|
315
|
+
dialogSearch: () => L;
|
|
316
|
+
/** The disabled trigger shown when the target resource is unregistered. */
|
|
317
|
+
unregistered: () => L;
|
|
110
318
|
}
|
|
111
|
-
declare function createPickerObject(page: PlaywrightPage
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
319
|
+
declare function createPickerObject<L>(page: PlaywrightPage<L>, targetResource: string): PickerObject<L>;
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/show-view.d.ts
|
|
322
|
+
interface ShowView<L extends ClickableLocator> extends FormView<L> {
|
|
323
|
+
/**
|
|
324
|
+
* The show page root — `useTestIds`' `pageShow(resource, recordId)`.
|
|
325
|
+
*
|
|
326
|
+
* The ONE show accessor that uses the record id. Every other locator on this
|
|
327
|
+
* view comes from `createFormView`, whose testids carry no record id at all
|
|
328
|
+
* (see {@link import('./resource-object.js').ResourceObject.show}), so this
|
|
329
|
+
* is what lets a test assert it is looking at the show page OF A GIVEN
|
|
330
|
+
* RECORD rather than at whatever detail page happens to be mounted.
|
|
331
|
+
*/
|
|
332
|
+
root: () => L;
|
|
123
333
|
}
|
|
124
|
-
declare function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
334
|
+
declare function createShowView<L extends ClickableLocator>(page: PlaywrightPage<L>, resource: string, recordId: string): ShowView<L>;
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/resource-object.d.ts
|
|
337
|
+
interface ResourceObject<L extends ClickableLocator> {
|
|
338
|
+
name: string;
|
|
339
|
+
list: (options?: ListViewOptions) => ListView<L>;
|
|
340
|
+
/**
|
|
341
|
+
* ⚠️ `recordId` is accepted and IGNORED: the form testids
|
|
342
|
+
* (`form-field:<resource>:edit:<field>`) carry no record id, so an edit view
|
|
343
|
+
* addressed for record A and one addressed for record B are the same
|
|
344
|
+
* locators. Only one edit form is ever mounted at a time, so this is
|
|
345
|
+
* harmless in practice — but the parameter reads like a scope and is not
|
|
346
|
+
* one.
|
|
347
|
+
*/
|
|
348
|
+
edit: (recordId: string) => FormView<L>;
|
|
349
|
+
/**
|
|
350
|
+
* ⚠️ `recordId` reaches {@link ShowView.root} and NOTHING ELSE. The show
|
|
351
|
+
* view's field/section/action locators all come from `createFormView`, whose
|
|
352
|
+
* testids carry no record id, so they are the same locators for record A and
|
|
353
|
+
* record B. `root()` is the one accessor that is genuinely scoped to the
|
|
354
|
+
* record — assert on it first if a test needs to know WHICH record's page is
|
|
355
|
+
* mounted.
|
|
356
|
+
*/
|
|
357
|
+
show: (recordId: string) => ShowView<L>;
|
|
358
|
+
create: () => FormView<L>;
|
|
359
|
+
picker: () => PickerObject<L>;
|
|
132
360
|
}
|
|
133
|
-
declare function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
361
|
+
declare function createResourceObject<L extends ClickableLocator>(page: PlaywrightPage<L>, name: string): ResourceObject<L>;
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/buro-page.d.ts
|
|
364
|
+
interface BuroPage<L extends ClickableLocator> {
|
|
365
|
+
resource: (name: string) => ResourceObject<L>;
|
|
366
|
+
/**
|
|
367
|
+
* The sidebar entry for a registered resource — `<NavMain>`'s
|
|
368
|
+
* `tid.navMenuItem(resource)`. The whole navigation half of a suite used to
|
|
369
|
+
* spell this string by hand even though the factory existed (SP3 stage C
|
|
370
|
+
* final review, Important 2).
|
|
371
|
+
*/
|
|
372
|
+
navMenuItem: (resource: string) => L;
|
|
373
|
+
/** The sidebar entry for an adopter-declared custom page, keyed by page key
|
|
374
|
+
* rather than resource name (`tid.sidebarCustomPage`). */
|
|
375
|
+
sidebarCustomPage: (pageKey: string) => L;
|
|
376
|
+
contextPicker: (sliceName: string) => L;
|
|
377
|
+
/** One option inside an open context picker — the half that actually
|
|
378
|
+
* performs a switch. Without it a caller can open the picker and not choose. */
|
|
379
|
+
contextPickerOption: (sliceName: string, value: string) => L;
|
|
380
|
+
/**
|
|
381
|
+
* The header language switcher (`<LanguageSwitch>`) and one locale button
|
|
382
|
+
* inside it. The trigger carries `data-language-state="<code>"`, which is
|
|
383
|
+
* how a test reads the ACTIVE locale without opening anything.
|
|
384
|
+
*/
|
|
385
|
+
languageSwitch: () => L;
|
|
386
|
+
languageSwitchOption: (localeCode: string) => L;
|
|
387
|
+
/** `<LoginForm>`'s three controls, in the order a sign-in uses them. */
|
|
388
|
+
login: () => LoginFormObject<L>;
|
|
389
|
+
/**
|
|
390
|
+
* `<TaskRunPanel>`'s three addressable parts, keyed by the RUN's name — a
|
|
391
|
+
* task run has no resource to key on (a long server-side operation has no
|
|
392
|
+
* record, a bulk run spans a selection).
|
|
393
|
+
*
|
|
394
|
+
* `root()` carries `data-state`, which is how a spec reads the outcome
|
|
395
|
+
* without matching translated text.
|
|
396
|
+
*/
|
|
397
|
+
taskRun: (name: string) => TaskRunObject<L>;
|
|
398
|
+
notification: (type: 'success' | 'error' | 'progress' | 'info') => L;
|
|
399
|
+
/**
|
|
400
|
+
* ⚠️ Addresses the RECORD-LESS spelling only. `useTestIds`'
|
|
401
|
+
* `actionConfirmDialog(resource, scope, action, recordId?)` appends the
|
|
402
|
+
* record id when one is supplied, so a row-scoped confirmation renders a
|
|
403
|
+
* testid this locator does not match.
|
|
404
|
+
*/
|
|
405
|
+
actionConfirm: (resource: string, scope: string, actionName: string) => L;
|
|
406
|
+
page: PlaywrightPage<L>;
|
|
139
407
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
readonly kind?: QueryKind | MutationKind;
|
|
147
|
-
readonly descriptor?: ErasedQueryDescriptor | ErasedMutationDescriptor;
|
|
148
|
-
readonly descriptors?: readonly ErasedQueryDescriptor[];
|
|
149
|
-
readonly signal?: AbortSignal;
|
|
408
|
+
interface TaskRunObject<L> {
|
|
409
|
+
root: () => L;
|
|
410
|
+
/** The bar; its `aria-valuenow`/`aria-valuemax` are the counts. */
|
|
411
|
+
progress: () => L;
|
|
412
|
+
/** One named unit that did not succeed — a refused row, or the step a run stopped at. */
|
|
413
|
+
failure: (subject: string) => L;
|
|
150
414
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
readonly batch?: readonly BatchedQueryOperation[];
|
|
156
|
-
/** Independent batch groups prove that grouping is explicit, never merged by the helper. */
|
|
157
|
-
readonly batchGroups?: readonly (readonly BatchedQueryOperation[])[];
|
|
158
|
-
/** List specimens for both pagination protocols, including expected totals/pageInfo. */
|
|
159
|
-
readonly pagination?: {
|
|
160
|
-
readonly offset?: ErasedQueryDescriptor;
|
|
161
|
-
readonly cursor?: ErasedQueryDescriptor;
|
|
162
|
-
};
|
|
163
|
-
readonly live?: ErasedQueryDescriptor;
|
|
164
|
-
/** Error specimens must make the selected handler reject with a DataError. */
|
|
165
|
-
readonly errors?: readonly ProviderContractErrorSpecimen[];
|
|
166
|
-
/** Hand-authored mapped results; assertions never derive expectations from an adapter. */
|
|
167
|
-
readonly expected?: {
|
|
168
|
-
readonly queries?: Partial<Record<QueryKind, unknown>>;
|
|
169
|
-
readonly mutations?: Partial<Record<MutationKind, unknown>>;
|
|
170
|
-
readonly batch?: unknown;
|
|
171
|
-
readonly pagination?: {
|
|
172
|
-
readonly offset?: unknown;
|
|
173
|
-
readonly cursor?: unknown;
|
|
174
|
-
};
|
|
175
|
-
};
|
|
415
|
+
interface LoginFormObject<L> {
|
|
416
|
+
email: () => L;
|
|
417
|
+
password: () => L;
|
|
418
|
+
submit: () => L;
|
|
176
419
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
readonly descriptor: ErasedMutationDescriptor;
|
|
185
|
-
};
|
|
186
|
-
interface ProviderContractFixture {
|
|
187
|
-
create(): DataProvider | Promise<DataProvider>;
|
|
188
|
-
descriptors: ProviderContractDescriptors;
|
|
189
|
-
observeCalls(): readonly ProviderObservedCall[];
|
|
420
|
+
declare function createBuroPage<L extends ClickableLocator>(page: PlaywrightPage<L>): BuroPage<L>;
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/confirm-dialog.d.ts
|
|
423
|
+
interface ConfirmDialogObject<L> {
|
|
424
|
+
dialog: () => L;
|
|
425
|
+
yes: () => L;
|
|
426
|
+
no: () => L;
|
|
190
427
|
}
|
|
428
|
+
declare function createConfirmDialog<L>(page: PlaywrightPage<L>, resource: string, scope: string, actionName: string): ConfirmDialogObject<L>;
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/index.d.ts
|
|
191
431
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
432
|
+
* `@burojs/test-helpers` — the Playwright entry point.
|
|
433
|
+
*
|
|
434
|
+
* Everything exported here is loadable inside a `@playwright/test` process:
|
|
435
|
+
* page objects over the testid convention `useTestIds()` (`@burojs/react`)
|
|
436
|
+
* emits, and nothing else. The vitest-based provider contract moved to
|
|
437
|
+
* `@burojs/test-helpers/provider-contract` — see that module for the measured
|
|
438
|
+
* reason it can no longer share an entry point with these.
|
|
195
439
|
*/
|
|
196
|
-
declare function dataProviderContract(name: string, fixture: ProviderContractFixture): void;
|
|
197
|
-
|
|
198
440
|
declare const PACKAGE_NAME: "@burojs/test-helpers";
|
|
199
441
|
declare const PACKAGE_VERSION: "0.0.0";
|
|
200
|
-
|
|
201
|
-
export { type BuroPage, type ConfirmDialogObject, type FormFieldObject, type FormView, type ListView, type ListViewOptions, PACKAGE_NAME, PACKAGE_VERSION, type PickerObject, type PlaywrightLocator, type PlaywrightPage, type
|
|
442
|
+
//#endregion
|
|
443
|
+
export { type BuroPage, type ClickableLocator, type ConfirmDialogObject, type FilterRowPath, type FormFieldObject, type FormView, type ListView, type ListViewOptions, type LoginFormObject, PACKAGE_NAME, PACKAGE_VERSION, type PickerObject, type PlaywrightLocator, type PlaywrightPage, type ResourceObject, type RowObject, type ShowView, type TaskRunObject, createBuroPage, createConfirmDialog, createFormView, createListView, createPickerObject, createResourceObject, createShowView };
|
|
444
|
+
//# sourceMappingURL=index.d.cts.map
|