@rickcedwhat/playwright-smart-table 6.1.1 → 6.3.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/dist/useTable.js CHANGED
@@ -23,11 +23,12 @@ const columns_1 = require("./strategies/columns");
23
23
  Object.defineProperty(exports, "CellNavigationStrategies", { enumerable: true, get: function () { return columns_1.CellNavigationStrategies; } });
24
24
  const smartRow_1 = require("./smartRow");
25
25
  const filterEngine_1 = require("./filterEngine");
26
+ const tableMapper_1 = require("./engine/tableMapper");
27
+ const rowFinder_1 = require("./engine/rowFinder");
26
28
  const resolution_1 = require("./strategies/resolution");
27
29
  Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get: function () { return resolution_1.ResolutionStrategies; } });
28
30
  const strategies_1 = require("./strategies");
29
31
  Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
30
- const validation_1 = require("./strategies/validation");
31
32
  const debugUtils_1 = require("./utils/debugUtils");
32
33
  const smartRowArray_1 = require("./utils/smartRowArray");
33
34
  /**
@@ -43,6 +44,9 @@ const useTable = (rootLocator, configOptions = {}) => {
43
44
  header: headers_1.HeaderStrategies.visible,
44
45
  cellNavigation: columns_1.CellNavigationStrategies.default,
45
46
  pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
47
+ loading: {
48
+ isHeaderLoading: loading_1.LoadingStrategies.Headers.stable(200)
49
+ }
46
50
  };
47
51
  const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text }) => text, autoScroll: true, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
48
52
  const resolve = (item, parent) => {
@@ -53,16 +57,13 @@ const useTable = (rootLocator, configOptions = {}) => {
53
57
  return item;
54
58
  };
55
59
  // Internal State
56
- let _headerMap = null;
57
60
  let _hasPaginated = false;
58
- let _isInitialized = false;
59
61
  // Helpers
60
62
  const log = (msg) => {
61
- (0, debugUtils_1.logDebug)(config, 'verbose', msg); // Legacy(`🔎 [SmartTable Debug] ${msg}`);
63
+ (0, debugUtils_1.logDebug)(config, 'verbose', msg);
62
64
  };
63
65
  const _createColumnError = (colName, map, context) => {
64
66
  const availableColumns = Array.from(map.keys());
65
- // Use Suggestion Logic from ResolutionStrategy (if we had a fuzzy one, for now manual suggest)
66
67
  const lowerCol = colName.toLowerCase();
67
68
  const suggestions = availableColumns.filter(col => col.toLowerCase().includes(lowerCol) ||
68
69
  lowerCol.includes(col.toLowerCase()) ||
@@ -80,138 +81,16 @@ const useTable = (rootLocator, configOptions = {}) => {
80
81
  const contextMsg = context ? ` (${context})` : '';
81
82
  return new Error(`Column "${colName}" not found${contextMsg}${suggestion}`);
82
83
  };
83
- const _getMap = (timeout) => __awaiter(void 0, void 0, void 0, function* () {
84
- if (_headerMap)
85
- return _headerMap;
86
- log('Mapping headers...');
87
- const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
88
- if (config.autoScroll) {
89
- try {
90
- yield rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
91
- }
92
- catch (e) { }
93
- }
94
- const headerLoc = resolve(config.headerSelector, rootLocator);
95
- try {
96
- yield headerLoc.first().waitFor({ state: 'visible', timeout: headerTimeout });
97
- }
98
- catch (e) { /* Ignore hydration */ }
99
- const strategy = config.strategies.header || headers_1.HeaderStrategies.visible;
100
- const context = {
101
- root: rootLocator,
102
- config: config,
103
- page: rootLocator.page(),
104
- resolve: resolve
105
- };
106
- const rawHeaders = yield strategy(context);
107
- const seenHeaders = new Set();
108
- const entries = [];
109
- for (let i = 0; i < rawHeaders.length; i++) {
110
- let text = rawHeaders[i].trim() || `__col_${i}`;
111
- if (config.headerTransformer) {
112
- text = yield config.headerTransformer({
113
- text,
114
- index: i,
115
- locator: rootLocator.locator(config.headerSelector).nth(i),
116
- seenHeaders
117
- });
118
- }
119
- entries.push([text, i]);
120
- seenHeaders.add(text);
121
- }
122
- // Validation: Check for empty table
123
- if (entries.length === 0) {
124
- throw new Error(`Initialization Error: No columns found using selector "${config.headerSelector}". Check your selector or ensure the table is visible.`);
125
- }
126
- // Validation: Check for duplicates
127
- const seen = new Set();
128
- const duplicates = new Set();
129
- for (const [name] of entries) {
130
- if (seen.has(name)) {
131
- duplicates.add(name);
132
- }
133
- seen.add(name);
134
- }
135
- if (duplicates.size > 0) {
136
- const dupList = Array.from(duplicates).map(d => `"${d}"`).join(', ');
137
- throw new Error(`Initialization Error: Duplicate column names found: ${dupList}. Use 'headerTransformer' to rename duplicate columns.`);
138
- }
139
- _headerMap = new Map(entries);
140
- log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
141
- return _headerMap;
142
- });
84
+ // Engines
85
+ const filterEngine = new filterEngine_1.FilterEngine(config, resolve);
86
+ const tableMapper = new tableMapper_1.TableMapper(rootLocator, config, resolve);
143
87
  // Placeholder for the final table object
144
88
  let finalTable = null;
145
- const filterEngine = new filterEngine_1.FilterEngine(config, resolve);
146
89
  // Helper factory
147
90
  const _makeSmart = (rowLocator, map, rowIndex) => {
148
- // Use the wrapped SmartRow logic
149
91
  return (0, smartRow_1.createSmartRow)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable);
150
92
  };
151
- const _findRowLocator = (filters_1, ...args_1) => __awaiter(void 0, [filters_1, ...args_1], void 0, function* (filters, options = {}) {
152
- var _a, _b;
153
- const map = yield _getMap();
154
- const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
155
- let currentPage = 1;
156
- log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
157
- while (true) {
158
- // Check for table loading
159
- if ((_b = config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
160
- const isLoading = yield config.strategies.loading.isTableLoading({ root: rootLocator, config, page: rootLocator.page(), resolve });
161
- if (isLoading) {
162
- log('Table is loading... waiting');
163
- yield rootLocator.page().waitForTimeout(200);
164
- continue;
165
- }
166
- }
167
- const allRows = resolve(config.rowSelector, rootLocator);
168
- // Use FilterEngine
169
- const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
170
- const count = yield matchedRows.count();
171
- log(`Page ${currentPage}: Found ${count} matches.`);
172
- if (count > 1) {
173
- // Sample data logic (simplified for refactor, kept inline or moved to util if needed)
174
- const sampleData = [];
175
- try {
176
- const firstFewRows = yield matchedRows.all();
177
- const sampleCount = Math.min(firstFewRows.length, 3);
178
- for (let i = 0; i < sampleCount; i++) {
179
- const rowData = yield _makeSmart(firstFewRows[i], map).toJSON();
180
- sampleData.push(JSON.stringify(rowData));
181
- }
182
- }
183
- catch (e) { }
184
- const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
185
- throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${currentPage}. ` +
186
- `Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
187
- }
188
- if (count === 1)
189
- return matchedRows.first();
190
- if (currentPage < effectiveMaxPages) {
191
- log(`Page ${currentPage}: Not found. Attempting pagination...`);
192
- const context = {
193
- root: rootLocator,
194
- config: config,
195
- page: rootLocator.page(),
196
- resolve: resolve
197
- };
198
- const paginationResult = yield config.strategies.pagination(context);
199
- const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
200
- if (didLoadMore) {
201
- _hasPaginated = true;
202
- currentPage++;
203
- continue;
204
- }
205
- else {
206
- log(`Page ${currentPage}: Pagination failed (end of data).`);
207
- }
208
- }
209
- if (_hasPaginated) {
210
- console.warn(`⚠️ [SmartTable] Row not found. The table has been paginated (Current Page: ${currentPage}). You may need to call 'await table.reset()' if the target row is on a previous page.`);
211
- }
212
- return null;
213
- }
214
- });
93
+ const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart);
215
94
  const _getCleanHtml = (loc) => __awaiter(void 0, void 0, void 0, function* () {
216
95
  return loc.evaluate((el) => {
217
96
  const clone = el.cloneNode(true);
@@ -247,28 +126,21 @@ const useTable = (rootLocator, configOptions = {}) => {
247
126
  console.log(finalPrompt);
248
127
  });
249
128
  const _ensureInitialized = () => __awaiter(void 0, void 0, void 0, function* () {
250
- if (!_isInitialized) {
251
- yield _getMap();
252
- _isInitialized = true;
253
- }
129
+ yield tableMapper.getMap();
254
130
  });
255
131
  const result = {
256
132
  init: (options) => __awaiter(void 0, void 0, void 0, function* () {
257
- if (_isInitialized && _headerMap)
133
+ if (tableMapper.isInitialized())
258
134
  return result;
259
135
  (0, debugUtils_1.warnIfDebugInCI)(config);
260
136
  (0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
261
- yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
262
- _isInitialized = true;
263
- if (_headerMap) {
264
- (0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${_headerMap.size} columns`, Array.from(_headerMap.keys()));
265
- // Trace event removed - redundant with debug logging
266
- }
137
+ const map = yield tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
138
+ (0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
267
139
  yield (0, debugUtils_1.debugDelay)(config, 'default');
268
140
  return result;
269
141
  }),
270
142
  scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
271
- const map = yield _getMap();
143
+ const map = yield tableMapper.getMap();
272
144
  const idx = map.get(columnName);
273
145
  if (idx === undefined)
274
146
  throw _createColumnError(columnName, map);
@@ -282,14 +154,14 @@ const useTable = (rootLocator, configOptions = {}) => {
282
154
  });
283
155
  }),
284
156
  getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
285
- yield _ensureInitialized();
286
- return Array.from(_headerMap.keys());
157
+ const map = yield tableMapper.getMap();
158
+ return Array.from(map.keys());
287
159
  }),
288
160
  getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
289
- yield _ensureInitialized();
290
- const idx = _headerMap.get(columnName);
161
+ const map = yield tableMapper.getMap();
162
+ const idx = map.get(columnName);
291
163
  if (idx === undefined)
292
- throw _createColumnError(columnName, _headerMap, 'header cell');
164
+ throw _createColumnError(columnName, map, 'header cell');
293
165
  return resolve(config.headerSelector, rootLocator).nth(idx);
294
166
  }),
295
167
  reset: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -297,22 +169,20 @@ const useTable = (rootLocator, configOptions = {}) => {
297
169
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
298
170
  yield config.onReset(context);
299
171
  _hasPaginated = false;
300
- _headerMap = null;
301
- _isInitialized = false;
172
+ tableMapper.clear();
302
173
  log("Table reset complete.");
303
174
  }),
304
175
  revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
305
176
  log("Revalidating table structure...");
306
- _headerMap = null; // Clear the map to force re-scanning
307
- yield _getMap(); // Re-scan headers
177
+ yield tableMapper.remapHeaders();
308
178
  log("Table revalidated.");
309
179
  }),
310
180
  getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
311
181
  var _a, _b;
312
- yield _ensureInitialized();
313
- const colIdx = _headerMap.get(column);
182
+ const map = yield tableMapper.getMap();
183
+ const colIdx = map.get(column);
314
184
  if (colIdx === undefined)
315
- throw _createColumnError(column, _headerMap);
185
+ throw _createColumnError(column, map);
316
186
  const mapper = (_a = options === null || options === void 0 ? void 0 : options.mapper) !== null && _a !== void 0 ? _a : ((c) => c.innerText());
317
187
  const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
318
188
  let currentPage = 1;
@@ -339,103 +209,35 @@ const useTable = (rootLocator, configOptions = {}) => {
339
209
  return results;
340
210
  }),
341
211
  getRow: (filters, options = { exact: false }) => {
342
- if (!_isInitialized || !_headerMap)
212
+ const map = tableMapper.getMapSync();
213
+ if (!map)
343
214
  throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
344
215
  const allRows = resolve(config.rowSelector, rootLocator);
345
- const matchedRows = filterEngine.applyFilters(allRows, filters, _headerMap, options.exact || false, rootLocator.page());
216
+ const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
346
217
  const rowLocator = matchedRows.first();
347
- return _makeSmart(rowLocator, _headerMap, 0); // fallback index 0
218
+ return _makeSmart(rowLocator, map, 0); // fallback index 0
348
219
  },
349
220
  getRowByIndex: (index, options = {}) => {
350
- if (!_isInitialized || !_headerMap)
221
+ const map = tableMapper.getMapSync();
222
+ if (!map)
351
223
  throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
352
224
  const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
353
- return _makeSmart(rowLocator, _headerMap, index);
225
+ return _makeSmart(rowLocator, map, index);
354
226
  },
355
227
  findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
356
- (0, debugUtils_1.logDebug)(config, 'info', 'Searching for row', filters);
357
- yield _ensureInitialized();
358
- let row = yield _findRowLocator(filters, options);
359
- if (row) {
360
- (0, debugUtils_1.logDebug)(config, 'info', 'Row found');
361
- yield (0, debugUtils_1.debugDelay)(config, 'findRow');
362
- return _makeSmart(row, _headerMap, 0);
363
- }
364
- (0, debugUtils_1.logDebug)(config, 'error', 'Row not found', filters);
365
- yield (0, debugUtils_1.debugDelay)(config, 'findRow');
366
- // Return sentinel row
367
- row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
368
- return _makeSmart(row, _headerMap, 0);
228
+ // @ts-ignore
229
+ return rowFinder.findRow(filters, options);
369
230
  }),
370
231
  getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
371
- var _a;
372
- yield _ensureInitialized();
373
- let rowLocators = resolve(config.rowSelector, rootLocator);
374
- if (options === null || options === void 0 ? void 0 : options.filter) {
375
- rowLocators = filterEngine.applyFilters(rowLocators, options.filter, _headerMap, options.exact || false, rootLocator.page());
376
- }
377
- const allRowLocs = yield rowLocators.all();
378
- const smartRows = [];
379
- const isRowLoading = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isRowLoading;
380
- for (let i = 0; i < allRowLocs.length; i++) {
381
- const smartRow = _makeSmart(allRowLocs[i], _headerMap, i);
382
- if (isRowLoading) {
383
- const loading = yield isRowLoading(smartRow);
384
- if (loading)
385
- continue;
386
- }
387
- smartRows.push(smartRow);
388
- }
389
- return (0, smartRowArray_1.createSmartRowArray)(smartRows);
232
+ console.warn('DEPRECATED: table.getRows() is deprecated and will be removed in a future version. Use table.findRows() instead.');
233
+ // @ts-ignore
234
+ return rowFinder.findRows((options === null || options === void 0 ? void 0 : options.filter) || {}, Object.assign(Object.assign({}, options), { maxPages: 1 }));
390
235
  }),
391
236
  findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
392
- var _a, _b, _c, _d, _e;
393
- yield _ensureInitialized();
394
- const allRows = [];
395
- const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
396
- let pageCount = 0;
397
- // Collect rows from current page
398
- let rowLocators = resolve(config.rowSelector, rootLocator);
399
- rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false, rootLocator.page());
400
- let currentRows = yield rowLocators.all();
401
- const isRowLoading = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.isRowLoading;
402
- for (let i = 0; i < currentRows.length; i++) {
403
- const smartRow = _makeSmart(currentRows[i], _headerMap, i);
404
- if (isRowLoading && (yield isRowLoading(smartRow)))
405
- continue;
406
- allRows.push(smartRow);
407
- }
408
- // Paginate and collect more rows
409
- while (pageCount < effectiveMaxPages && config.strategies.pagination) {
410
- const paginationResult = yield config.strategies.pagination({
411
- root: rootLocator,
412
- config,
413
- resolve,
414
- page: rootLocator.page()
415
- });
416
- const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
417
- if (!didPaginate)
418
- break;
419
- pageCount++;
420
- _hasPaginated = true;
421
- // Collect rows from new page
422
- rowLocators = resolve(config.rowSelector, rootLocator);
423
- rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_e = options === null || options === void 0 ? void 0 : options.exact) !== null && _e !== void 0 ? _e : false, rootLocator.page());
424
- const newRows = yield rowLocators.all();
425
- for (let i = 0; i < newRows.length; i++) {
426
- const smartRow = _makeSmart(newRows[i], _headerMap, i);
427
- if (isRowLoading && (yield isRowLoading(smartRow)))
428
- continue;
429
- allRows.push(smartRow);
430
- }
431
- }
432
- if (options === null || options === void 0 ? void 0 : options.asJSON) {
433
- return Promise.all(allRows.map(r => r.toJSON()));
434
- }
435
- return allRows;
237
+ return rowFinder.findRows(filters, options);
436
238
  }),
437
239
  isInitialized: () => {
438
- return _isInitialized;
240
+ return tableMapper.isInitialized();
439
241
  },
440
242
  sorting: {
441
243
  apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
@@ -463,6 +265,7 @@ const useTable = (rootLocator, configOptions = {}) => {
463
265
  throw new Error('No pagination strategy provided.');
464
266
  yield result.reset();
465
267
  yield result.init();
268
+ const map = tableMapper.getMapSync();
466
269
  const restrictedTable = {
467
270
  init: result.init,
468
271
  getHeaders: result.getHeaders,
@@ -497,7 +300,7 @@ const useTable = (rootLocator, configOptions = {}) => {
497
300
  const smartRowsArray = [];
498
301
  const isRowLoading = (_f = config.strategies.loading) === null || _f === void 0 ? void 0 : _f.isRowLoading;
499
302
  for (let i = 0; i < rowLocators.length; i++) {
500
- const smartRow = _makeSmart(rowLocators[i], _headerMap, i);
303
+ const smartRow = _makeSmart(rowLocators[i], map, i);
501
304
  if (isRowLoading && (yield isRowLoading(smartRow)))
502
305
  continue;
503
306
  smartRowsArray.push(smartRow);
@@ -533,7 +336,7 @@ const useTable = (rootLocator, configOptions = {}) => {
533
336
  let isLast = getIsLast({ index: callbackIndex, paginationResult });
534
337
  const isLastDueToMax = index === effectiveMaxIterations - 1;
535
338
  if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
536
- yield options.beforeFirst({ index: callbackIndex, rows: callbackRows, allData });
339
+ yield options.beforeFirst({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(callbackRows), allData });
537
340
  }
538
341
  const batchInfo = isBatching ? {
539
342
  startIndex: batchStartIndex,
@@ -565,7 +368,7 @@ const useTable = (rootLocator, configOptions = {}) => {
565
368
  finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
566
369
  }
567
370
  if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
568
- yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
371
+ yield options.afterLast({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(callbackRows), allData });
569
372
  }
570
373
  if (finalIsLast || !paginationResult) {
571
374
  log(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
@@ -589,7 +392,7 @@ const useTable = (rootLocator, configOptions = {}) => {
589
392
  const isFirst = getIsFirst({ index: callbackIndex });
590
393
  const isLast = true;
591
394
  if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
592
- yield options.beforeFirst({ index: callbackIndex, rows: batchRows, allData });
395
+ yield options.beforeFirst({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(batchRows), allData });
593
396
  }
594
397
  const batchInfo = {
595
398
  startIndex: batchStartIndex,
@@ -612,7 +415,7 @@ const useTable = (rootLocator, configOptions = {}) => {
612
415
  allData.push(returnValue);
613
416
  }
614
417
  if (options === null || options === void 0 ? void 0 : options.afterLast) {
615
- yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
418
+ yield options.afterLast({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(batchRows), allData });
616
419
  }
617
420
  log(`Pagination failed mid-batch (index: ${index})`);
618
421
  break;
@@ -18,11 +18,19 @@ function levenshteinDistance(a, b) {
18
18
  }
19
19
  for (let i = 1; i <= b.length; i++) {
20
20
  for (let j = 1; j <= a.length; j++) {
21
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
21
+ const charB = b.charAt(i - 1);
22
+ const charA = a.charAt(j - 1);
23
+ if (charB === charA) {
24
+ // Exact match
22
25
  matrix[i][j] = matrix[i - 1][j - 1];
23
26
  }
24
27
  else {
25
- matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
28
+ let cost = 1;
29
+ // If characters match ignoring case, cost is only 0.1 (almost identical)
30
+ if (charB.toLowerCase() === charA.toLowerCase()) {
31
+ cost = 0.1;
32
+ }
33
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + cost, // substitution
26
34
  matrix[i][j - 1] + 1, // insertion
27
35
  matrix[i - 1][j] + 1 // deletion
28
36
  );
@@ -36,7 +44,8 @@ function levenshteinDistance(a, b) {
36
44
  * 1 = identical, 0 = completely different
37
45
  */
38
46
  function stringSimilarity(a, b) {
39
- const distance = levenshteinDistance(a.toLowerCase(), b.toLowerCase());
47
+ // We do NOT modify case here anymore, because levenshteinDistance now handles case weighting
48
+ const distance = levenshteinDistance(a, b);
40
49
  const maxLen = Math.max(a.length, b.length);
41
50
  return maxLen === 0 ? 1 : 1 - (distance / maxLen);
42
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.1.1",
3
+ "version": "6.3.0",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",
@@ -22,7 +22,10 @@
22
22
  "docs:build": "vitepress build docs",
23
23
  "build": "npm run generate-types && npm run generate-docs && npm run generate-all-api-docs && npm run update-all-api-signatures && tsc",
24
24
  "prepublishOnly": "npm run build",
25
- "test": "npx playwright test",
25
+ "test": "npm run test:unit && npx playwright test",
26
+ "test:unit": "vitest run --reporter=verbose --reporter=html",
27
+ "test:unit:ui": "vitest --ui",
28
+ "test:e2e": "npx playwright test",
26
29
  "test:compatibility": "npx playwright test compatibility",
27
30
  "prepare": "husky install"
28
31
  },
@@ -38,9 +41,11 @@
38
41
  "devDependencies": {
39
42
  "@playwright/test": "^1.49.1",
40
43
  "@types/node": "^22.10.5",
44
+ "@vitest/ui": "^4.0.18",
45
+ "happy-dom": "^20.6.1",
41
46
  "husky": "^9.1.7",
42
47
  "typescript": "^5.7.2",
43
- "vitepress": "^1.6.4"
44
- },
45
- "dependencies": {}
48
+ "vitepress": "^1.6.4",
49
+ "vitest": "^4.0.18"
50
+ }
46
51
  }