@burojs/test-helpers 0.2.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/index.cjs +322 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +201 -0
- package/dist/index.d.ts +201 -0
- package/dist/index.js +311 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maksim Kuznetsov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @burojs/test-helpers
|
|
2
|
+
|
|
3
|
+
Typed [Playwright](https://playwright.dev) page objects for [buro](https://github.com/buro/buro) apps, built over the framework's stable `data-testid` convention.
|
|
4
|
+
|
|
5
|
+
Because buro generates its own test ids (`list-row:<resource>:<id>`, `picker-quickcreate:<resource>`, …), your e2e tests can address a screen you never hand-wrote.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add -D @burojs/test-helpers @playwright/test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { test, expect } from '@playwright/test';
|
|
17
|
+
import { createBuroPage } from '@burojs/test-helpers';
|
|
18
|
+
|
|
19
|
+
test('a document can be filtered and opened', async ({ page }) => {
|
|
20
|
+
const app = createBuroPage(page);
|
|
21
|
+
const documents = app.resource('document_document');
|
|
22
|
+
|
|
23
|
+
const list = documents.list();
|
|
24
|
+
await list.goto();
|
|
25
|
+
await list.search().fill('invoice');
|
|
26
|
+
await list.paginationNext().click();
|
|
27
|
+
|
|
28
|
+
await expect(documents.show('42').field('title').input()).toBeVisible();
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every locator above is derived from the resource name, so none of these test ids are hand-written in your app.
|
|
33
|
+
|
|
34
|
+
## Main exports
|
|
35
|
+
|
|
36
|
+
- **`createBuroPage` / `BuroPage`** — the entry point; wraps a Playwright `Page`. Exposes `resource(name)`, `contextPicker(slice)`, `notification(type)`, `actionConfirm(resource, scope, action)` and the raw `page`.
|
|
37
|
+
- **`createResourceObject` / `ResourceObject`** — one resource's four surfaces: `list()`, `create()`, `edit(id)`, `show(id)`, plus `picker()`.
|
|
38
|
+
- **`createFormView` / `FormView` / `FormFieldObject`** — form fields across create/edit/show: `field(name).input()`, `.error()`, `.help()`, and `section(key)`.
|
|
39
|
+
- **`createShowView` / `ShowView`** — the detail surface (a `FormView` in `show` mode).
|
|
40
|
+
- **`createPickerObject` / `PickerObject`** — relation pickers, including quick-create.
|
|
41
|
+
- **`createConfirmDialog` / `ConfirmDialogObject`** — confirmation dialogs raised by declared actions.
|
|
42
|
+
|
|
43
|
+
The list surface (`documents.list()`) covers `goto()`, `row(id)`, `search()`, `paginationPrev()` / `paginationNext()`, and the filter builder: `filterButton()`, `filterPanel()`, `filterAddRow(groupPath?)`, `filterRowField(path)` / `filterRowOp(path)` / `filterRowValue(path)`, `filterApply()`, `filterReset()`, `filterBadge(index)` / `filterBadgeRemove(index)`, `filterClearAll()` and `filterPinned(field)`.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@burojs/core');
|
|
4
|
+
var vitest = require('vitest');
|
|
5
|
+
|
|
6
|
+
// src/form-view.ts
|
|
7
|
+
function createFormView(page, resource, view) {
|
|
8
|
+
return {
|
|
9
|
+
field: (fieldName) => {
|
|
10
|
+
page.getByTestId(`form-field:${resource}:${view}:${fieldName}`);
|
|
11
|
+
return {
|
|
12
|
+
input: () => page.getByTestId(`form-field-input:${resource}:${view}:${fieldName}`),
|
|
13
|
+
error: () => page.getByTestId(`form-field-error:${resource}:${view}:${fieldName}`),
|
|
14
|
+
help: () => page.getByTestId(`form-field-help:${resource}:${view}:${fieldName}`)
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
section: (sectionKey) => page.getByTestId(`form-section:${resource}:${view}:${sectionKey}`),
|
|
18
|
+
submit: () => page.getByTestId(`form-submit:${resource}:${view}`),
|
|
19
|
+
cancel: () => page.getByTestId(`form-cancel:${resource}:${view}`),
|
|
20
|
+
action: (actionName) => page.getByTestId(`form-action:${resource}:${view}:${actionName}`)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/list-view.ts
|
|
25
|
+
function pathStr(path) {
|
|
26
|
+
return path.join("-");
|
|
27
|
+
}
|
|
28
|
+
function createListView(page, resource, options = {}) {
|
|
29
|
+
const path = options.basePath ?? `/${resource}`;
|
|
30
|
+
const filterBase = `list-filter:${resource}`;
|
|
31
|
+
const rowBase = (rowPath) => `${filterBase}:row:${pathStr(rowPath)}`;
|
|
32
|
+
const groupBase = (groupPath) => `${filterBase}:group:${pathStr(groupPath)}`;
|
|
33
|
+
return {
|
|
34
|
+
goto: async () => {
|
|
35
|
+
await page.goto(path);
|
|
36
|
+
},
|
|
37
|
+
row: (recordId) => createRowObject(page, resource, recordId),
|
|
38
|
+
search: () => page.getByTestId(`list-search:${resource}`),
|
|
39
|
+
filtersContainer: () => page.getByTestId(`list-filters:${resource}`),
|
|
40
|
+
filterButton: () => page.getByTestId(`${filterBase}:button`),
|
|
41
|
+
filterPanel: () => page.getByTestId(`${filterBase}:panel`),
|
|
42
|
+
filterAddRow: (groupPath = []) => page.getByTestId(`${groupBase(groupPath)}:add-row`),
|
|
43
|
+
filterRowField: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:field`),
|
|
44
|
+
filterRowFieldOption: (rowPath, field) => page.getByTestId(`${rowBase(rowPath)}:field:option:${field}`),
|
|
45
|
+
filterRowOp: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:op`),
|
|
46
|
+
filterRowOpOption: (rowPath, op) => page.getByTestId(`${rowBase(rowPath)}:op:option:${op}`),
|
|
47
|
+
filterRowValue: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:value`),
|
|
48
|
+
filterRowRemove: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:remove`),
|
|
49
|
+
filterApply: () => page.getByTestId(`${filterBase}:apply`),
|
|
50
|
+
filterReset: () => page.getByTestId(`${filterBase}:reset`),
|
|
51
|
+
filterBadge: (index) => page.getByTestId(`${filterBase}:badge:${index}`),
|
|
52
|
+
filterBadgeRemove: (index) => page.getByTestId(`${filterBase}:badge:${index}:remove`),
|
|
53
|
+
filterClearAll: () => page.getByTestId(`${filterBase}:clear-all`),
|
|
54
|
+
filterPinned: (field) => page.getByTestId(`${filterBase}:pinned:${field}`),
|
|
55
|
+
paginationPrev: () => page.getByTestId(`list-pagination-prev:${resource}`),
|
|
56
|
+
paginationNext: () => page.getByTestId(`list-pagination-next:${resource}`),
|
|
57
|
+
empty: () => page.getByTestId(`list-empty:${resource}`),
|
|
58
|
+
toolbar: () => page.getByTestId(`list-toolbar:${resource}`),
|
|
59
|
+
table: () => page.getByTestId(`list-table:${resource}`)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createRowObject(page, resource, recordId) {
|
|
63
|
+
const rowId = `list-row:${resource}:${recordId}`;
|
|
64
|
+
return {
|
|
65
|
+
cell: (fieldName) => page.getByTestId(`list-cell:${resource}:${recordId}:${fieldName}`),
|
|
66
|
+
action: (actionName) => page.getByTestId(`list-row-action:${resource}:${recordId}:${actionName}`),
|
|
67
|
+
checkbox: () => page.getByTestId(`list-row-checkbox:${resource}:${recordId}`),
|
|
68
|
+
open: async () => {
|
|
69
|
+
await page.getByTestId(rowId).click();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/picker-object.ts
|
|
75
|
+
function createPickerObject(page, targetResource) {
|
|
76
|
+
return {
|
|
77
|
+
dialog: () => page.getByTestId(`picker-dialog:${targetResource}`),
|
|
78
|
+
popover: () => page.getByTestId(`picker-popover:${targetResource}`),
|
|
79
|
+
option: (value) => page.getByTestId(`picker-option:${targetResource}:${value}`),
|
|
80
|
+
search: () => page.getByTestId(`picker-search:${targetResource}`),
|
|
81
|
+
clear: () => page.getByTestId(`picker-clear:${targetResource}`),
|
|
82
|
+
quickCreate: () => page.getByTestId(`picker-quickcreate:${targetResource}`)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/show-view.ts
|
|
87
|
+
function createShowView(page, resource) {
|
|
88
|
+
return createFormView(page, resource, "show");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/resource-object.ts
|
|
92
|
+
function createResourceObject(page, name) {
|
|
93
|
+
return {
|
|
94
|
+
name,
|
|
95
|
+
list: (options) => createListView(page, name, options),
|
|
96
|
+
edit: (_recordId) => createFormView(page, name, "edit"),
|
|
97
|
+
show: (_recordId) => createShowView(page, name),
|
|
98
|
+
create: () => createFormView(page, name, "create"),
|
|
99
|
+
picker: () => createPickerObject(page, name)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/buro-page.ts
|
|
104
|
+
function createBuroPage(page) {
|
|
105
|
+
return {
|
|
106
|
+
resource: (name) => createResourceObject(page, name),
|
|
107
|
+
contextPicker: (sliceName) => page.getByTestId(`context-picker:${sliceName}`),
|
|
108
|
+
notification: (type) => page.getByTestId(`notification:${type}`),
|
|
109
|
+
actionConfirm: (resource, scope, actionName) => page.getByTestId(`action-confirm-dialog:${resource}:${scope}:${actionName}`),
|
|
110
|
+
page
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/confirm-dialog.ts
|
|
115
|
+
function createConfirmDialog(page, resource, scope, actionName) {
|
|
116
|
+
return {
|
|
117
|
+
dialog: () => page.getByTestId(`action-confirm-dialog:${resource}:${scope}:${actionName}`),
|
|
118
|
+
yes: () => page.getByTestId(`action-confirm-yes:${resource}:${scope}:${actionName}`),
|
|
119
|
+
no: () => page.getByTestId(`action-confirm-no:${resource}:${scope}:${actionName}`)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var SESSION = Object.freeze({
|
|
123
|
+
sessionKey: "contract-session",
|
|
124
|
+
scopeKey: "contract-scope",
|
|
125
|
+
generation: 1
|
|
126
|
+
});
|
|
127
|
+
var REQUEST_CONTEXT = Object.freeze({ contract: "provider" });
|
|
128
|
+
function context(signal) {
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
signal,
|
|
131
|
+
requestContext: REQUEST_CONTEXT,
|
|
132
|
+
session: SESSION,
|
|
133
|
+
providerServiceKey: "contract-provider"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function queryShape(kind, value) {
|
|
137
|
+
if (kind === "custom") return;
|
|
138
|
+
vitest.expect(value).toBeTypeOf("object");
|
|
139
|
+
if (kind === "list" || kind === "many")
|
|
140
|
+
vitest.expect(value.data).toBeInstanceOf(Array);
|
|
141
|
+
if (kind === "one") vitest.expect(value).toHaveProperty("data");
|
|
142
|
+
if (kind === "count")
|
|
143
|
+
vitest.expect([null, "number"]).toContain(typeof value.total);
|
|
144
|
+
}
|
|
145
|
+
function observed(fixture, expected, signal) {
|
|
146
|
+
vitest.expect(fixture.observeCalls()).toContainEqual(vitest.expect.objectContaining({ ...expected, signal }));
|
|
147
|
+
}
|
|
148
|
+
async function assertBatch(provider, fixture, operations, expected) {
|
|
149
|
+
vitest.expect(provider.batch?.queries).toBeTypeOf("function");
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const result = await provider.batch.queries(operations, context(controller.signal));
|
|
152
|
+
core.validateBatchResult(operations, result);
|
|
153
|
+
if (expected !== void 0) vitest.expect(result).toEqual(expected);
|
|
154
|
+
for (const entry of result.results) if (entry.status === "error") dataError(entry.error);
|
|
155
|
+
observed(
|
|
156
|
+
fixture,
|
|
157
|
+
{ lifecycle: "batch", descriptors: operations.map((operation) => operation.descriptor) },
|
|
158
|
+
controller.signal
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
function dataError(value) {
|
|
162
|
+
vitest.expect(core.isDataError(value)).toBe(true);
|
|
163
|
+
}
|
|
164
|
+
function livePlan(value) {
|
|
165
|
+
vitest.expect(value, "descriptors.live was supplied, so live.open must return a plan").not.toBeNull();
|
|
166
|
+
vitest.expect(value).toBeTypeOf("object");
|
|
167
|
+
vitest.expect(value?.subscribe).toBeTypeOf("function");
|
|
168
|
+
}
|
|
169
|
+
function liveDescriptorRequired(hasLiveSlot, descriptor) {
|
|
170
|
+
if (!hasLiveSlot) return;
|
|
171
|
+
vitest.expect(
|
|
172
|
+
descriptor,
|
|
173
|
+
"the provider exposes a `live` slot, so the fixture must supply `descriptors.live` \u2014 omitting it silently skips the whole live cell"
|
|
174
|
+
).toBeDefined();
|
|
175
|
+
}
|
|
176
|
+
function dataProviderContract(name, fixture) {
|
|
177
|
+
vitest.describe(`${name} data provider contract`, () => {
|
|
178
|
+
for (const kind of ["list", "one", "many", "count", "custom"]) {
|
|
179
|
+
vitest.it(`preserves the ${kind} query descriptor and AbortSignal`, async () => {
|
|
180
|
+
const provider = await fixture.create();
|
|
181
|
+
const descriptor = fixture.descriptors.queries?.[kind];
|
|
182
|
+
const handler = provider.queries?.[kind];
|
|
183
|
+
if (descriptor === void 0 || handler === void 0) return;
|
|
184
|
+
const controller = new AbortController();
|
|
185
|
+
const result = await handler(descriptor, context(controller.signal));
|
|
186
|
+
queryShape(kind, result);
|
|
187
|
+
const expected = fixture.descriptors.expected?.queries?.[kind];
|
|
188
|
+
if (expected !== void 0) vitest.expect(result).toEqual(expected);
|
|
189
|
+
observed(fixture, { lifecycle: "query", kind, descriptor }, controller.signal);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
for (const kind of [
|
|
193
|
+
"createOne",
|
|
194
|
+
"createMany",
|
|
195
|
+
"updateOne",
|
|
196
|
+
"updateMany",
|
|
197
|
+
"deleteOne",
|
|
198
|
+
"deleteMany",
|
|
199
|
+
"custom"
|
|
200
|
+
]) {
|
|
201
|
+
vitest.it(`validates ${kind} mutation output and preserves its request`, async () => {
|
|
202
|
+
const provider = await fixture.create();
|
|
203
|
+
const descriptor = fixture.descriptors.mutations?.[kind];
|
|
204
|
+
const handler = provider.mutations?.[kind];
|
|
205
|
+
if (descriptor === void 0 || handler === void 0) return;
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const result = await handler(descriptor, context(controller.signal));
|
|
208
|
+
core.validateMutationResult(descriptor, result);
|
|
209
|
+
const expected = fixture.descriptors.expected?.mutations?.[kind];
|
|
210
|
+
if (expected !== void 0) vitest.expect(result).toEqual(expected);
|
|
211
|
+
observed(fixture, { lifecycle: "mutation", kind, descriptor }, controller.signal);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
for (const specimen of fixture.descriptors.errors ?? []) {
|
|
215
|
+
vitest.it(`returns a structured error for the ${specimen.lifecycle}.${specimen.kind} error specimen`, async () => {
|
|
216
|
+
const provider = await fixture.create();
|
|
217
|
+
const controller = new AbortController();
|
|
218
|
+
const handler = specimen.lifecycle === "query" ? provider.queries?.[specimen.kind] : provider.mutations?.[specimen.kind];
|
|
219
|
+
vitest.expect(handler).toBeTypeOf("function");
|
|
220
|
+
try {
|
|
221
|
+
await handler(specimen.descriptor, context(controller.signal));
|
|
222
|
+
throw new Error("Provider error specimen resolved instead of rejecting.");
|
|
223
|
+
} catch (error) {
|
|
224
|
+
dataError(error);
|
|
225
|
+
}
|
|
226
|
+
observed(
|
|
227
|
+
fixture,
|
|
228
|
+
{ lifecycle: specimen.lifecycle, kind: specimen.kind, descriptor: specimen.descriptor },
|
|
229
|
+
controller.signal
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
vitest.it("reports absent optional capabilities without invoking fallback handlers", async () => {
|
|
234
|
+
const provider = await fixture.create();
|
|
235
|
+
const before = fixture.observeCalls().length;
|
|
236
|
+
for (const kind of ["list", "one", "many", "count", "custom"]) {
|
|
237
|
+
if (provider.queries?.[kind] === void 0) {
|
|
238
|
+
const capability = core.resolveDataCapability(provider, { lifecycle: "query", kind });
|
|
239
|
+
vitest.expect(capability).toMatchObject({ kind: "capability", code: "missing-data-capability" });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const kind of [
|
|
243
|
+
"createOne",
|
|
244
|
+
"createMany",
|
|
245
|
+
"updateOne",
|
|
246
|
+
"updateMany",
|
|
247
|
+
"deleteOne",
|
|
248
|
+
"deleteMany",
|
|
249
|
+
"custom"
|
|
250
|
+
]) {
|
|
251
|
+
if (provider.mutations?.[kind] === void 0) {
|
|
252
|
+
const capability = core.resolveDataCapability(provider, { lifecycle: "mutation", kind });
|
|
253
|
+
vitest.expect(capability).toMatchObject({ kind: "capability", code: "missing-data-capability" });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
vitest.expect(fixture.observeCalls()).toHaveLength(before);
|
|
257
|
+
});
|
|
258
|
+
vitest.it("validates batch IDs, kinds, partial errors, and the shared AbortSignal", async () => {
|
|
259
|
+
const provider = await fixture.create();
|
|
260
|
+
const operations = fixture.descriptors.batch;
|
|
261
|
+
if (provider.batch?.queries === void 0 || operations === void 0) return;
|
|
262
|
+
await assertBatch(provider, fixture, operations, fixture.descriptors.expected?.batch);
|
|
263
|
+
});
|
|
264
|
+
for (const [index, operations] of (fixture.descriptors.batchGroups ?? []).entries()) {
|
|
265
|
+
vitest.it(`keeps batch group ${index + 1} isolated and validates its partial results`, async () => {
|
|
266
|
+
const provider = await fixture.create();
|
|
267
|
+
await assertBatch(provider, fixture, operations, void 0);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
for (const kind of ["offset", "cursor"]) {
|
|
271
|
+
vitest.it(`preserves ${kind} pagination and its mapped total/page shape`, async () => {
|
|
272
|
+
const provider = await fixture.create();
|
|
273
|
+
const descriptor = fixture.descriptors.pagination?.[kind];
|
|
274
|
+
if (descriptor === void 0 || provider.queries?.list === void 0) return;
|
|
275
|
+
const controller = new AbortController();
|
|
276
|
+
const result = await provider.queries.list(descriptor, context(controller.signal));
|
|
277
|
+
queryShape("list", result);
|
|
278
|
+
const expected = fixture.descriptors.expected?.pagination?.[kind];
|
|
279
|
+
if (expected !== void 0) vitest.expect(result).toEqual(expected);
|
|
280
|
+
observed(fixture, { lifecycle: "query", kind: "list", descriptor }, controller.signal);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
vitest.it("opens live plans with the complete descriptor and subscribes with an AbortSignal", async () => {
|
|
284
|
+
const provider = await fixture.create();
|
|
285
|
+
const descriptor = fixture.descriptors.live;
|
|
286
|
+
liveDescriptorRequired(provider.live !== void 0, descriptor);
|
|
287
|
+
if (provider.live === void 0 || descriptor === void 0) return;
|
|
288
|
+
const openController = new AbortController();
|
|
289
|
+
const plan = await provider.live.open(descriptor, context(openController.signal));
|
|
290
|
+
observed(fixture, { lifecycle: "live-open", descriptor }, openController.signal);
|
|
291
|
+
livePlan(plan);
|
|
292
|
+
if (plan === null) return;
|
|
293
|
+
const subscribeController = new AbortController();
|
|
294
|
+
const observer = {
|
|
295
|
+
next: () => void 0,
|
|
296
|
+
status: () => void 0,
|
|
297
|
+
error: () => void 0,
|
|
298
|
+
complete: () => void 0
|
|
299
|
+
};
|
|
300
|
+
const unsubscribe = await plan.subscribe(observer, { signal: subscribeController.signal });
|
|
301
|
+
observed(fixture, { lifecycle: "live-subscribe" }, subscribeController.signal);
|
|
302
|
+
await unsubscribe();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/index.ts
|
|
308
|
+
var PACKAGE_NAME = "@burojs/test-helpers";
|
|
309
|
+
var PACKAGE_VERSION = "0.0.0";
|
|
310
|
+
|
|
311
|
+
exports.PACKAGE_NAME = PACKAGE_NAME;
|
|
312
|
+
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
|
313
|
+
exports.createBuroPage = createBuroPage;
|
|
314
|
+
exports.createConfirmDialog = createConfirmDialog;
|
|
315
|
+
exports.createFormView = createFormView;
|
|
316
|
+
exports.createListView = createListView;
|
|
317
|
+
exports.createPickerObject = createPickerObject;
|
|
318
|
+
exports.createResourceObject = createResourceObject;
|
|
319
|
+
exports.createShowView = createShowView;
|
|
320
|
+
exports.dataProviderContract = dataProviderContract;
|
|
321
|
+
//# sourceMappingURL=index.cjs.map
|
|
322
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/form-view.ts","../src/list-view.ts","../src/picker-object.ts","../src/show-view.ts","../src/resource-object.ts","../src/buro-page.ts","../src/confirm-dialog.ts","../src/data-provider-contract.ts","../src/index.ts"],"names":["expect","validateBatchResult","isDataError","describe","it","validateMutationResult","resolveDataCapability"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { PlaywrightLocator, PlaywrightPage } from './types.js';\n\nexport interface FormFieldObject {\n input: () => PlaywrightLocator;\n error: () => PlaywrightLocator;\n help: () => PlaywrightLocator;\n}\n\nexport interface FormView {\n field: (fieldName: string) => FormFieldObject;\n section: (sectionKey: string) => PlaywrightLocator;\n submit: () => PlaywrightLocator;\n cancel: () => PlaywrightLocator;\n action: (actionName: string) => PlaywrightLocator;\n}\n\nexport function createFormView(\n page: PlaywrightPage,\n resource: string,\n view: 'create' | 'edit' | 'show',\n): FormView {\n return {\n field: (fieldName) => {\n page.getByTestId(`form-field:${resource}:${view}:${fieldName}`);\n return {\n input: () => page.getByTestId(`form-field-input:${resource}:${view}:${fieldName}`),\n error: () => page.getByTestId(`form-field-error:${resource}:${view}:${fieldName}`),\n help: () => page.getByTestId(`form-field-help:${resource}:${view}:${fieldName}`),\n };\n },\n section: (sectionKey) => page.getByTestId(`form-section:${resource}:${view}:${sectionKey}`),\n submit: () => page.getByTestId(`form-submit:${resource}:${view}`),\n cancel: () => page.getByTestId(`form-cancel:${resource}:${view}`),\n action: (actionName) => page.getByTestId(`form-action:${resource}:${view}:${actionName}`),\n };\n}\n","import type { PlaywrightLocator, PlaywrightPage } from './types.js';\n\n/**\n * A row's address inside the filter-builder draft: `[]` is the root group,\n * `[0]` is its first row, `[1, 0]` is the first row of the group nested at\n * top-level index 1. Mirrors `DraftPath` in\n * `packages/ui-shadcn/src/list-kit/filter-builder/draft.ts` — the testid\n * suffix is `path.join('-')` (an empty path renders as the empty string, so\n * the root group's own testid carries a trailing `:`, e.g.\n * `list-filter:<r>:group::add-row`).\n */\nexport type FilterRowPath = readonly number[];\n\nexport interface ListView {\n goto: () => Promise<void>;\n row: (recordId: string) => RowObject;\n search: () => PlaywrightLocator;\n // ── Filter builder (SP2) ─────────────────────────────────────────────\n // The chip toolbar this file used to model (`list-dyn-filter:*`,\n // `list-add-filter:*`) is gone — SP2 replaced it with a popover panel:\n // open the panel, add a row, pick a field/operator/value, Apply. Applied\n // rows then render as removable badges OUTSIDE the panel. See\n // `packages/ui-shadcn/src/list-kit/filter-builder/*` for the components\n // these testids come from.\n /** The filter region container — `list-filters:<resource>`. */\n filtersContainer: () => PlaywrightLocator;\n /** Opens/closes the filter-builder popover. */\n filterButton: () => PlaywrightLocator;\n /** The popover body once open. */\n filterPanel: () => PlaywrightLocator;\n /** \"+ Filter\" — appends a new predicate row to the group at `groupPath` (root: `[]`). */\n filterAddRow: (groupPath?: FilterRowPath) => PlaywrightLocator;\n /** Field-select trigger for the row at `path`. */\n filterRowField: (path: FilterRowPath) => PlaywrightLocator;\n /** One field option inside an open field-select. */\n filterRowFieldOption: (path: FilterRowPath, field: string) => PlaywrightLocator;\n /** Operator-select trigger for the row at `path`. */\n filterRowOp: (path: FilterRowPath) => PlaywrightLocator;\n /** One operator option inside an open operator-select. */\n filterRowOpOption: (path: FilterRowPath, op: string) => PlaywrightLocator;\n /** Value control trigger/input for the row at `path` — shape depends on the field kind (text box, enum/relation/boolean popover). */\n filterRowValue: (path: FilterRowPath) => PlaywrightLocator;\n /** Removes the row at `path` from the draft (inside the panel, before Apply). */\n filterRowRemove: (path: FilterRowPath) => PlaywrightLocator;\n /** Commits the draft — applies it and closes the panel. */\n filterApply: () => PlaywrightLocator;\n /** Clears the draft back to empty (still requires Apply to take effect). */\n filterReset: () => PlaywrightLocator;\n /** One applied-filter badge outside the panel, by its top-level index. */\n filterBadge: (index: number) => PlaywrightLocator;\n /** A badge's × — removes that row immediately, no Apply needed. */\n filterBadgeRemove: (index: number) => PlaywrightLocator;\n /** \"Clear all\" — empties both the pinned slot and the builder group. */\n filterClearAll: () => PlaywrightLocator;\n /** An always-visible pinned control (`views.list.filters.pinned`) for `field`, rendered outside the panel. */\n filterPinned: (field: string) => PlaywrightLocator;\n paginationPrev: () => PlaywrightLocator;\n paginationNext: () => PlaywrightLocator;\n empty: () => PlaywrightLocator;\n toolbar: () => PlaywrightLocator;\n table: () => PlaywrightLocator;\n}\n\nexport interface RowObject {\n cell: (fieldName: string) => PlaywrightLocator;\n action: (actionName: string) => PlaywrightLocator;\n checkbox: () => PlaywrightLocator;\n open: () => Promise<void>;\n}\n\nexport interface ListViewOptions {\n basePath?: string;\n}\n\n/** `path.join('-')` — see `FilterRowPath`'s doc for the empty-path edge case. */\nfunction pathStr(path: FilterRowPath): string {\n return path.join('-');\n}\n\nexport function createListView(\n page: PlaywrightPage,\n resource: string,\n options: ListViewOptions = {},\n): ListView {\n const path = options.basePath ?? `/${resource}`;\n const filterBase = `list-filter:${resource}`;\n const rowBase = (rowPath: FilterRowPath) => `${filterBase}:row:${pathStr(rowPath)}`;\n const groupBase = (groupPath: FilterRowPath) => `${filterBase}:group:${pathStr(groupPath)}`;\n return {\n goto: async () => {\n await page.goto(path);\n },\n row: (recordId) => createRowObject(page, resource, recordId),\n search: () => page.getByTestId(`list-search:${resource}`),\n filtersContainer: () => page.getByTestId(`list-filters:${resource}`),\n filterButton: () => page.getByTestId(`${filterBase}:button`),\n filterPanel: () => page.getByTestId(`${filterBase}:panel`),\n filterAddRow: (groupPath = []) => page.getByTestId(`${groupBase(groupPath)}:add-row`),\n filterRowField: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:field`),\n filterRowFieldOption: (rowPath, field) =>\n page.getByTestId(`${rowBase(rowPath)}:field:option:${field}`),\n filterRowOp: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:op`),\n filterRowOpOption: (rowPath, op) => page.getByTestId(`${rowBase(rowPath)}:op:option:${op}`),\n filterRowValue: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:value`),\n filterRowRemove: (rowPath) => page.getByTestId(`${rowBase(rowPath)}:remove`),\n filterApply: () => page.getByTestId(`${filterBase}:apply`),\n filterReset: () => page.getByTestId(`${filterBase}:reset`),\n filterBadge: (index) => page.getByTestId(`${filterBase}:badge:${index}`),\n filterBadgeRemove: (index) => page.getByTestId(`${filterBase}:badge:${index}:remove`),\n filterClearAll: () => page.getByTestId(`${filterBase}:clear-all`),\n filterPinned: (field) => page.getByTestId(`${filterBase}:pinned:${field}`),\n paginationPrev: () => page.getByTestId(`list-pagination-prev:${resource}`),\n paginationNext: () => page.getByTestId(`list-pagination-next:${resource}`),\n empty: () => page.getByTestId(`list-empty:${resource}`),\n toolbar: () => page.getByTestId(`list-toolbar:${resource}`),\n table: () => page.getByTestId(`list-table:${resource}`),\n };\n}\n\nfunction createRowObject(page: PlaywrightPage, resource: string, recordId: string): RowObject {\n const rowId = `list-row:${resource}:${recordId}`;\n return {\n cell: (fieldName) => page.getByTestId(`list-cell:${resource}:${recordId}:${fieldName}`),\n action: (actionName) =>\n page.getByTestId(`list-row-action:${resource}:${recordId}:${actionName}`),\n checkbox: () => page.getByTestId(`list-row-checkbox:${resource}:${recordId}`),\n open: async () => {\n await page.getByTestId(rowId).click();\n },\n };\n}\n","import type { PlaywrightLocator, PlaywrightPage } from './types.js';\n\nexport interface PickerObject {\n dialog: () => PlaywrightLocator;\n popover: () => PlaywrightLocator;\n option: (value: string) => PlaywrightLocator;\n search: () => PlaywrightLocator;\n clear: () => PlaywrightLocator;\n quickCreate: () => PlaywrightLocator;\n}\n\nexport function createPickerObject(page: PlaywrightPage, targetResource: string): PickerObject {\n return {\n dialog: () => page.getByTestId(`picker-dialog:${targetResource}`),\n popover: () => page.getByTestId(`picker-popover:${targetResource}`),\n option: (value) => page.getByTestId(`picker-option:${targetResource}:${value}`),\n search: () => page.getByTestId(`picker-search:${targetResource}`),\n clear: () => page.getByTestId(`picker-clear:${targetResource}`),\n quickCreate: () => page.getByTestId(`picker-quickcreate:${targetResource}`),\n };\n}\n","import { type FormView, createFormView } from './form-view.js';\nimport type { PlaywrightPage } from './types.js';\n\nexport type ShowView = FormView;\n\nexport function createShowView(page: PlaywrightPage, resource: string): ShowView {\n return createFormView(page, resource, 'show');\n}\n","import { type FormView, createFormView } from './form-view.js';\nimport { type ListView, type ListViewOptions, createListView } from './list-view.js';\nimport { type PickerObject, createPickerObject } from './picker-object.js';\nimport { type ShowView, createShowView } from './show-view.js';\nimport type { PlaywrightPage } from './types.js';\n\nexport interface ResourceObject {\n name: string;\n list: (options?: ListViewOptions) => ListView;\n edit: (recordId: string) => FormView;\n show: (recordId: string) => ShowView;\n create: () => FormView;\n picker: () => PickerObject;\n}\n\nexport function createResourceObject(page: PlaywrightPage, name: string): ResourceObject {\n return {\n name,\n list: (options) => createListView(page, name, options),\n edit: (_recordId) => createFormView(page, name, 'edit'),\n show: (_recordId) => createShowView(page, name),\n create: () => createFormView(page, name, 'create'),\n picker: () => createPickerObject(page, name),\n };\n}\n","import { type ResourceObject, createResourceObject } from './resource-object.js';\nimport type { PlaywrightLocator, PlaywrightPage } from './types.js';\n\nexport interface BuroPage {\n resource: (name: string) => ResourceObject;\n contextPicker: (sliceName: string) => PlaywrightLocator;\n notification: (type: 'success' | 'error' | 'progress' | 'info') => PlaywrightLocator;\n actionConfirm: (resource: string, scope: string, actionName: string) => PlaywrightLocator;\n page: PlaywrightPage;\n}\n\nexport function createBuroPage(page: PlaywrightPage): BuroPage {\n return {\n resource: (name) => createResourceObject(page, name),\n contextPicker: (sliceName) => page.getByTestId(`context-picker:${sliceName}`),\n notification: (type) => page.getByTestId(`notification:${type}`),\n actionConfirm: (resource, scope, actionName) =>\n page.getByTestId(`action-confirm-dialog:${resource}:${scope}:${actionName}`),\n page,\n };\n}\n","import type { PlaywrightLocator, PlaywrightPage } from './types.js';\n\nexport interface ConfirmDialogObject {\n dialog: () => PlaywrightLocator;\n yes: () => PlaywrightLocator;\n no: () => PlaywrightLocator;\n}\n\nexport function createConfirmDialog(\n page: PlaywrightPage,\n resource: string,\n scope: string,\n actionName: string,\n): ConfirmDialogObject {\n return {\n dialog: () => page.getByTestId(`action-confirm-dialog:${resource}:${scope}:${actionName}`),\n yes: () => page.getByTestId(`action-confirm-yes:${resource}:${scope}:${actionName}`),\n no: () => page.getByTestId(`action-confirm-no:${resource}:${scope}:${actionName}`),\n };\n}\n","import {\n resolveDataCapability,\n isDataError,\n validateBatchResult,\n validateMutationResult,\n type BatchedQueryOperation,\n type DataError,\n type DataProvider,\n type ErasedMutationDescriptor,\n type ErasedQueryDescriptor,\n type LiveObserver,\n} from '@burojs/core';\nimport { describe, expect, it } from 'vitest';\n\ntype QueryKind = ErasedQueryDescriptor['kind'];\ntype MutationKind = ErasedMutationDescriptor['kind'];\n\nexport interface ProviderObservedCall {\n readonly lifecycle: 'query' | 'mutation' | 'batch' | 'live-open' | 'live-subscribe';\n readonly kind?: QueryKind | MutationKind;\n readonly descriptor?: ErasedQueryDescriptor | ErasedMutationDescriptor;\n readonly descriptors?: readonly ErasedQueryDescriptor[];\n readonly signal?: AbortSignal;\n}\n\n/** Specimens supplied by an adapter author; unsupported optional methods are omitted. */\nexport interface ProviderContractDescriptors {\n readonly queries?: Partial<Record<QueryKind, ErasedQueryDescriptor>>;\n readonly mutations?: Partial<Record<MutationKind, ErasedMutationDescriptor>>;\n readonly batch?: readonly BatchedQueryOperation[];\n /** Independent batch groups prove that grouping is explicit, never merged by the helper. */\n readonly batchGroups?: readonly (readonly BatchedQueryOperation[])[];\n /** List specimens for both pagination protocols, including expected totals/pageInfo. */\n readonly pagination?: {\n readonly offset?: ErasedQueryDescriptor;\n readonly cursor?: ErasedQueryDescriptor;\n };\n readonly live?: ErasedQueryDescriptor;\n /** Error specimens must make the selected handler reject with a DataError. */\n readonly errors?: readonly ProviderContractErrorSpecimen[];\n /** Hand-authored mapped results; assertions never derive expectations from an adapter. */\n readonly expected?: {\n readonly queries?: Partial<Record<QueryKind, unknown>>;\n readonly mutations?: Partial<Record<MutationKind, unknown>>;\n readonly batch?: unknown;\n readonly pagination?: { readonly offset?: unknown; readonly cursor?: unknown };\n };\n}\n\nexport type ProviderContractErrorSpecimen =\n | {\n readonly lifecycle: 'query';\n readonly kind: QueryKind;\n readonly descriptor: ErasedQueryDescriptor;\n }\n | {\n readonly lifecycle: 'mutation';\n readonly kind: MutationKind;\n readonly descriptor: ErasedMutationDescriptor;\n };\n\nexport interface ProviderContractFixture {\n create(): DataProvider | Promise<DataProvider>;\n descriptors: ProviderContractDescriptors;\n observeCalls(): readonly ProviderObservedCall[];\n}\n\nconst SESSION = Object.freeze({\n sessionKey: 'contract-session',\n scopeKey: 'contract-scope',\n generation: 1,\n});\nconst REQUEST_CONTEXT = Object.freeze({ contract: 'provider' });\n\nfunction context(signal: AbortSignal) {\n return Object.freeze({\n signal,\n requestContext: REQUEST_CONTEXT,\n session: SESSION,\n providerServiceKey: 'contract-provider',\n });\n}\n\nfunction queryShape(kind: QueryKind, value: unknown): void {\n if (kind === 'custom') return;\n expect(value).toBeTypeOf('object');\n if (kind === 'list' || kind === 'many')\n expect((value as { data?: unknown }).data).toBeInstanceOf(Array);\n if (kind === 'one') expect(value).toHaveProperty('data');\n if (kind === 'count')\n expect([null, 'number']).toContain(typeof (value as { total?: unknown }).total);\n}\n\nfunction observed(\n fixture: ProviderContractFixture,\n expected: Omit<ProviderObservedCall, 'signal'>,\n signal: AbortSignal,\n): void {\n expect(fixture.observeCalls()).toContainEqual(expect.objectContaining({ ...expected, signal }));\n}\n\nasync function assertBatch(\n provider: DataProvider,\n fixture: ProviderContractFixture,\n operations: readonly BatchedQueryOperation[],\n expected: unknown,\n): Promise<void> {\n expect(provider.batch?.queries).toBeTypeOf('function');\n const controller = new AbortController();\n const result = await provider.batch!.queries(operations, context(controller.signal));\n validateBatchResult(operations, result);\n if (expected !== undefined) expect(result).toEqual(expected);\n for (const entry of result.results) if (entry.status === 'error') dataError(entry.error);\n observed(\n fixture,\n { lifecycle: 'batch', descriptors: operations.map((operation) => operation.descriptor) },\n controller.signal,\n );\n}\n\nfunction dataError(value: unknown): asserts value is DataError {\n expect(isDataError(value)).toBe(true);\n}\n\n/**\n * A supplied `descriptors.live` IS the adapter author's declaration that this\n * descriptor should produce a plan — an adapter with no live support omits the\n * descriptor (or the `live` slot) and is skipped instead. So a null plan here\n * is a failure, not a skip.\n *\n * Split out as its own assertion, rather than left inline, so the suite's own\n * tests can prove it rejects — the same reason `queryShape`/`observed`/`batch`\n * already are.\n */\nfunction livePlan(value: unknown): void {\n expect(value, 'descriptors.live was supplied, so live.open must return a plan').not.toBeNull();\n expect(value).toBeTypeOf('object');\n expect((value as { subscribe?: unknown } | null)?.subscribe).toBeTypeOf('function');\n}\n\n/**\n * The OTHER door into the same free green. `livePlan` closes \"supplied a\n * descriptor, returned a null plan\"; this closes \"provider has a `live` slot,\n * fixture supplied no descriptor\" — which used to `return` silently, leaving\n * the live cell green with nothing executed. Deleting the single line\n * `live: listDescriptor` from a fixture left `examples/marketplace-ops`'s\n * hasura contract at 35/35 green with the live `it` still passing.\n *\n * The rule is asymmetric on purpose: a provider with NO `live` slot is a\n * provider without live support, and skipping is right. A provider that HAS\n * one and a fixture that says nothing about it is an untested capability\n * reported as a pass.\n *\n * Split out for the same reason `livePlan` is: the suite's own tests drive it\n * directly to prove it rejects.\n */\nfunction liveDescriptorRequired(hasLiveSlot: boolean, descriptor: unknown): void {\n if (!hasLiveSlot) return;\n expect(\n descriptor,\n 'the provider exposes a `live` slot, so the fixture must supply `descriptors.live` — omitting it silently skips the whole live cell',\n ).toBeDefined();\n}\n\n/** @internal Direct assertion seam for the contract suite's intentionally broken-provider tests. */\nexport const __providerContractAssertions = Object.freeze({\n queryShape,\n observed,\n batch: validateBatchResult,\n mutation: validateMutationResult,\n dataError,\n livePlan,\n liveDescriptorRequired,\n});\n\n/**\n * Registers transport-agnostic provider conformance tests. It intentionally\n * imports only core contracts: adapters supply their own deterministic client\n * and expose provider-boundary observations through the fixture.\n */\nexport function dataProviderContract(name: string, fixture: ProviderContractFixture): void {\n describe(`${name} data provider contract`, () => {\n for (const kind of ['list', 'one', 'many', 'count', 'custom'] as const) {\n it(`preserves the ${kind} query descriptor and AbortSignal`, async () => {\n const provider = await fixture.create();\n const descriptor = fixture.descriptors.queries?.[kind];\n const handler = provider.queries?.[kind];\n if (descriptor === undefined || handler === undefined) return;\n const controller = new AbortController();\n const result = await handler(descriptor as never, context(controller.signal));\n queryShape(kind, result);\n const expected = fixture.descriptors.expected?.queries?.[kind];\n if (expected !== undefined) expect(result).toEqual(expected);\n observed(fixture, { lifecycle: 'query', kind, descriptor }, controller.signal);\n });\n }\n\n for (const kind of [\n 'createOne',\n 'createMany',\n 'updateOne',\n 'updateMany',\n 'deleteOne',\n 'deleteMany',\n 'custom',\n ] as const) {\n it(`validates ${kind} mutation output and preserves its request`, async () => {\n const provider = await fixture.create();\n const descriptor = fixture.descriptors.mutations?.[kind];\n const handler = provider.mutations?.[kind];\n if (descriptor === undefined || handler === undefined) return;\n const controller = new AbortController();\n const result = await handler(descriptor as never, context(controller.signal));\n validateMutationResult(descriptor, result);\n const expected = fixture.descriptors.expected?.mutations?.[kind];\n if (expected !== undefined) expect(result).toEqual(expected);\n observed(fixture, { lifecycle: 'mutation', kind, descriptor }, controller.signal);\n });\n }\n\n for (const specimen of fixture.descriptors.errors ?? []) {\n it(`returns a structured error for the ${specimen.lifecycle}.${specimen.kind} error specimen`, async () => {\n const provider = await fixture.create();\n const controller = new AbortController();\n const handler =\n specimen.lifecycle === 'query'\n ? provider.queries?.[specimen.kind as QueryKind]\n : provider.mutations?.[specimen.kind as MutationKind];\n expect(handler).toBeTypeOf('function');\n try {\n await (\n handler as (\n descriptor: never,\n operation: ReturnType<typeof context>,\n ) => Promise<unknown>\n )(specimen.descriptor as never, context(controller.signal));\n throw new Error('Provider error specimen resolved instead of rejecting.');\n } catch (error) {\n dataError(error);\n }\n observed(\n fixture,\n { lifecycle: specimen.lifecycle, kind: specimen.kind, descriptor: specimen.descriptor },\n controller.signal,\n );\n });\n }\n\n it('reports absent optional capabilities without invoking fallback handlers', async () => {\n const provider = await fixture.create();\n const before = fixture.observeCalls().length;\n for (const kind of ['list', 'one', 'many', 'count', 'custom'] as const) {\n if (provider.queries?.[kind] === undefined) {\n const capability = resolveDataCapability(provider, { lifecycle: 'query', kind });\n expect(capability).toMatchObject({ kind: 'capability', code: 'missing-data-capability' });\n }\n }\n for (const kind of [\n 'createOne',\n 'createMany',\n 'updateOne',\n 'updateMany',\n 'deleteOne',\n 'deleteMany',\n 'custom',\n ] as const) {\n if (provider.mutations?.[kind] === undefined) {\n const capability = resolveDataCapability(provider, { lifecycle: 'mutation', kind });\n expect(capability).toMatchObject({ kind: 'capability', code: 'missing-data-capability' });\n }\n }\n expect(fixture.observeCalls()).toHaveLength(before);\n });\n\n it('validates batch IDs, kinds, partial errors, and the shared AbortSignal', async () => {\n const provider = await fixture.create();\n const operations = fixture.descriptors.batch;\n if (provider.batch?.queries === undefined || operations === undefined) return;\n await assertBatch(provider, fixture, operations, fixture.descriptors.expected?.batch);\n });\n\n for (const [index, operations] of (fixture.descriptors.batchGroups ?? []).entries()) {\n it(`keeps batch group ${index + 1} isolated and validates its partial results`, async () => {\n const provider = await fixture.create();\n await assertBatch(provider, fixture, operations, undefined);\n });\n }\n\n for (const kind of ['offset', 'cursor'] as const) {\n it(`preserves ${kind} pagination and its mapped total/page shape`, async () => {\n const provider = await fixture.create();\n const descriptor = fixture.descriptors.pagination?.[kind];\n if (descriptor === undefined || provider.queries?.list === undefined) return;\n const controller = new AbortController();\n const result = await provider.queries.list(descriptor as never, context(controller.signal));\n queryShape('list', result);\n const expected = fixture.descriptors.expected?.pagination?.[kind];\n if (expected !== undefined) expect(result).toEqual(expected);\n observed(fixture, { lifecycle: 'query', kind: 'list', descriptor }, controller.signal);\n });\n }\n\n it('opens live plans with the complete descriptor and subscribes with an AbortSignal', async () => {\n const provider = await fixture.create();\n const descriptor = fixture.descriptors.live;\n liveDescriptorRequired(provider.live !== undefined, descriptor);\n if (provider.live === undefined || descriptor === undefined) return;\n const openController = new AbortController();\n const plan = await provider.live.open(descriptor, context(openController.signal));\n observed(fixture, { lifecycle: 'live-open', descriptor }, openController.signal);\n // A null plan used to `return` here, which handed every adapter a free\n // green on the whole live cell: `subscribe` was never reached, so\n // nothing about the plan was ever checked, and the `it` passed for an\n // adapter that had done no live work at all.\n livePlan(plan);\n if (plan === null) return; // unreachable past `livePlan`; narrows the type\n const subscribeController = new AbortController();\n const observer: LiveObserver = {\n next: () => undefined,\n status: () => undefined,\n error: () => undefined,\n complete: () => undefined,\n };\n const unsubscribe = await plan.subscribe(observer, { signal: subscribeController.signal });\n observed(fixture, { lifecycle: 'live-subscribe' }, subscribeController.signal);\n await unsubscribe();\n });\n });\n}\n","// Constants\nexport const PACKAGE_NAME = '@burojs/test-helpers' as const;\nexport const PACKAGE_VERSION = '0.0.0' as const;\n\n// Types\nexport type { PlaywrightPage, PlaywrightLocator } from './types.js';\n\n// Page object factories\nexport { createBuroPage, type BuroPage } from './buro-page.js';\nexport { createResourceObject, type ResourceObject } from './resource-object.js';\nexport {\n createListView,\n type ListView,\n type RowObject,\n type ListViewOptions,\n} from './list-view.js';\nexport { createFormView, type FormView, type FormFieldObject } from './form-view.js';\nexport { createShowView, type ShowView } from './show-view.js';\nexport { createPickerObject, type PickerObject } from './picker-object.js';\nexport { createConfirmDialog, type ConfirmDialogObject } from './confirm-dialog.js';\nexport {\n dataProviderContract,\n type ProviderContractDescriptors,\n type ProviderContractFixture,\n type ProviderObservedCall,\n} from './data-provider-contract.js';\n"]}
|