@rickcedwhat/playwright-smart-table 6.20.0 → 6.20.1-next.1d11eb1

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.
@@ -1,13 +1,32 @@
1
1
  /**
2
2
  * @deprecated Use `presets` instead. Plugins will be removed in v7.0.0.
3
- * Presets for specific table/grid libraries. Each plugin exposes:
4
- * - Plugins.X — full preset (selectors + headerTransformer if any + strategies). Spread: useTable(loc, { ...Presets.MUI, maxPages: 5 }).
5
- * - Plugins.X.Strategies — strategies only. Use with your own selectors: useTable(loc, { rowSelector: '...', strategies: Presets.MUI.Strategies }).
3
+ *
4
+ * Compatibility shim over official presets:
5
+ * - `Plugins.MUI` → `presets.muiDataGrid` (**DataGrid only** — not `presets.muiTable`)
6
+ * - `Plugins.RDG` → `presets.rdg`
7
+ * - `Plugins.Glide` → `presets.glide`
8
+ *
9
+ * Prefer: `useTable(loc, { ...presets.muiDataGrid, maxPages: 5 })`.
10
+ * Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.muiDataGrid.strategies })`.
6
11
  */
7
12
  export declare const Plugins: {
8
13
  /** @deprecated Use `presets.rdg` */
9
14
  RDG: {
10
- Strategies: import("../types").TableStrategies | undefined;
15
+ Strategies: {
16
+ header: (context: import("..").TableContext) => Promise<string[]>;
17
+ getCellLocator: ({ row, columnIndex }: {
18
+ row: import("@playwright/test").Locator;
19
+ columnIndex: number;
20
+ }) => import("@playwright/test").Locator;
21
+ navigation: {
22
+ goRight: ({ root }: import("..").StrategyContext) => Promise<void>;
23
+ goLeft: ({ root }: import("..").StrategyContext) => Promise<void>;
24
+ goDown: ({ root }: import("..").StrategyContext) => Promise<void>;
25
+ goUp: ({ root }: import("..").StrategyContext) => Promise<void>;
26
+ goHome: ({ root }: import("..").StrategyContext) => Promise<void>;
27
+ };
28
+ pagination: import("..").PaginationPrimitives;
29
+ };
11
30
  headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
12
31
  rowSelector?: string | undefined;
13
32
  cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
@@ -22,14 +41,33 @@ export declare const Plugins: {
22
41
  autoScroll?: boolean | undefined;
23
42
  debug?: import("..").DebugConfig | undefined;
24
43
  onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
25
- strategies?: import("../types").TableStrategies | undefined;
44
+ strategies?: import("..").TableStrategies | undefined;
26
45
  columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
27
46
  syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
28
47
  emptyState?: import("@playwright/test").Locator | undefined;
29
48
  };
30
49
  /** @deprecated Use `presets.glide` */
31
50
  Glide: {
32
- Strategies: import("../types").TableStrategies | undefined;
51
+ Strategies: {
52
+ fillSimple: import("..").FillStrategy;
53
+ fill: import("..").FillStrategy;
54
+ pagination: import("..").PaginationPrimitives;
55
+ header: (context: import("..").StrategyContext, options?: {
56
+ limit?: number;
57
+ selector?: string;
58
+ scrollAmount?: number;
59
+ }) => Promise<string[]>;
60
+ viewport: import("..").ViewportStrategy;
61
+ loading: {
62
+ isHeaderLoading: () => Promise<boolean>;
63
+ };
64
+ getCellLocator: ({ row, columnIndex }: any) => any;
65
+ getActiveCell: ({ page }: any) => Promise<{
66
+ rowIndex: number;
67
+ columnIndex: number;
68
+ locator: any;
69
+ } | null>;
70
+ };
33
71
  headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
34
72
  rowSelector?: string | undefined;
35
73
  cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
@@ -44,14 +82,17 @@ export declare const Plugins: {
44
82
  autoScroll?: boolean | undefined;
45
83
  debug?: import("..").DebugConfig | undefined;
46
84
  onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
47
- strategies?: import("../types").TableStrategies | undefined;
85
+ strategies?: import("..").TableStrategies | undefined;
48
86
  columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
49
87
  syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
50
88
  emptyState?: import("@playwright/test").Locator | undefined;
51
89
  };
52
- /** @deprecated Use `presets.muiDataGrid` */
90
+ /**
91
+ * @deprecated Use `presets.muiDataGrid`.
92
+ * DataGrid only — for MUI `<Table>` use `presets.muiTable`.
93
+ */
53
94
  MUI: {
54
- Strategies: import("../types").TableStrategies | undefined;
95
+ Strategies: import("..").TableStrategies | undefined;
55
96
  headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
56
97
  rowSelector?: string | undefined;
57
98
  cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
@@ -66,7 +107,7 @@ export declare const Plugins: {
66
107
  autoScroll?: boolean | undefined;
67
108
  debug?: import("..").DebugConfig | undefined;
68
109
  onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
69
- strategies?: import("../types").TableStrategies | undefined;
110
+ strategies?: import("..").TableStrategies | undefined;
70
111
  columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
71
112
  syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
72
113
  emptyState?: import("@playwright/test").Locator | undefined;
@@ -4,15 +4,23 @@ exports.Plugins = void 0;
4
4
  const presets_1 = require("../presets");
5
5
  /**
6
6
  * @deprecated Use `presets` instead. Plugins will be removed in v7.0.0.
7
- * Presets for specific table/grid libraries. Each plugin exposes:
8
- * - Plugins.X — full preset (selectors + headerTransformer if any + strategies). Spread: useTable(loc, { ...Presets.MUI, maxPages: 5 }).
9
- * - Plugins.X.Strategies — strategies only. Use with your own selectors: useTable(loc, { rowSelector: '...', strategies: Presets.MUI.Strategies }).
7
+ *
8
+ * Compatibility shim over official presets:
9
+ * - `Plugins.MUI` → `presets.muiDataGrid` (**DataGrid only** — not `presets.muiTable`)
10
+ * - `Plugins.RDG` → `presets.rdg`
11
+ * - `Plugins.Glide` → `presets.glide`
12
+ *
13
+ * Prefer: `useTable(loc, { ...presets.muiDataGrid, maxPages: 5 })`.
14
+ * Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.muiDataGrid.strategies })`.
10
15
  */
11
16
  exports.Plugins = {
12
17
  /** @deprecated Use `presets.rdg` */
13
- RDG: Object.assign(Object.assign({}, presets_1.rdg), { Strategies: presets_1.rdg.strategies }),
18
+ RDG: Object.assign(Object.assign({}, presets_1.rdg), { Strategies: presets_1.rdg.Strategies }),
14
19
  /** @deprecated Use `presets.glide` */
15
- Glide: Object.assign(Object.assign({}, presets_1.glide), { Strategies: presets_1.glide.strategies }),
16
- /** @deprecated Use `presets.muiDataGrid` */
20
+ Glide: Object.assign(Object.assign({}, presets_1.glide), { Strategies: presets_1.glide.Strategies }),
21
+ /**
22
+ * @deprecated Use `presets.muiDataGrid`.
23
+ * DataGrid only — for MUI `<Table>` use `presets.muiTable`.
24
+ */
17
25
  MUI: Object.assign(Object.assign({}, presets_1.muiDataGrid), { Strategies: presets_1.muiDataGrid.strategies }),
18
26
  };
@@ -12,7 +12,7 @@ export declare const GlideStrategies: {
12
12
  selector?: string;
13
13
  scrollAmount?: number;
14
14
  }) => Promise<string[]>;
15
- viewport: import("../../types").ViewportStrategy;
15
+ viewport: import("../..").ViewportStrategy;
16
16
  loading: {
17
17
  isHeaderLoading: () => Promise<boolean>;
18
18
  };
@@ -123,8 +123,8 @@ function createGlide(options = {}) {
123
123
  }
124
124
  /**
125
125
  * Full preset for Glide Data Grid (selectors + default strategies only).
126
- * Spread: useTable(loc, { ...Plugins.Glide, maxPages: 5 }).
127
- * Strategies only (including fillSimple): useTable(loc, { rowSelector: '...', strategies: Plugins.Glide.Strategies }).
126
+ * Spread: `useTable(loc, { ...presets.glide, maxPages: 5 })`.
127
+ * Strategies only (including fillSimple): `useTable(loc, { rowSelector: '...', strategies: presets.glide.Strategies })`.
128
128
  * For a non-64-column grid, use `createGlide({ columnCount })` instead.
129
129
  */
130
130
  const GlidePreset = {
@@ -1,6 +1,6 @@
1
1
  import type { Locator } from '@playwright/test';
2
2
  import { TableContext, TableConfig, ViewportStrategy, StrategyContext } from '../types';
3
- /** Full strategies for React Data Grid. Use when you want to supply your own selectors: strategies: Plugins.RDG.Strategies */
3
+ /** Full strategies for React Data Grid. Use when you want to supply your own selectors: `strategies: presets.rdg.Strategies` */
4
4
  export declare const RDGStrategies: {
5
5
  header: (context: TableContext) => Promise<string[]>;
6
6
  getCellLocator: ({ row, columnIndex }: {
@@ -92,20 +92,20 @@ const rdgNavigation = {
92
92
  });
93
93
  }
94
94
  };
95
- /** Default strategies for the RDG preset (used when you spread Plugins.RDG). */
95
+ /** Default strategies for the RDG preset (used when you spread `presets.rdg`). */
96
96
  const RDGDefaultStrategies = {
97
97
  header: scrollRightHeaderRDG,
98
98
  getCellLocator: rdgGetCellLocator,
99
99
  navigation: rdgNavigation,
100
100
  pagination: rdgPaginationStrategy
101
101
  };
102
- /** Full strategies for React Data Grid. Use when you want to supply your own selectors: strategies: Plugins.RDG.Strategies */
102
+ /** Full strategies for React Data Grid. Use when you want to supply your own selectors: `strategies: presets.rdg.Strategies` */
103
103
  // fallow-ignore-next-line unused-export
104
104
  exports.RDGStrategies = RDGDefaultStrategies;
105
105
  /**
106
106
  * Full preset for React Data Grid (selectors + default strategies).
107
- * Spread: useTable(loc, { ...Plugins.RDG, maxPages: 5 }).
108
- * Strategies only: useTable(loc, { rowSelector: '...', strategies: Plugins.RDG.Strategies }).
107
+ * Spread: `useTable(loc, { ...presets.rdg, maxPages: 5 })`.
108
+ * Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.rdg.Strategies })`.
109
109
  */
110
110
  const RDGPreset = {
111
111
  rowSelector: '[role="row"].rdg-row',
package/dist/smartRow.js CHANGED
@@ -196,6 +196,8 @@ const _navigateToCell = async (params) => {
196
196
  currCol = ac.columnIndex;
197
197
  }
198
198
  }
199
+ // Column-0 snap is owned by the navigation primitive (scrollLeft=0, Home, etc.).
200
+ // Core must not special-case canvas/Home — that was pre-viewport Glide glue.
199
201
  if (index === 0 && nav.snapFirstColumnIntoView) {
200
202
  (0, debugUtils_1.logDebug)(config, 'verbose', '_navigateToCell: snapFirstColumnIntoView for column index 0');
201
203
  await nav.snapFirstColumnIntoView(context);
@@ -206,27 +208,6 @@ const _navigateToCell = async (params) => {
206
208
  currCol = ac.columnIndex;
207
209
  }
208
210
  }
209
- // Home moves a11y focus within the current row to column 0. If focus row !== target row
210
- // (e.g. still on previous row), Home jumps to grid origin and breaks `tr.nth(k)` reads.
211
- if (typeof rowIndex === 'number' && currRow === rowIndex) {
212
- await rootLocator.evaluate((el) => {
213
- var _a;
214
- const canvas = el.closest('canvas') || ((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('canvas'));
215
- if (canvas instanceof HTMLCanvasElement) {
216
- canvas.tabIndex = 0;
217
- canvas.focus();
218
- }
219
- });
220
- await page.keyboard.press('Home');
221
- await page.waitForTimeout(120);
222
- if (config.strategies.getActiveCell) {
223
- const ac = await config.strategies.getActiveCell({ config, root: rootLocator, page, resolve });
224
- if (ac) {
225
- currRow = ac.rowIndex;
226
- currCol = ac.columnIndex;
227
- }
228
- }
229
- }
230
211
  }
231
212
  let cDiff = index - currCol;
232
213
  if (Math.abs(cDiff) > 12 && nav.seekColumnIndex) {
@@ -253,8 +234,8 @@ const _navigateToCell = async (params) => {
253
234
  const settleMs = (Number.isFinite(nav.settleMs) && nav.settleMs >= 0) ? nav.settleMs : computed;
254
235
  await page.waitForTimeout(settleMs);
255
236
  }
256
- // Wait for active cell to match target: poll getActiveCell or fallback to fixed delay
257
- // This is the "Midas Touch" buffer needed for Glide's async accessibility updates.
237
+ // After horizontal steps, poll until getActiveCell / DOM presence confirms the target
238
+ // (a11y trees and virtual mounts often lag the scroll). Tune via navigation.maxWaitMs.
258
239
  const pollIntervalMs = 10;
259
240
  const computedMax = Math.min(6000, 250 + horizontalSteps * 25);
260
241
  const maxWaitMs = (Number.isFinite(nav.maxWaitMs) && nav.maxWaitMs >= 0) ? nav.maxWaitMs : computedMax;
@@ -424,7 +405,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
424
405
  return (await cell.innerText()).trim();
425
406
  };
426
407
  smart.toJSON = async (options) => {
427
- var _a, _b, _c, _d, _e, _f, _g, _h;
408
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
428
409
  // Atomic mode: snapshot the row in a single evaluate, then apply column overrides
429
410
  // against a frozen reconstruction. Zero inter-column stagger — even in-place React
430
411
  // re-renders can't affect it because overrides read from a detached clone.
@@ -434,10 +415,84 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
434
415
  throw new Error('[SmartTable] toJSON({ atomic: true }) requires cellSelector to be a CSS string, not a function.');
435
416
  }
436
417
  const page = rootLocator.page();
418
+ // Re-pin: the row locator is a positional nth-child from the iteration loop.
419
+ // On recycling virtualizers, elements can shift between SmartRow creation and
420
+ // this clone — the nth element may now show a different row's content.
421
+ // Verify via resolveRowIndex and rescan if drifted.
422
+ let cloneTarget = rowLocator;
423
+ const resolveRI = config.strategies.resolveRowIndex;
424
+ const isSelfHealing = smart._selfHealing === true;
425
+ const rescanForRow = resolveRI && typeof rowIndex === 'number' && !isSelfHealing
426
+ ? async () => {
427
+ const rows = await resolve(config.rowSelector, rootLocator).all();
428
+ for (const r of rows) {
429
+ try {
430
+ const rResult = await resolveRI(r);
431
+ if (rResult !== undefined && (0, rowResolution_1.normalizeRowIndexResult)(rResult).index === rowIndex)
432
+ return r;
433
+ }
434
+ catch ( /* element may have detached during scan */_a) { /* element may have detached during scan */ }
435
+ }
436
+ return null;
437
+ }
438
+ : null;
439
+ if (rescanForRow) {
440
+ let drifted = false;
441
+ try {
442
+ const curResult = await resolveRI(rowLocator);
443
+ const curIndex = curResult !== undefined
444
+ ? (0, rowResolution_1.normalizeRowIndexResult)(curResult).index
445
+ : undefined;
446
+ drifted = curIndex !== rowIndex;
447
+ }
448
+ catch (_k) {
449
+ drifted = true;
450
+ }
451
+ if (drifted) {
452
+ const inBatch = smart._inBatch === true || !!smart._barrier;
453
+ let recovered = await rescanForRow();
454
+ if (!recovered && !inBatch && ((_a = config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.scrollToRow)) {
455
+ await config.strategies.viewport.scrollToRow({ root: rootLocator, config, page, resolve }, rowIndex);
456
+ const deadline = Date.now() + 500;
457
+ while (!recovered && Date.now() < deadline) {
458
+ recovered = await rescanForRow();
459
+ if (!recovered)
460
+ await page.waitForTimeout(50);
461
+ }
462
+ }
463
+ if (!recovered) {
464
+ throw new Error(`[SmartTable] toJSON({ atomic: true }): row ${rowIndex} recycled out of the DOM and could not be recovered. ` +
465
+ (inBatch
466
+ ? `(During a map/forEach batch, scroll-back recovery is disabled to avoid disrupting sibling rows.)`
467
+ : `Ensure a viewport.scrollToRow strategy can restore the row.`));
468
+ }
469
+ cloneTarget = recovered;
470
+ }
471
+ }
472
+ // Wait for async content renders to settle before cloning.
473
+ if (config.strategies.contentReady) {
474
+ await config.strategies.contentReady(cloneTarget, page);
475
+ // Revalidate: the element may have recycled during the await.
476
+ if (rescanForRow) {
477
+ let postIdx;
478
+ try {
479
+ const r = await resolveRI(cloneTarget);
480
+ postIdx = r !== undefined ? (0, rowResolution_1.normalizeRowIndexResult)(r).index : undefined;
481
+ }
482
+ catch (_l) {
483
+ postIdx = undefined;
484
+ }
485
+ if (postIdx !== rowIndex) {
486
+ const reFound = await rescanForRow();
487
+ if (reFound)
488
+ cloneTarget = reFound;
489
+ }
490
+ }
491
+ }
437
492
  // Step 1: clone row in browser memory (NOT in the DOM), extract text + row HTML.
438
493
  // Uses textContent (not innerText) because the clone is detached — innerText
439
494
  // falls back to textContent on detached nodes per HTML spec, so be explicit.
440
- const cloneHandle = await rowLocator.evaluateHandle(el => el.cloneNode(true));
495
+ const cloneHandle = await cloneTarget.evaluateHandle(el => el.cloneNode(true));
441
496
  let snapshot;
442
497
  try {
443
498
  snapshot = await cloneHandle.evaluate((row, sel) => {
@@ -491,7 +546,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
491
546
  await materializeRow();
492
547
  const result = {};
493
548
  for (const [col, idx] of columnsToProcess) {
494
- const columnOverride = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[col];
549
+ const columnOverride = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[col];
495
550
  const mapper = columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read;
496
551
  if (mapper) {
497
552
  const cell = page.locator(`#${snapId}`).locator(cellSel).nth(idx);
@@ -508,7 +563,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
508
563
  result[col] = snapshot.cells[idx].text;
509
564
  }
510
565
  }
511
- for (const [name, def] of Object.entries((_b = config.syntheticColumns) !== null && _b !== void 0 ? _b : {})) {
566
+ for (const [name, def] of Object.entries((_c = config.syntheticColumns) !== null && _c !== void 0 ? _c : {})) {
512
567
  if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(name))
513
568
  continue;
514
569
  const snapshotRow = Object.create(smart);
@@ -607,7 +662,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
607
662
  // Re-pin to the correct logical row before reading this column (#366).
608
663
  const stableRow = await resolveStableRow();
609
664
  // Check if we have a column override for this column
610
- const columnOverride = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[col];
665
+ const columnOverride = (_d = config.columnOverrides) === null || _d === void 0 ? void 0 : _d[col];
611
666
  const mapper = columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read;
612
667
  // --- Navigation Logic Start ---
613
668
  const cell = config.strategies.getCellLocator
@@ -654,13 +709,13 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
654
709
  });
655
710
  }
656
711
  // Cell loading wait (if configured)
657
- const isCellLoading = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.isCellLoading;
658
- const rawCellTimeout = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.cellLoadingTimeout;
712
+ const isCellLoading = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.isCellLoading;
713
+ const rawCellTimeout = (_f = config.strategies.loading) === null || _f === void 0 ? void 0 : _f.cellLoadingTimeout;
659
714
  // undefined = unset (read as-is immediately); 0 = no wait (immediate check); >0 = wait up to N ms
660
715
  const cellLoadingTimeout = rawCellTimeout !== undefined && Number.isFinite(rawCellTimeout) && rawCellTimeout >= 0
661
716
  ? rawCellTimeout
662
717
  : undefined;
663
- const onCellLoadingTimeout = (_g = (_f = config.strategies.loading) === null || _f === void 0 ? void 0 : _f.onCellLoadingTimeout) !== null && _g !== void 0 ? _g : 'read-as-is';
718
+ const onCellLoadingTimeout = (_h = (_g = config.strategies.loading) === null || _g === void 0 ? void 0 : _g.onCellLoadingTimeout) !== null && _h !== void 0 ? _h : 'read-as-is';
664
719
  if (isCellLoading && await isCellLoading(targetCell, col, smart)) {
665
720
  if (cellLoadingTimeout !== undefined) {
666
721
  (0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — waiting up to ${cellLoadingTimeout}ms`);
@@ -699,7 +754,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
699
754
  result[col] = (text || '').trim();
700
755
  }
701
756
  }
702
- for (const [name, def] of Object.entries((_h = config.syntheticColumns) !== null && _h !== void 0 ? _h : {})) {
757
+ for (const [name, def] of Object.entries((_j = config.syntheticColumns) !== null && _j !== void 0 ? _j : {})) {
703
758
  if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(name))
704
759
  continue;
705
760
  const snapshotRow = Object.create(smart);
@@ -12,7 +12,9 @@ export interface NavigationPrimitives {
12
12
  goHome?: (context: StrategyContext) => Promise<void>;
13
13
  /**
14
14
  * After vertical moves, run before horizontal steps when the target column index is 0.
15
- * Use for horizontally virtualized a11y tables (e.g. Glide) so `td[aria-colindex="1"]` exists again.
15
+ * Use for horizontally virtualized tables so the first column exists in the DOM again
16
+ * (e.g. set scrollLeft = 0). If the table needs keyboard `Home` or canvas focus, put that
17
+ * here — the core orchestrator does not special-case those behaviors.
16
18
  * Do not reset vertical scroll here — RDG-style `goHome` is often unsuitable.
17
19
  */
18
20
  snapFirstColumnIntoView?: (context: StrategyContext) => Promise<void>;
@@ -33,7 +35,8 @@ export interface NavigationPrimitives {
33
35
  maxWaitMs?: number;
34
36
  }
35
37
  /**
36
- * @deprecated Use NavigationPrimitives instead. This will be removed in a future version.
38
+ * @deprecated Use NavigationPrimitives (`strategies.navigation`) instead. Removed from
39
+ * the public `Strategies` namespace in #428; this type remains for transitional typings.
37
40
  * Defines the contract for a cell navigation strategy.
38
41
  */
39
42
  export type CellNavigationStrategy = (context: StrategyContext & {
@@ -46,10 +49,3 @@ export type CellNavigationStrategy = (context: StrategyContext & {
46
49
  locator: Locator;
47
50
  } | null;
48
51
  }) => Promise<void>;
49
- export declare const CellNavigationStrategies: {
50
- /**
51
- * Default strategy: Assumes column is accessible or standard scrolling works.
52
- * No specific action taken other than what Playwright's default locator handling does.
53
- */
54
- default: () => Promise<void>;
55
- };
@@ -1,12 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CellNavigationStrategies = void 0;
4
- exports.CellNavigationStrategies = {
5
- /**
6
- * Default strategy: Assumes column is accessible or standard scrolling works.
7
- * No specific action taken other than what Playwright's default locator handling does.
8
- */
9
- default: async () => {
10
- // No-op
11
- }
12
- };
@@ -0,0 +1,20 @@
1
+ import type { ContentReadyStrategy } from '../types';
2
+ export type { ContentReadyStrategy };
3
+ export declare const ContentReadyStrategies: {
4
+ /**
5
+ * Polls the row's text content until two consecutive reads match.
6
+ */
7
+ textStable: (options?: {
8
+ timeout?: number;
9
+ interval?: number;
10
+ }) => ContentReadyStrategy;
11
+ /**
12
+ * Uses MutationObserver to wait for DOM mutations on the row's subtree
13
+ * to settle. Resolves when no mutations occur for `quietPeriod` ms, or
14
+ * when `timeout` expires.
15
+ */
16
+ mutationSettled: (options?: {
17
+ timeout?: number;
18
+ quietPeriod?: number;
19
+ }) => ContentReadyStrategy;
20
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentReadyStrategies = void 0;
4
+ exports.ContentReadyStrategies = {
5
+ /**
6
+ * Polls the row's text content until two consecutive reads match.
7
+ */
8
+ textStable: (options = {}) => {
9
+ var _a, _b;
10
+ const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 500;
11
+ const interval = (_b = options.interval) !== null && _b !== void 0 ? _b : 50;
12
+ return async (row, page) => {
13
+ const deadline = Date.now() + timeout;
14
+ const remaining = () => Math.max(0, deadline - Date.now());
15
+ let prev = await row.innerText({ timeout: remaining() || timeout });
16
+ while (remaining() > 0) {
17
+ await page.waitForTimeout(Math.min(interval, remaining()));
18
+ if (remaining() <= 0)
19
+ break;
20
+ const cur = await row.innerText({ timeout: remaining() });
21
+ if (cur === prev)
22
+ return;
23
+ prev = cur;
24
+ }
25
+ };
26
+ },
27
+ /**
28
+ * Uses MutationObserver to wait for DOM mutations on the row's subtree
29
+ * to settle. Resolves when no mutations occur for `quietPeriod` ms, or
30
+ * when `timeout` expires.
31
+ */
32
+ mutationSettled: (options = {}) => {
33
+ var _a, _b;
34
+ const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 500;
35
+ const quietPeriod = (_b = options.quietPeriod) !== null && _b !== void 0 ? _b : 100;
36
+ return async (row) => {
37
+ await row.evaluate((el, opts) => {
38
+ return new Promise((resolve) => {
39
+ let quietTimer;
40
+ let deadlineTimer;
41
+ const done = () => {
42
+ clearTimeout(quietTimer);
43
+ clearTimeout(deadlineTimer);
44
+ observer.disconnect();
45
+ resolve();
46
+ };
47
+ const observer = new MutationObserver(() => {
48
+ clearTimeout(quietTimer);
49
+ quietTimer = setTimeout(done, opts.quietPeriod);
50
+ });
51
+ observer.observe(el, {
52
+ childList: true,
53
+ subtree: true,
54
+ characterData: true,
55
+ });
56
+ quietTimer = setTimeout(done, opts.quietPeriod);
57
+ deadlineTimer = setTimeout(done, opts.timeout);
58
+ });
59
+ }, { timeout, quietPeriod });
60
+ };
61
+ },
62
+ };
@@ -2,12 +2,8 @@ import type { FilterStrategy } from '../types';
2
2
  /**
3
3
  * Example filter strategies.
4
4
  * - default: small convenience wrapper that mirrors the engine's default behavior.
5
- * - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
6
5
  */
7
6
  export declare const FilterStrategies: {
8
7
  default: FilterStrategy;
9
- spy: (calledRef?: {
10
- called?: boolean;
11
- }) => FilterStrategy;
12
8
  };
13
9
  export type { FilterStrategy } from '../types';
@@ -4,7 +4,6 @@ exports.FilterStrategies = void 0;
4
4
  /**
5
5
  * Example filter strategies.
6
6
  * - default: small convenience wrapper that mirrors the engine's default behavior.
7
- * - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
8
7
  */
9
8
  exports.FilterStrategies = {
10
9
  default: {
@@ -20,19 +19,4 @@ exports.FilterStrategies = {
20
19
  return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
21
20
  }
22
21
  },
23
- spy: (calledRef = {}) => ({
24
- apply({ rows, filter, colIndex, tableContext }) {
25
- calledRef.called = true;
26
- // Delegate to default behaviour for actual filtering
27
- const page = tableContext.page;
28
- const resolve = tableContext.resolve;
29
- const cellTemplate = resolve(tableContext.config.cellSelector, page);
30
- const targetCell = cellTemplate.nth(colIndex);
31
- if (typeof filter.value === 'function') {
32
- return rows.filter({ has: filter.value(targetCell) });
33
- }
34
- const textVal = typeof filter.value === 'number' ? String(filter.value) : filter.value;
35
- return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
36
- }
37
- }),
38
22
  };
@@ -3,12 +3,19 @@ export * from './sorting';
3
3
  export * from './columns';
4
4
  export * from './headers';
5
5
  export * from './fill';
6
- export * from './resolution';
6
+ export type { ColumnResolutionStrategy } from './resolution';
7
7
  export * from './dedupe';
8
8
  export * from './loading';
9
9
  export * from './stabilization';
10
10
  export * from './filter';
11
11
  export * from './viewport';
12
+ export * from './contentReady';
13
+ /**
14
+ * Built-in strategy factories.
15
+ * @note `CellNavigation` / `Resolution` removed from this namespace in #428 (dead/no-op).
16
+ * Use `strategies.navigation` (NavigationPrimitives) and header maps instead.
17
+ * `Filter.spy` was test-only and is no longer shipped.
18
+ */
12
19
  export declare const Strategies: {
13
20
  Pagination: {
14
21
  click: (selectors: {
@@ -26,21 +33,18 @@ export declare const Strategies: {
26
33
  detectCurrentPage?: (root: import("@playwright/test").Locator) => number | Promise<number>;
27
34
  stabilization?: import("./stabilization").StabilizationStrategy;
28
35
  timeout?: number;
29
- }) => import("../types").PaginationStrategy;
36
+ }) => import("..").PaginationStrategy;
30
37
  infiniteScroll: (options?: {
31
38
  action?: "scroll" | "js-scroll";
32
39
  scrollTarget?: import("..").Selector;
33
40
  scrollAmount?: number;
34
41
  stabilization?: import("./stabilization").StabilizationStrategy;
35
42
  timeout?: number;
36
- }) => import("../types").PaginationStrategy;
43
+ }) => import("..").PaginationStrategy;
37
44
  };
38
45
  Sorting: {
39
46
  AriaSort: () => import("..").SortingStrategy;
40
47
  };
41
- CellNavigation: {
42
- default: () => Promise<void>;
43
- };
44
48
  Header: {
45
49
  visible: ({ config, resolve, root }: import("..").StrategyContext) => Promise<string[]>;
46
50
  horizontalScroll: (options?: {
@@ -50,13 +54,10 @@ export declare const Strategies: {
50
54
  }) => import("./headers").HeaderStrategy;
51
55
  };
52
56
  Fill: {
53
- default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("../types").FillStrategy>[0]) => Promise<void>;
54
- };
55
- Resolution: {
56
- default: import("./resolution").ColumnResolutionStrategy;
57
+ default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("..").FillStrategy>[0]) => Promise<void>;
57
58
  };
58
59
  Dedupe: {
59
- byTopPosition: (tolerance?: number) => import("../types").DedupeStrategy;
60
+ byTopPosition: (tolerance?: number) => import("..").DedupeStrategy;
60
61
  };
61
62
  Loading: {
62
63
  Table: {
@@ -93,11 +94,18 @@ export declare const Strategies: {
93
94
  };
94
95
  Filter: {
95
96
  default: import("./filter").FilterStrategy;
96
- spy: (calledRef?: {
97
- called?: boolean;
98
- }) => import("./filter").FilterStrategy;
99
97
  };
100
98
  Viewport: {
101
- dataAttribute: (options?: import("./viewport").DataAttributeViewportOptions) => import("../types").ViewportStrategy;
99
+ dataAttribute: (options?: import("./viewport").DataAttributeViewportOptions) => import("..").ViewportStrategy;
100
+ };
101
+ ContentReady: {
102
+ textStable: (options?: {
103
+ timeout?: number;
104
+ interval?: number;
105
+ }) => import("./contentReady").ContentReadyStrategy;
106
+ mutationSettled: (options?: {
107
+ timeout?: number;
108
+ quietPeriod?: number;
109
+ }) => import("./contentReady").ContentReadyStrategy;
102
110
  };
103
111
  };