@nextsparkjs/testing 0.1.0-beta.100

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/dist/index.js ADDED
@@ -0,0 +1,900 @@
1
+ // src/utils/utils.ts
2
+ function createCyId(...parts) {
3
+ return parts.filter(Boolean).join("-");
4
+ }
5
+ function createTestId(...parts) {
6
+ return parts.filter(Boolean).join("-");
7
+ }
8
+ function sel(...parts) {
9
+ return parts.filter(Boolean).join(".");
10
+ }
11
+ var isDevelopment = process.env.NODE_ENV === "development";
12
+ var isTest = process.env.NODE_ENV === "test";
13
+ var enableTestingAttributes = isDevelopment || isTest;
14
+ function createStateAttr(state) {
15
+ return state;
16
+ }
17
+ function createPriorityAttr(priority) {
18
+ return priority;
19
+ }
20
+ function createTestingProps(config) {
21
+ const props = {};
22
+ if (config.testId) {
23
+ props["data-testid"] = enableTestingAttributes ? config.testId : void 0;
24
+ }
25
+ if (config.cyId) {
26
+ props["data-cy"] = enableTestingAttributes ? config.cyId : void 0;
27
+ }
28
+ if (config.state) {
29
+ props["data-state"] = config.state;
30
+ }
31
+ if (config.priority) {
32
+ props["data-priority"] = config.priority;
33
+ }
34
+ if (config.taskId) {
35
+ props["data-task-id"] = config.taskId;
36
+ }
37
+ if (config.userId) {
38
+ props["data-user-id"] = config.userId;
39
+ }
40
+ return Object.fromEntries(
41
+ Object.entries(props).filter((entry) => entry[1] !== void 0)
42
+ );
43
+ }
44
+ function createAriaLabel(template, values) {
45
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
46
+ return String(values[key] ?? match);
47
+ });
48
+ }
49
+ var keyboardHelpers = {
50
+ /**
51
+ * Handle Enter and Space key activation
52
+ */
53
+ createActivationHandler: (onActivate) => {
54
+ return (e) => {
55
+ if (e.key === "Enter" || e.key === " ") {
56
+ e.preventDefault();
57
+ onActivate();
58
+ }
59
+ };
60
+ },
61
+ /**
62
+ * Handle Escape key for closing
63
+ */
64
+ createEscapeHandler: (onClose) => {
65
+ return (e) => {
66
+ if (e.key === "Escape") {
67
+ e.preventDefault();
68
+ onClose();
69
+ }
70
+ };
71
+ },
72
+ /**
73
+ * Handle arrow navigation in lists
74
+ */
75
+ createArrowNavigationHandler: (currentIndex, maxIndex, onIndexChange) => {
76
+ return (e) => {
77
+ switch (e.key) {
78
+ case "ArrowDown":
79
+ e.preventDefault();
80
+ onIndexChange(currentIndex < maxIndex ? currentIndex + 1 : 0);
81
+ break;
82
+ case "ArrowUp":
83
+ e.preventDefault();
84
+ onIndexChange(currentIndex > 0 ? currentIndex - 1 : maxIndex);
85
+ break;
86
+ }
87
+ };
88
+ }
89
+ };
90
+
91
+ // src/pom/BasePOMCore.ts
92
+ var BasePOMCore = class {
93
+ /**
94
+ * Get a Cypress selector using the centralized selectors
95
+ * Wrapper for cySelector with a shorter name
96
+ *
97
+ * @example
98
+ * this.cy('auth.login.form')
99
+ * // Returns: '[data-cy="login-form"]'
100
+ *
101
+ * this.cy('entities.table.row', { slug: 'tasks', id: '123' })
102
+ * // Returns: '[data-cy="tasks-row-123"]'
103
+ */
104
+ cy(path, replacements) {
105
+ return this.cySelector(path, replacements);
106
+ }
107
+ /**
108
+ * Replaces placeholders in a selector pattern and wraps with data-cy attribute
109
+ *
110
+ * @param pattern - Selector pattern with {placeholder} syntax
111
+ * @param replacements - Object with placeholder values
112
+ * @returns Formatted data-cy selector string
113
+ *
114
+ * @example
115
+ * selector('{slug}-row-{id}', { slug: 'tasks', id: '123' })
116
+ * // Returns: '[data-cy="tasks-row-123"]'
117
+ */
118
+ selector(pattern, replacements = {}) {
119
+ let result = pattern;
120
+ for (const [key, value] of Object.entries(replacements)) {
121
+ result = result.replaceAll(`{${key}}`, String(value));
122
+ }
123
+ return `[data-cy="${result}"]`;
124
+ }
125
+ /**
126
+ * Wrapper for cy.get with selector pattern support
127
+ * @param pattern - Selector pattern or direct selector
128
+ * @param replacements - Optional placeholder replacements
129
+ */
130
+ get(pattern, replacements = {}) {
131
+ return cy.get(this.selector(pattern, replacements));
132
+ }
133
+ /**
134
+ * Generic wait with configurable timeout
135
+ * @param selector - CSS selector to wait for
136
+ * @param timeout - Max wait time in ms (default: 15000)
137
+ */
138
+ waitFor(selector, timeout = 15e3) {
139
+ cy.get(selector, { timeout }).should("be.visible");
140
+ return this;
141
+ }
142
+ /**
143
+ * Wait for URL to contain a specific path
144
+ * @param path - Path segment to check for
145
+ */
146
+ waitForUrl(path) {
147
+ cy.url().should("include", path);
148
+ return this;
149
+ }
150
+ /**
151
+ * Wait for URL to match a regex pattern
152
+ * @param pattern - RegExp to match against URL
153
+ */
154
+ waitForUrlMatch(pattern) {
155
+ cy.url().should("match", pattern);
156
+ return this;
157
+ }
158
+ /**
159
+ * Visit a URL and return self for chaining
160
+ * @param url - URL to visit
161
+ */
162
+ visit(url) {
163
+ cy.visit(url);
164
+ return this;
165
+ }
166
+ /**
167
+ * Wait for page to load (checks for body visible)
168
+ */
169
+ waitForPageLoad() {
170
+ cy.get("body").should("be.visible");
171
+ return this;
172
+ }
173
+ /**
174
+ * Get an element by selector
175
+ * @param selector - CSS selector
176
+ */
177
+ getElement(selector) {
178
+ return cy.get(selector);
179
+ }
180
+ /**
181
+ * Click on an element
182
+ * @param selector - CSS selector
183
+ */
184
+ click(selector) {
185
+ cy.get(selector).click();
186
+ return this;
187
+ }
188
+ /**
189
+ * Type text into an input
190
+ * @param selector - CSS selector
191
+ * @param text - Text to type
192
+ */
193
+ type(selector, text) {
194
+ cy.get(selector).clear().type(text);
195
+ return this;
196
+ }
197
+ /**
198
+ * Check if element exists
199
+ * @param selector - CSS selector
200
+ */
201
+ exists(selector) {
202
+ return cy.get(selector).should("exist");
203
+ }
204
+ /**
205
+ * Check if element is visible
206
+ * @param selector - CSS selector
207
+ */
208
+ isVisible(selector) {
209
+ return cy.get(selector).should("be.visible");
210
+ }
211
+ /**
212
+ * Check if element does not exist
213
+ * @param selector - CSS selector
214
+ */
215
+ notExists(selector) {
216
+ return cy.get(selector).should("not.exist");
217
+ }
218
+ };
219
+
220
+ // src/helpers/ApiInterceptor.ts
221
+ var ApiInterceptor = class {
222
+ constructor(slugOrConfig) {
223
+ if (typeof slugOrConfig === "string") {
224
+ this.slug = slugOrConfig;
225
+ this.endpoint = `/api/v1/${slugOrConfig}`;
226
+ } else {
227
+ this.slug = slugOrConfig.slug;
228
+ this.endpoint = slugOrConfig.customPath || `/api/v1/${slugOrConfig.slug}`;
229
+ }
230
+ }
231
+ // ============================================
232
+ // ACCESSORS
233
+ // ============================================
234
+ /** Get the API endpoint path */
235
+ get path() {
236
+ return this.endpoint;
237
+ }
238
+ /** Get the entity slug */
239
+ get entitySlug() {
240
+ return this.slug;
241
+ }
242
+ /** Get alias names for all operations */
243
+ get aliases() {
244
+ return {
245
+ list: `${this.slug}List`,
246
+ create: `${this.slug}Create`,
247
+ update: `${this.slug}Update`,
248
+ delete: `${this.slug}Delete`
249
+ };
250
+ }
251
+ // ============================================
252
+ // INTERCEPT SETUP
253
+ // ============================================
254
+ /**
255
+ * Setup intercepts for all CRUD operations
256
+ * Call this BEFORE navigation in beforeEach or at test start
257
+ *
258
+ * Note: We intercept both PUT and PATCH for updates since different
259
+ * APIs may use different HTTP methods for updates.
260
+ */
261
+ setupCrudIntercepts() {
262
+ cy.intercept("GET", `${this.endpoint}*`).as(this.aliases.list);
263
+ cy.intercept("POST", this.endpoint).as(this.aliases.create);
264
+ cy.intercept("PUT", `${this.endpoint}/*`).as(this.aliases.update);
265
+ cy.intercept("PATCH", `${this.endpoint}/*`).as(`${this.aliases.update}Patch`);
266
+ cy.intercept("DELETE", `${this.endpoint}/*`).as(this.aliases.delete);
267
+ return this;
268
+ }
269
+ /**
270
+ * Setup only list + create intercepts
271
+ * Useful for list pages with inline create
272
+ */
273
+ setupListIntercepts() {
274
+ cy.intercept("GET", `${this.endpoint}*`).as(this.aliases.list);
275
+ cy.intercept("POST", this.endpoint).as(this.aliases.create);
276
+ return this;
277
+ }
278
+ // ============================================
279
+ // WAIT METHODS
280
+ // ============================================
281
+ /**
282
+ * Wait for list response (GET)
283
+ * Use after navigation or after mutations to wait for refresh
284
+ */
285
+ waitForList(timeout = 1e4) {
286
+ return cy.wait(`@${this.aliases.list}`, { timeout });
287
+ }
288
+ /**
289
+ * Wait for create response (POST) and validate success status
290
+ */
291
+ waitForCreate(timeout = 1e4) {
292
+ return cy.wait(`@${this.aliases.create}`, { timeout }).its("response.statusCode").should("be.oneOf", [200, 201]);
293
+ }
294
+ /**
295
+ * Wait for update response (PATCH or PUT) and validate success status
296
+ * Waits for PATCH first (more common), falls back to PUT
297
+ */
298
+ waitForUpdate(timeout = 1e4) {
299
+ return cy.wait(`@${this.aliases.update}Patch`, { timeout }).its("response.statusCode").should("be.oneOf", [200, 201]);
300
+ }
301
+ /**
302
+ * Wait for delete response (DELETE) and validate success status
303
+ */
304
+ waitForDelete(timeout = 1e4) {
305
+ return cy.wait(`@${this.aliases.delete}`, { timeout }).its("response.statusCode").should("be.oneOf", [200, 204]);
306
+ }
307
+ // ============================================
308
+ // CONVENIENCE METHODS
309
+ // ============================================
310
+ /**
311
+ * Wait for list refresh (alias for waitForList)
312
+ * Semantic name for use after create/update/delete
313
+ */
314
+ waitForRefresh(timeout = 1e4) {
315
+ return this.waitForList(timeout);
316
+ }
317
+ /**
318
+ * Wait for create + list refresh
319
+ * Common pattern: create entity, wait for success, wait for list to refresh
320
+ */
321
+ waitForCreateAndRefresh(timeout = 1e4) {
322
+ this.waitForCreate(timeout);
323
+ return this.waitForList(timeout);
324
+ }
325
+ /**
326
+ * Wait for update + list refresh
327
+ * Common pattern: update entity, wait for success, wait for list to refresh
328
+ */
329
+ waitForUpdateAndRefresh(timeout = 1e4) {
330
+ this.waitForUpdate(timeout);
331
+ return this.waitForList(timeout);
332
+ }
333
+ /**
334
+ * Wait for delete + list refresh
335
+ * Common pattern: delete entity, wait for success, wait for list to refresh
336
+ */
337
+ waitForDeleteAndRefresh(timeout = 1e4) {
338
+ this.waitForDelete(timeout);
339
+ return this.waitForList(timeout);
340
+ }
341
+ };
342
+
343
+ // src/pom/DashboardEntityPOMCore.ts
344
+ var DashboardEntityPOMCore = class extends BasePOMCore {
345
+ constructor(entitySlugOrConfig) {
346
+ super();
347
+ this._api = null;
348
+ if (typeof entitySlugOrConfig === "string") {
349
+ this.slug = entitySlugOrConfig;
350
+ this.entityConfig = { slug: entitySlugOrConfig };
351
+ } else {
352
+ this.slug = entitySlugOrConfig.slug;
353
+ this.entityConfig = entitySlugOrConfig;
354
+ }
355
+ }
356
+ /**
357
+ * Get the entity slug (public accessor)
358
+ * Useful for building dynamic selectors and URLs in tests
359
+ */
360
+ get entitySlug() {
361
+ return this.slug;
362
+ }
363
+ // ============================================
364
+ // API INTERCEPTOR
365
+ // ============================================
366
+ /**
367
+ * Get or create ApiInterceptor instance for this entity
368
+ */
369
+ get api() {
370
+ if (!this._api) {
371
+ this._api = new ApiInterceptor(this.slug);
372
+ }
373
+ return this._api;
374
+ }
375
+ /**
376
+ * Setup API intercepts for all CRUD operations
377
+ * Call this BEFORE navigation
378
+ */
379
+ setupApiIntercepts() {
380
+ this.api.setupCrudIntercepts();
381
+ return this;
382
+ }
383
+ // ============================================
384
+ // SELECTORS (uses cySelector from theme)
385
+ // ============================================
386
+ /**
387
+ * Get all selectors for this entity, with placeholders replaced
388
+ * Uses the abstract cySelector method which themes implement
389
+ */
390
+ get selectors() {
391
+ const slug = this.slug;
392
+ return {
393
+ // Page
394
+ page: this.cySelector("entities.page.container", { slug }),
395
+ // List - Table
396
+ tableContainer: this.cySelector("entities.list.table.container", { slug }),
397
+ table: this.cySelector("entities.list.table.element", { slug }),
398
+ addButton: this.cySelector("entities.list.addButton", { slug }),
399
+ selectAll: this.cySelector("entities.list.table.selectAll", { slug }),
400
+ selectionCount: this.cySelector("entities.list.selectionCount", { slug }),
401
+ row: (id) => this.cySelector("entities.list.table.row.element", { slug, id }),
402
+ rowSelect: (id) => this.cySelector("entities.list.table.row.checkbox", { slug, id }),
403
+ rowMenu: (id) => this.cySelector("entities.list.table.row.menu", { slug, id }),
404
+ rowAction: (action, id) => this.cySelector("entities.list.table.row.action", { slug, action, id }),
405
+ cell: (name, id) => this.cySelector("entities.list.table.cell.element", { slug, name, id }),
406
+ rowGeneric: `[data-cy^="${slug}-row-"]`,
407
+ // List - Search
408
+ search: this.cySelector("entities.list.search.input", { slug }),
409
+ searchContainer: this.cySelector("entities.list.search.container", { slug }),
410
+ searchClear: this.cySelector("entities.list.search.clear", { slug }),
411
+ // List - Pagination
412
+ pagination: this.cySelector("entities.list.pagination.container", { slug }),
413
+ pageSize: this.cySelector("entities.list.pagination.pageSize", { slug }),
414
+ pageSizeOption: (size) => this.cySelector("entities.list.pagination.pageSizeOption", { slug, size }),
415
+ pageInfo: this.cySelector("entities.list.pagination.info", { slug }),
416
+ pageFirst: this.cySelector("entities.list.pagination.first", { slug }),
417
+ pagePrev: this.cySelector("entities.list.pagination.prev", { slug }),
418
+ pageNext: this.cySelector("entities.list.pagination.next", { slug }),
419
+ pageLast: this.cySelector("entities.list.pagination.last", { slug }),
420
+ // List - Filters
421
+ filter: (field) => this.cySelector("entities.list.filters.container", { slug, field }),
422
+ filterTrigger: (field) => this.cySelector("entities.list.filters.trigger", { slug, field }),
423
+ filterContent: (field) => this.cySelector("entities.list.filters.content", { slug, field }),
424
+ filterOption: (field, value) => this.cySelector("entities.list.filters.option", { slug, field, value }),
425
+ filterBadge: (field, value) => this.cySelector("entities.list.filters.badge", { slug, field, value }),
426
+ filterRemoveBadge: (field, value) => this.cySelector("entities.list.filters.removeBadge", { slug, field, value }),
427
+ filterClearAll: (field) => this.cySelector("entities.list.filters.clearAll", { slug, field }),
428
+ // List - Bulk actions
429
+ bulkBar: this.cySelector("entities.list.bulk.bar", { slug }),
430
+ bulkCount: this.cySelector("entities.list.bulk.count", { slug }),
431
+ bulkSelectAll: this.cySelector("entities.list.bulk.selectAll", { slug }),
432
+ bulkStatus: this.cySelector("entities.list.bulk.statusButton", { slug }),
433
+ bulkDelete: this.cySelector("entities.list.bulk.deleteButton", { slug }),
434
+ bulkClear: this.cySelector("entities.list.bulk.clearButton", { slug }),
435
+ // List - Bulk status dialog
436
+ bulkStatusDialog: this.cySelector("entities.list.bulk.statusDialog", { slug }),
437
+ bulkStatusSelect: this.cySelector("entities.list.bulk.statusSelect", { slug }),
438
+ bulkStatusOption: (value) => this.cySelector("entities.list.bulk.statusOption", { slug, value }),
439
+ bulkStatusCancel: this.cySelector("entities.list.bulk.statusCancel", { slug }),
440
+ bulkStatusConfirm: this.cySelector("entities.list.bulk.statusConfirm", { slug }),
441
+ // List - Bulk delete dialog
442
+ bulkDeleteDialog: this.cySelector("entities.list.bulk.deleteDialog", { slug }),
443
+ bulkDeleteCancel: this.cySelector("entities.list.bulk.deleteCancel", { slug }),
444
+ bulkDeleteConfirm: this.cySelector("entities.list.bulk.deleteConfirm", { slug }),
445
+ // List - Confirm dialogs (for row actions)
446
+ confirmDialog: this.cySelector("entities.list.confirm.dialog", { slug }),
447
+ confirmCancel: this.cySelector("entities.list.confirm.cancel", { slug }),
448
+ confirmAction: this.cySelector("entities.list.confirm.action", { slug }),
449
+ // Header (detail pages) - modes: view, edit, create
450
+ viewHeader: this.cySelector("entities.header.container", { slug, mode: "view" }),
451
+ editHeader: this.cySelector("entities.header.container", { slug, mode: "edit" }),
452
+ createHeader: this.cySelector("entities.header.container", { slug, mode: "create" }),
453
+ backButton: this.cySelector("entities.header.backButton", { slug }),
454
+ editButton: this.cySelector("entities.header.editButton", { slug }),
455
+ deleteButton: this.cySelector("entities.header.deleteButton", { slug }),
456
+ title: this.cySelector("entities.header.title", { slug }),
457
+ // Header - Delete confirmation
458
+ deleteDialog: this.cySelector("entities.header.deleteDialog", { slug }),
459
+ deleteCancel: this.cySelector("entities.header.deleteCancel", { slug }),
460
+ deleteConfirm: this.cySelector("entities.header.deleteConfirm", { slug }),
461
+ // Detail view
462
+ detail: this.cySelector("entities.detail.container", { slug }),
463
+ // Form
464
+ form: this.cySelector("entities.form.container", { slug }),
465
+ field: (name) => this.cySelector("entities.form.field", { slug, name }),
466
+ submitButton: this.cySelector("entities.form.submitButton", { slug }),
467
+ // Parent delete confirmation (EntityDetailWrapper - generic, no slug)
468
+ parentDeleteConfirm: '[data-cy="confirm-delete"]',
469
+ parentDeleteCancel: '[data-cy="cancel-delete"]',
470
+ // Row action selectors (generic patterns for checking existence)
471
+ rowActionEditGeneric: `[data-cy^="${slug}-action-edit-"]`,
472
+ rowActionDeleteGeneric: `[data-cy^="${slug}-action-delete-"]`
473
+ };
474
+ }
475
+ // ============================================
476
+ // NAVIGATION
477
+ // ============================================
478
+ /**
479
+ * Navigate to entity list page
480
+ */
481
+ visitList() {
482
+ cy.visit(`/dashboard/${this.slug}`);
483
+ return this;
484
+ }
485
+ /**
486
+ * Navigate to create page
487
+ */
488
+ visitCreate() {
489
+ cy.visit(`/dashboard/${this.slug}/create`);
490
+ return this;
491
+ }
492
+ /**
493
+ * Navigate to edit page for specific entity
494
+ */
495
+ visitEdit(id) {
496
+ cy.visit(`/dashboard/${this.slug}/${id}/edit`);
497
+ return this;
498
+ }
499
+ /**
500
+ * Navigate to detail/view page for specific entity
501
+ */
502
+ visitDetail(id) {
503
+ cy.visit(`/dashboard/${this.slug}/${id}`);
504
+ return this;
505
+ }
506
+ // ============================================
507
+ // API-AWARE NAVIGATION
508
+ // ============================================
509
+ /**
510
+ * Navigate to list and wait for API response
511
+ */
512
+ visitListWithApiWait() {
513
+ this.setupApiIntercepts();
514
+ this.visitList();
515
+ this.api.waitForList();
516
+ return this;
517
+ }
518
+ /**
519
+ * Navigate to edit page and wait for form to be visible
520
+ */
521
+ visitEditWithApiWait(id) {
522
+ this.setupApiIntercepts();
523
+ this.visitEdit(id);
524
+ this.waitForForm();
525
+ return this;
526
+ }
527
+ /**
528
+ * Navigate to detail page and wait for content
529
+ */
530
+ visitDetailWithApiWait(id) {
531
+ this.setupApiIntercepts();
532
+ this.visitDetail(id);
533
+ this.waitForDetail();
534
+ return this;
535
+ }
536
+ // ============================================
537
+ // WAITS
538
+ // ============================================
539
+ /**
540
+ * Wait for list page to be fully loaded
541
+ */
542
+ waitForList() {
543
+ cy.url().should("include", `/dashboard/${this.slug}`);
544
+ cy.get(this.selectors.tableContainer, { timeout: 15e3 }).should("be.visible");
545
+ return this;
546
+ }
547
+ /**
548
+ * Wait for form to be visible
549
+ */
550
+ waitForForm() {
551
+ cy.get(this.selectors.form, { timeout: 15e3 }).should("be.visible");
552
+ return this;
553
+ }
554
+ /**
555
+ * Wait for detail page to be loaded
556
+ */
557
+ waitForDetail() {
558
+ cy.url().should("match", new RegExp(`/dashboard/${this.slug}/[a-z0-9-]+$`));
559
+ cy.get(this.selectors.editButton, { timeout: 15e3 }).should("be.visible");
560
+ return this;
561
+ }
562
+ // ============================================
563
+ // TABLE ACTIONS
564
+ // ============================================
565
+ /**
566
+ * Click the Add/Create button
567
+ */
568
+ clickAdd() {
569
+ cy.get(this.selectors.addButton).click();
570
+ return this;
571
+ }
572
+ /**
573
+ * Type in the search input
574
+ */
575
+ search(term) {
576
+ cy.get(this.selectors.search).clear().type(term);
577
+ return this;
578
+ }
579
+ /**
580
+ * Clear the search input
581
+ */
582
+ clearSearch() {
583
+ cy.get(this.selectors.searchClear).click();
584
+ return this;
585
+ }
586
+ /**
587
+ * Click a specific row by ID
588
+ */
589
+ clickRow(id) {
590
+ cy.get(this.selectors.row(id)).click();
591
+ return this;
592
+ }
593
+ /**
594
+ * Find and click a row containing specific text
595
+ */
596
+ clickRowByText(text) {
597
+ cy.contains(this.selectors.rowGeneric, text).click();
598
+ return this;
599
+ }
600
+ /**
601
+ * Select a row checkbox
602
+ */
603
+ selectRow(id) {
604
+ cy.get(this.selectors.rowSelect(id)).click();
605
+ return this;
606
+ }
607
+ /**
608
+ * Open the row menu (three dots)
609
+ */
610
+ openRowMenu(id) {
611
+ cy.get(this.selectors.rowMenu(id)).click();
612
+ return this;
613
+ }
614
+ /**
615
+ * Click an action in the row menu
616
+ */
617
+ clickRowAction(action, id) {
618
+ cy.get(this.selectors.rowAction(action, id)).click();
619
+ return this;
620
+ }
621
+ // ============================================
622
+ // FILTERS
623
+ // ============================================
624
+ /**
625
+ * Open a filter dropdown
626
+ */
627
+ openFilter(field) {
628
+ cy.get(this.selectors.filterTrigger(field)).click();
629
+ return this;
630
+ }
631
+ /**
632
+ * Select a filter option
633
+ */
634
+ selectFilterOption(field, value) {
635
+ cy.get(this.selectors.filterOption(field, value)).click();
636
+ return this;
637
+ }
638
+ /**
639
+ * Open filter and select option (convenience method)
640
+ */
641
+ selectFilter(field, value) {
642
+ this.openFilter(field);
643
+ this.selectFilterOption(field, value);
644
+ return this;
645
+ }
646
+ /**
647
+ * Clear all selected options for a specific filter
648
+ * NOTE: Clear button only appears when >1 option is selected
649
+ */
650
+ clearFilter(field) {
651
+ cy.get(this.selectors.filterClearAll(field)).click();
652
+ return this;
653
+ }
654
+ // ============================================
655
+ // PAGINATION
656
+ // ============================================
657
+ /**
658
+ * Go to next page
659
+ */
660
+ nextPage() {
661
+ cy.get(this.selectors.pageNext).click();
662
+ return this;
663
+ }
664
+ /**
665
+ * Go to previous page
666
+ */
667
+ prevPage() {
668
+ cy.get(this.selectors.pagePrev).click();
669
+ return this;
670
+ }
671
+ /**
672
+ * Go to first page
673
+ */
674
+ firstPage() {
675
+ cy.get(this.selectors.pageFirst).click();
676
+ return this;
677
+ }
678
+ /**
679
+ * Go to last page
680
+ */
681
+ lastPage() {
682
+ cy.get(this.selectors.pageLast).click();
683
+ return this;
684
+ }
685
+ /**
686
+ * Change page size
687
+ */
688
+ setPageSize(size) {
689
+ cy.get(this.selectors.pageSize).click();
690
+ cy.get(this.selectors.pageSizeOption(size)).click();
691
+ return this;
692
+ }
693
+ // ============================================
694
+ // FORM ACTIONS
695
+ // ============================================
696
+ /**
697
+ * Fill a text input field
698
+ */
699
+ fillTextField(name, value) {
700
+ cy.get(this.selectors.field(name)).find("input").clear().type(value);
701
+ return this;
702
+ }
703
+ /**
704
+ * Fill a textarea field
705
+ */
706
+ fillTextarea(name, value) {
707
+ cy.get(this.selectors.field(name)).find("textarea").clear().type(value);
708
+ return this;
709
+ }
710
+ /**
711
+ * Select an option in a combobox/select field
712
+ */
713
+ selectOption(name, value) {
714
+ cy.get(this.selectors.field(name)).find('[role="combobox"]').click();
715
+ cy.get(`[data-cy="${this.slug}-field-${name}-option-${value}"]`).click();
716
+ return this;
717
+ }
718
+ /**
719
+ * Submit the form
720
+ */
721
+ submitForm() {
722
+ cy.get(this.selectors.submitButton).click();
723
+ return this;
724
+ }
725
+ // ============================================
726
+ // HEADER/DETAIL ACTIONS
727
+ // ============================================
728
+ /**
729
+ * Click back button
730
+ */
731
+ clickBack() {
732
+ cy.get(this.selectors.backButton).click();
733
+ return this;
734
+ }
735
+ /**
736
+ * Click edit button
737
+ */
738
+ clickEdit() {
739
+ cy.get(this.selectors.editButton).click();
740
+ return this;
741
+ }
742
+ /**
743
+ * Click delete button
744
+ */
745
+ clickDelete() {
746
+ cy.get(this.selectors.deleteButton).click();
747
+ return this;
748
+ }
749
+ /**
750
+ * Confirm delete in dialog
751
+ */
752
+ confirmDelete() {
753
+ cy.get(this.selectors.deleteConfirm).click();
754
+ return this;
755
+ }
756
+ /**
757
+ * Cancel delete in dialog
758
+ */
759
+ cancelDelete() {
760
+ cy.get(this.selectors.deleteCancel).click();
761
+ return this;
762
+ }
763
+ // ============================================
764
+ // BULK ACTIONS
765
+ // ============================================
766
+ /**
767
+ * Select all items using table header checkbox
768
+ */
769
+ selectAll() {
770
+ cy.get(this.selectors.selectAll).click();
771
+ return this;
772
+ }
773
+ /**
774
+ * Click bulk delete button
775
+ */
776
+ bulkDelete() {
777
+ cy.get(this.selectors.bulkDelete).click();
778
+ return this;
779
+ }
780
+ /**
781
+ * Confirm bulk delete
782
+ */
783
+ confirmBulkDelete() {
784
+ cy.get(this.selectors.bulkDeleteConfirm).click();
785
+ return this;
786
+ }
787
+ /**
788
+ * Cancel bulk delete
789
+ */
790
+ cancelBulkDelete() {
791
+ cy.get(this.selectors.bulkDeleteCancel).click();
792
+ return this;
793
+ }
794
+ /**
795
+ * Click bulk status button
796
+ */
797
+ bulkChangeStatus() {
798
+ cy.get(this.selectors.bulkStatus).click();
799
+ return this;
800
+ }
801
+ /**
802
+ * Select status in bulk status dialog
803
+ */
804
+ selectBulkStatus(value) {
805
+ cy.get(this.selectors.bulkStatusSelect).click();
806
+ cy.get(this.selectors.bulkStatusOption(value)).click();
807
+ return this;
808
+ }
809
+ /**
810
+ * Confirm bulk status change
811
+ */
812
+ confirmBulkStatus() {
813
+ cy.get(this.selectors.bulkStatusConfirm).click();
814
+ return this;
815
+ }
816
+ /**
817
+ * Clear selection
818
+ */
819
+ clearSelection() {
820
+ cy.get(this.selectors.bulkClear).click();
821
+ return this;
822
+ }
823
+ // ============================================
824
+ // ASSERTIONS
825
+ // ============================================
826
+ /**
827
+ * Assert text is visible in the list
828
+ */
829
+ assertInList(text) {
830
+ cy.contains(text).should("be.visible");
831
+ return this;
832
+ }
833
+ /**
834
+ * Assert text is not in the list
835
+ */
836
+ assertNotInList(text) {
837
+ cy.contains(text).should("not.exist");
838
+ return this;
839
+ }
840
+ /**
841
+ * Assert table is visible
842
+ */
843
+ assertTableVisible() {
844
+ cy.get(this.selectors.table).should("be.visible");
845
+ return this;
846
+ }
847
+ /**
848
+ * Assert form is visible
849
+ */
850
+ assertFormVisible() {
851
+ cy.get(this.selectors.form).should("be.visible");
852
+ return this;
853
+ }
854
+ /**
855
+ * Assert page title contains text
856
+ */
857
+ assertPageTitle(expected) {
858
+ cy.get(this.selectors.title).should("contain.text", expected);
859
+ return this;
860
+ }
861
+ /**
862
+ * Assert row exists
863
+ */
864
+ assertRowExists(id) {
865
+ cy.get(this.selectors.row(id)).should("exist");
866
+ return this;
867
+ }
868
+ /**
869
+ * Assert row does not exist
870
+ */
871
+ assertRowNotExists(id) {
872
+ cy.get(this.selectors.row(id)).should("not.exist");
873
+ return this;
874
+ }
875
+ /**
876
+ * Assert selection count
877
+ */
878
+ assertSelectionCount(count) {
879
+ cy.get(this.selectors.selectionCount).should("contain.text", count.toString());
880
+ return this;
881
+ }
882
+ /**
883
+ * Assert bulk bar is visible
884
+ */
885
+ assertBulkBarVisible() {
886
+ cy.get(this.selectors.bulkBar).should("be.visible");
887
+ return this;
888
+ }
889
+ /**
890
+ * Assert bulk bar is hidden
891
+ */
892
+ assertBulkBarHidden() {
893
+ cy.get(this.selectors.bulkBar).should("not.be.visible");
894
+ return this;
895
+ }
896
+ };
897
+
898
+ export { ApiInterceptor, BasePOMCore, DashboardEntityPOMCore, createAriaLabel, createCyId, createPriorityAttr, createStateAttr, createTestId, createTestingProps, keyboardHelpers, sel };
899
+ //# sourceMappingURL=index.js.map
900
+ //# sourceMappingURL=index.js.map