arthub-table 0.2.2 → 0.2.3
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/arthub-table.common.js +1 -1
- package/dist/arthub-table.common.js.map +1 -1
- package/dist/arthub-table.umd.js +1 -1
- package/dist/arthub-table.umd.js.map +1 -1
- package/dist/arthub-table.umd.min.js +1 -1
- package/dist/arthub-table.umd.min.js.map +1 -1
- package/dist/types/core/DataGrid.d.ts +179 -1
- package/dist/types/core/viewers/RelatedTaskViewer.d.ts +28 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/testing/TestHooks.d.ts +228 -0
- package/dist/types/testing/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -2083,14 +2083,29 @@ declare class DataGrid {
|
|
|
2083
2083
|
match: boolean;
|
|
2084
2084
|
}>;
|
|
2085
2085
|
/**
|
|
2086
|
-
* Get info for all
|
|
2086
|
+
* Get info for all nested grids in the current viewport rows.
|
|
2087
2087
|
* Used by E2E tests to discover and interact with nested grids.
|
|
2088
|
+
*
|
|
2089
|
+
* ⚠️ 实现细节(避免 e2e 探测假阴性):
|
|
2090
|
+
* 1. `body.rows` 由虚拟滚动只缓存视口内的行,所以"完全滚出视口的行"
|
|
2091
|
+
* 上的嵌套表无法被报告 —— 这是必然损耗,e2e 端需要先 scroll 到目标行附近。
|
|
2092
|
+
* 2. `cell.nestedGrid` 是 **延迟创建** 的(见 Cell.ts:createNestedGridLazy()),
|
|
2093
|
+
* 只有 cell 被 `draw()` 至少一次才会实例化。横向滚动到刚露出的 cell
|
|
2094
|
+
* 在第一次 draw 之前 nestedGrid 仍是 null,但 `cell.hasNestedData=true`
|
|
2095
|
+
* 已经设好。这里用 `hasNestedData` 当判据,未实例化的也报告出来,
|
|
2096
|
+
* 让 e2e 端能正确识别"该列存在嵌套表,只是当前还没 draw"。
|
|
2097
|
+
* 对于这种 cell,`dataRowCount/columnCount` 等字段从 `nestedConfig`
|
|
2098
|
+
* 或 `column.nestedColumns` 推导,bounds 仍由 cell 几何 + scroll 计算。
|
|
2099
|
+
* 3. 触发实际 draw:调用方拿到这个 list 后若需要交互,可用 `triggerNestedGridDraw`
|
|
2100
|
+
* 或者先 scrollToCell 让目标 cell 进入绘制流程,nestedGrid 就会被创建。
|
|
2088
2101
|
*/
|
|
2089
2102
|
testGetNestedGridInfoList(): Array<{
|
|
2090
2103
|
parentRowId: string | number;
|
|
2091
2104
|
parentRowIndex: number;
|
|
2092
2105
|
parentColKey: string;
|
|
2093
2106
|
parentColIndex: number;
|
|
2107
|
+
/** Parent column display name(取自 column.name / title / label,便于 spec 按"配置/美宣"等业务名匹配) */
|
|
2108
|
+
parentColName: string;
|
|
2094
2109
|
bounds: {
|
|
2095
2110
|
x: number;
|
|
2096
2111
|
y: number;
|
|
@@ -2121,6 +2136,8 @@ declare class DataGrid {
|
|
|
2121
2136
|
col: number;
|
|
2122
2137
|
} | null;
|
|
2123
2138
|
rowHeight: number;
|
|
2139
|
+
/** NestedGrid 实例是否已 lazy-create(false=配置存在但还没 draw 过) */
|
|
2140
|
+
rendered: boolean;
|
|
2124
2141
|
}>;
|
|
2125
2142
|
/**
|
|
2126
2143
|
* Get nested grid info for a specific parent cell.
|
|
@@ -2364,6 +2381,167 @@ declare class DataGrid {
|
|
|
2364
2381
|
rowId: string;
|
|
2365
2382
|
};
|
|
2366
2383
|
} | null;
|
|
2384
|
+
/**
|
|
2385
|
+
* Get the row checkbox area for a normal (non-group) data row, in canvas coordinates.
|
|
2386
|
+
*
|
|
2387
|
+
* 这是 LRN-20260512-001 约定的"长期稳定 e2e 锚点 API":批量下载 / 多选等
|
|
2388
|
+
* spec 在 canvas 模式下需要点击行 checkbox。原本依赖 e2e 端按"index 列宽度的
|
|
2389
|
+
* 一半"近似估算 click point,在分组模式 / 不同 originFixedWidth 下会偏移到
|
|
2390
|
+
* 行号区,触发 onRowClick 而不是 checkbox toggle,导致 selectFirst5Tasks 失败。
|
|
2391
|
+
*
|
|
2392
|
+
* 这里直接复用 RowHeader 内部已有的 checkbox 偏移规则(与
|
|
2393
|
+
* `RowHeader.isInsideCheckboxBoundary` 完全对齐),保证 e2e 点中的位置就是
|
|
2394
|
+
* 业务运行时判定 checkbox 命中的位置。
|
|
2395
|
+
*
|
|
2396
|
+
* @param rowId - Row identifier
|
|
2397
|
+
* @returns 16×16px checkbox 矩形 + 行内 y 中心坐标 + 元数据;行不存在 / 关闭了
|
|
2398
|
+
* showTableIndex / showCheckbox / 是组头行 → 返回 null。
|
|
2399
|
+
*/
|
|
2400
|
+
testGetRowCheckboxArea(rowId: string | number): {
|
|
2401
|
+
canvasX: number;
|
|
2402
|
+
canvasY: number;
|
|
2403
|
+
width: number;
|
|
2404
|
+
height: number;
|
|
2405
|
+
meta: {
|
|
2406
|
+
rowIndex: number;
|
|
2407
|
+
checked: boolean;
|
|
2408
|
+
maxGroupLevel: number;
|
|
2409
|
+
parentGroupLevel: number | null;
|
|
2410
|
+
};
|
|
2411
|
+
} | null;
|
|
2412
|
+
/**
|
|
2413
|
+
* Find a row's stable user-facing id (`row[rowKey]`) by a name-like field value.
|
|
2414
|
+
*
|
|
2415
|
+
* 这是 LRN-20260512-001 约定的"长期稳定 e2e 锚点 API",专门给 AmE2ETest 在 canvas
|
|
2416
|
+
* 模式下做"按任务名找 row"用——之前 helper 端 `findRowIdByTaskName` 自己在
|
|
2417
|
+
* `getAllVisibleCells` / `grid.body.data` / `controller.tableTaskList` 三层 fallback
|
|
2418
|
+
* 各 evaluate 一次,每次都序列化深拷贝整张表,刚 setExtraTableData 后还有 hooks
|
|
2419
|
+
* 短暂返回 null 的窗口期;这里把"name → rowId"映射收敛进表本体的 `body.data`,
|
|
2420
|
+
* 不依赖任何视口状态、也不需要 row 在 rowCache 中实例化。
|
|
2421
|
+
*
|
|
2422
|
+
* 匹配规则(默认):
|
|
2423
|
+
* - 候选字段:`name`, `task_name`, `taskName`, `title`
|
|
2424
|
+
* · 优先精确相等;找不到才退到 includes 匹配。
|
|
2425
|
+
* · 字段值是对象时(自定义 viewer,例如交付物列返回 `{ files: [...] }`),
|
|
2426
|
+
* 展开 `value.name / value.task_name / value.taskName / value.title /
|
|
2427
|
+
* value.value / value.label / value.text` 再匹配字符串。
|
|
2428
|
+
* - 跳过组头行 / 分隔行 / 加任务行(这些行的 rowKey 通常不是真实任务 id)。
|
|
2429
|
+
*
|
|
2430
|
+
* 搜索范围(iter#3 加固,覆盖 batch-download spec 在上传/分组场景的命中失败):
|
|
2431
|
+
* 1) 先扫 `this.body.data`(视口内 + 当前 filter 后的数据,最常见路径)
|
|
2432
|
+
* 2) 没命中再扫 `this._fullData`(包含被折叠组隐藏的子行 / filter 过滤掉的行)
|
|
2433
|
+
* - 这样上传文件触发的"行临时移出 body.data"窗口期、以及 grouped 模式下查
|
|
2434
|
+
* 找折叠组子任务时不再返回 null,spec 端 retry 次数显著下降。
|
|
2435
|
+
* - 返回的 id 仍然是 `row[rowKey]`,与 body.data 路径完全一致;不会因为
|
|
2436
|
+
* fallback 出现"id 形态不一致 → checkbox 命中比较失败"的二次问题。
|
|
2437
|
+
*
|
|
2438
|
+
* 返回的是 `row[rowKey]`(与 `getAllVisibleCells().rowId` / `getCheckedRowIds()`
|
|
2439
|
+
* 严格对齐),不是内部的 `_rowId` string——这样 spec 端 `===` 比较不会因
|
|
2440
|
+
* `'string' === 1234` 失败。
|
|
2441
|
+
*
|
|
2442
|
+
* @param name - 任务名 / 显示名(不区分精确匹配 vs 包含匹配,由内部判定)
|
|
2443
|
+
* @param options.fields - 自定义候选字段数组(默认 `['name','task_name','taskName','title']`)
|
|
2444
|
+
* @param options.exact - true 时仅做精确相等匹配;默认 false(fallback 到 includes)
|
|
2445
|
+
* @returns row[rowKey] 或 null(找不到 / data 为空)
|
|
2446
|
+
*/
|
|
2447
|
+
testFindRowIdByName(name: string, options?: {
|
|
2448
|
+
fields?: string[];
|
|
2449
|
+
exact?: boolean;
|
|
2450
|
+
}): string | number | null;
|
|
2451
|
+
/**
|
|
2452
|
+
* E2E 诊断 hook:返回当前 DataGrid 的关键状态摘要(不含完整 row dump,避免序列化爆炸)。
|
|
2453
|
+
*
|
|
2454
|
+
* iter#4 新增(task=cdc1b0da):spec 在 canvas 模式下连续 3 轮出现 0/10 task 命中,
|
|
2455
|
+
* 但既有 hook 全是"找不到就返回 null"——观察侧拿不到任何中间状态信息。本 hook 给
|
|
2456
|
+
* spec / observation 一个轻量的"实地探查"入口,可以快速判断:
|
|
2457
|
+
* - 当前 grid 是不是空的(data.length === 0)
|
|
2458
|
+
* - rowKey 是不是预期值(业务侧默认 'id',task 池常用 'task_id')
|
|
2459
|
+
* - 任务名是否真的存在于 body.data / _fullData 但被规则筛掉了(broadScan 命中数)
|
|
2460
|
+
* - 前 3 行的字段集合是什么样(用于人工核对 task name 落在哪个字段)
|
|
2461
|
+
*
|
|
2462
|
+
* 返回结构刻意保持小(前 3 行 sample, 字段名列表)—— 便于 console.log 整段直接 dump。
|
|
2463
|
+
*
|
|
2464
|
+
* @param name - 可选;提供时会做一次 case-insensitive includes 扫描,返回命中数和首批样本
|
|
2465
|
+
* @returns 诊断对象,永不抛错
|
|
2466
|
+
*/
|
|
2467
|
+
testGetDiagnostics(name?: string): {
|
|
2468
|
+
rowKey: string;
|
|
2469
|
+
bodyDataLength: number;
|
|
2470
|
+
fullDataLength: number;
|
|
2471
|
+
columnsCount: number;
|
|
2472
|
+
sampleRowFieldNames: string[];
|
|
2473
|
+
sampleRows: Array<Record<string, any>>;
|
|
2474
|
+
nameProbe: {
|
|
2475
|
+
query: string;
|
|
2476
|
+
bodyMatchCount: number;
|
|
2477
|
+
fullMatchCount: number;
|
|
2478
|
+
bodyHits: Array<{
|
|
2479
|
+
rowIndex: number;
|
|
2480
|
+
matchedField: string;
|
|
2481
|
+
matchedValue: string;
|
|
2482
|
+
rowKeyValue: any;
|
|
2483
|
+
}>;
|
|
2484
|
+
} | null;
|
|
2485
|
+
};
|
|
2486
|
+
/**
|
|
2487
|
+
* Look up a column's business `key` by header text / column title.
|
|
2488
|
+
*
|
|
2489
|
+
* 与 `testFindRowIdByName` 互补:解决 LRN-20260512-002 描述的
|
|
2490
|
+
* "column.key vs header.colId(数字字符串)混淆"——AmE2ETest helper
|
|
2491
|
+
* `findCanvasDeliverableColumnKey('交付物')` 在 iter-1 主要走 host
|
|
2492
|
+
* controller 的 `e2eFindCanvasColumnKey`,但 host 没就绪 / 业务列正在重建时
|
|
2493
|
+
* 会 fallback 到 `grid.columns[i].title.includes(...)`,多次 evaluate 命中率低。
|
|
2494
|
+
*
|
|
2495
|
+
* 现在表格本体直接给一个稳定 entry:从 `this.columns[i].title / name / label`
|
|
2496
|
+
* 里反查到第一个匹配项的 `key`。
|
|
2497
|
+
*
|
|
2498
|
+
* @param displayName - 列的显示名(如 "交付物")
|
|
2499
|
+
* @param options.exact - 默认 false:includes 匹配;true 时仅做精确相等
|
|
2500
|
+
* @returns column.key(业务字段名)或 null
|
|
2501
|
+
*/
|
|
2502
|
+
testGetColumnKeyByName(displayName: string, options?: {
|
|
2503
|
+
exact?: boolean;
|
|
2504
|
+
}): string | null;
|
|
2505
|
+
/**
|
|
2506
|
+
* Atomic"按任务名 + 列显示名一次性拿到 cell interactive areas"hook。
|
|
2507
|
+
*
|
|
2508
|
+
* iter#2 加固(task=3f7204c0/iter#2):解决 batch-download spec 在 canvas 模式下
|
|
2509
|
+
* 的"多次 page.evaluate 之间存在重渲染窗口期"问题——
|
|
2510
|
+
* 旧路径 = ① findRowIdByName → ② getColumnKeyByName → ③ scrollToRow
|
|
2511
|
+
* → ④ getCellInteractiveAreas,每一步都跨一次浏览器 tick;上传完文件后
|
|
2512
|
+
* setExtraTableData 会触发部分 row 临时重排,第 ① 步拿到的 rowId 在第
|
|
2513
|
+
* ④ 步前可能已经从 body.data 里移走,导致 getCellInteractiveAreas 返回
|
|
2514
|
+
* null,spec 端判定"找不到 cell"。
|
|
2515
|
+
*
|
|
2516
|
+
* 这里把 4 步压成单次同步调用:name → rowId(含 _fullData fallback) →
|
|
2517
|
+
* displayName → columnKey → 自动 scrollIntoView → 直接复用
|
|
2518
|
+
* `testGetCellInteractiveAreas` 拿区域信息。spec / helper 只需要一次 evaluate。
|
|
2519
|
+
*
|
|
2520
|
+
* 不能命中的情况:
|
|
2521
|
+
* - taskName 在 body.data 与 _fullData 都查不到 → 返回 { ok: false, reason: 'row-not-found' }
|
|
2522
|
+
* - column displayName 找不到对应 column.key → 返回 { ok: false, reason: 'column-not-found', rowId }
|
|
2523
|
+
* - rowId 找到了但 row 不在 body.data(被分组折叠)+ scrollIntoView 后仍未进入
|
|
2524
|
+
* 视口 → 返回 { ok: false, reason: 'row-not-visible', rowId, columnKey }
|
|
2525
|
+
*
|
|
2526
|
+
* 完全命中时返回 testGetCellInteractiveAreas 的全部输出 + 解析出的 rowId/columnKey。
|
|
2527
|
+
*
|
|
2528
|
+
* @param taskName - 任务名(与 testFindRowIdByName 的入参语义一致)
|
|
2529
|
+
* @param columnDisplayName - 列显示名(与 testGetColumnKeyByName 的入参语义一致)
|
|
2530
|
+
* @param options.scrollIfNeeded - 默认 true:如果 row 不在视口内,先调
|
|
2531
|
+
* scrollRowIntoViewById 让它进入视口再取 areas;false 时跳过
|
|
2532
|
+
* scrollIntoView(保持当前 scroll 状态,直接用上一次绘制的 cell 几何)
|
|
2533
|
+
* @param options.exact - 透传给 findRowIdByName / getColumnKeyByName 的 exact 标志
|
|
2534
|
+
*/
|
|
2535
|
+
testFindCellAreasByName(taskName: string, columnDisplayName: string, options?: {
|
|
2536
|
+
scrollIfNeeded?: boolean;
|
|
2537
|
+
exact?: boolean;
|
|
2538
|
+
}): {
|
|
2539
|
+
ok: boolean;
|
|
2540
|
+
reason?: 'row-not-found' | 'column-not-found' | 'row-not-visible' | 'cell-not-found';
|
|
2541
|
+
rowId?: string | number;
|
|
2542
|
+
columnKey?: string;
|
|
2543
|
+
areas?: ReturnType<DataGrid['testGetCellInteractiveAreas']>;
|
|
2544
|
+
};
|
|
2367
2545
|
/**
|
|
2368
2546
|
* Get all visible group rows.
|
|
2369
2547
|
*/
|
|
@@ -17,6 +17,8 @@ declare class RelatedTaskViewer implements CellViewer<RelatedTaskViewerData> {
|
|
|
17
17
|
private get LABEL_COLOR();
|
|
18
18
|
private get EMPTY_TEXT_COLOR();
|
|
19
19
|
private get DASHED_BORDER_COLOR();
|
|
20
|
+
/** Color for trailing "..." indicating overflow content — uses link/brand color so users notice the cell has more content. */
|
|
21
|
+
private get OVERFLOW_HINT_COLOR();
|
|
20
22
|
constructor();
|
|
21
23
|
/**
|
|
22
24
|
* Calculate thumbnail height for single-line mode.
|
|
@@ -39,8 +41,32 @@ declare class RelatedTaskViewer implements CellViewer<RelatedTaskViewerData> {
|
|
|
39
41
|
draw(context: ViewerRenderContext, data: RelatedTaskViewerData): void;
|
|
40
42
|
private drawSingleLine;
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
+
* Single file-type field branch: draws label + all thumbnails proportionally scaled to fit.
|
|
45
|
+
* Mirrors ImageViewer.drawMultipleImages scaling logic so all images stay visible whenever possible.
|
|
46
|
+
* Drops trailing thumbnails (and shows trailing "...") only when scaled width would fall below MIN_THUMBNAIL_WIDTH.
|
|
47
|
+
*/
|
|
48
|
+
private drawSingleFileFieldLine;
|
|
49
|
+
/**
|
|
50
|
+
* Compute scaled-thumbnail layout for "single file-type field" mode.
|
|
51
|
+
* Algorithm:
|
|
52
|
+
* 1. Each thumbnail's ideal size starts from thumbHeight × aspectRatio.
|
|
53
|
+
* Then cap by MAX_THUMBNAIL_WIDTH: if idealWidth > MAX, scale that thumbnail's
|
|
54
|
+
* ideal height down so its width = MAX. (Keeps aspect ratio, prevents wide images
|
|
55
|
+
* from dominating the row.)
|
|
56
|
+
* 2. Row drawHeight = min(all per-thumbnail ideal heights). All thumbnails align to
|
|
57
|
+
* this height; per-thumbnail width = drawHeight × aspectRatio (≤ MAX_THUMBNAIL_WIDTH).
|
|
58
|
+
* 3. If total width + gaps > availableWidth → uniform scale-down on drawHeight.
|
|
59
|
+
* 4. After scaling, drop trailing thumbnails whose width < MIN_THUMBNAIL_WIDTH;
|
|
60
|
+
* retry on remaining items.
|
|
61
|
+
*/
|
|
62
|
+
private computeSingleFileFieldLayout;
|
|
63
|
+
/**
|
|
64
|
+
* Draw a single inline thumbnail at the given position (for single-line mode, multi-field branch).
|
|
65
|
+
* Reuses computeSingleFileFieldLayout so behavior matches the single-file-field branch:
|
|
66
|
+
* - All thumbnails proportionally scale into available width
|
|
67
|
+
* - Trailing thumbnails dropped only when scaled width < MIN_THUMBNAIL_WIDTH
|
|
68
|
+
* - When n === 1, accepts whatever width to keep at least one image visible
|
|
69
|
+
* @returns The X position after the thumbnail(s) and whether any thumbnails were dropped
|
|
44
70
|
*/
|
|
45
71
|
private drawInlineThumbnail;
|
|
46
72
|
private drawMultiLine;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -49,3 +49,5 @@ export { DataGridEventAdapter, EventCategory, extractEventContext, mapDomEventTo
|
|
|
49
49
|
export type { EventContext, } from './adapters/DataGridEventAdapter';
|
|
50
50
|
export { buildDataGridInitParams, refreshDataGridRows, refreshDataGridColumns, refreshDataGridColumnByKeys, refreshDataGridColumnByKeysAsync, refreshDataGridColumnByKeysFast, refreshDataGridRowByIndex, } from './adapters/DataGridIntegration';
|
|
51
51
|
export type { DataGridInitOptions, DataGridCallbacks, DataGridInitResult, } from './adapters/DataGridIntegration';
|
|
52
|
+
export { installTestHooks, removeTestHooks, SnapshotManager, } from './testing';
|
|
53
|
+
export type { TestHooksAPI, TestHooksRegistry, NestedGridTestInfo, } from './testing';
|
|
@@ -334,6 +334,19 @@ export interface TestHooksAPI {
|
|
|
334
334
|
* Returns expand arrow and checkbox bounds.
|
|
335
335
|
*/
|
|
336
336
|
getGroupRowAreas: (rowId: string) => GroupRowAreas | null;
|
|
337
|
+
/**
|
|
338
|
+
* 获取普通数据行 checkbox 的精确 canvas 坐标(与 RowHeader 的命中检测严格对齐)。
|
|
339
|
+
*
|
|
340
|
+
* LRN-20260512-001 约定的"长期稳定 e2e 锚点 API":批量下载 / 多选 spec 必须能
|
|
341
|
+
* 在 canvas 模式下点中行 checkbox 而不是行号。原本 e2e 端按"index 列宽度的一半"
|
|
342
|
+
* 估算坐标,在分组 / 不同 originFixedWidth 下会偏到行号区,触发 onRowClick
|
|
343
|
+
* 而不是 checkbox toggle。
|
|
344
|
+
*
|
|
345
|
+
* @param rowId - 行标识
|
|
346
|
+
* @returns 16×16 px 的 checkbox 区域(canvas 相对坐标),关闭索引列 / checkbox /
|
|
347
|
+
* 组头行 / 行不存在时返回 null。
|
|
348
|
+
*/
|
|
349
|
+
getRowCheckboxArea: (rowId: string | number) => RowCheckboxArea | null;
|
|
337
350
|
/**
|
|
338
351
|
* Get all visible group rows with their expand/collapse state.
|
|
339
352
|
*/
|
|
@@ -342,6 +355,183 @@ export interface TestHooksAPI {
|
|
|
342
355
|
* Get current selection, editing, and checked-row state.
|
|
343
356
|
*/
|
|
344
357
|
getSelectionState: () => SelectionState;
|
|
358
|
+
/**
|
|
359
|
+
* 仅返回当前已勾选行(checkbox-selected)的 rowId 列表。
|
|
360
|
+
*
|
|
361
|
+
* 与 `getSelectionState().checkedRowIds` 等价,但作为独立的稳定 hook 暴露,
|
|
362
|
+
* 给 E2E 单点查询用,避免上层为了"行勾选状态"再读完整 SelectionState。
|
|
363
|
+
*
|
|
364
|
+
* 这是按 LRN-20260512-001 约定的"长期稳定 e2e 锚点 API"——业务侧 / 表格侧
|
|
365
|
+
* 重构时不要随手隐藏。
|
|
366
|
+
*/
|
|
367
|
+
getCheckedRowIds: () => (string | number)[];
|
|
368
|
+
/**
|
|
369
|
+
* 按"任务名 / 显示名"反查 row 的 user-facing rowKey 值(一般是 task id)。
|
|
370
|
+
*
|
|
371
|
+
* LRN-20260512-001 + LRN-20260512-005 衍生需求:之前 AmE2ETest 端
|
|
372
|
+
* `findRowIdByTaskName` 自己分三层 evaluate(visibleCells → grid.body.data
|
|
373
|
+
* → controller.tableTaskList),每一层都做一次完整深拷贝,刚 setExtraTableData
|
|
374
|
+
* 后还存在 hooks 短暂返回 null 的窗口。表格本体直接给一个稳定 entry,
|
|
375
|
+
* 走 `body.data` 全量扫描(不依赖 rowCache 是否实例化),开销小且与
|
|
376
|
+
* `getCheckedRowIds` 的 id 类型严格对齐。
|
|
377
|
+
*
|
|
378
|
+
* 匹配规则:
|
|
379
|
+
* - 默认候选字段:`name`, `task_name`, `taskName`, `title`
|
|
380
|
+
* (可经 `options.fields` 自定义)
|
|
381
|
+
* - 字段值是对象时(自定义 viewer,如交付物列返回 `{ files: [...] }`),
|
|
382
|
+
* 展开 `value.name / .task_name / .taskName / .title / .value / .label / .text`
|
|
383
|
+
* 再匹配字符串。
|
|
384
|
+
* - 优先精确相等,其次 `includes` 子串匹配(设 `options.exact = true` 关闭子串)。
|
|
385
|
+
* - 跳过组头 / 分隔行 / 加任务行(这些 row 的 rowKey 通常不是真实 id)。
|
|
386
|
+
*
|
|
387
|
+
* @returns 找到时返回 `row[rowKey]`(保留 number/string 原始类型);未找到返回 null。
|
|
388
|
+
*/
|
|
389
|
+
findRowIdByName: (name: string, options?: {
|
|
390
|
+
fields?: string[];
|
|
391
|
+
exact?: boolean;
|
|
392
|
+
}) => string | number | null;
|
|
393
|
+
/**
|
|
394
|
+
* 按列显示名(header text / title)反查列的业务 `key`(即 `column.key`)。
|
|
395
|
+
*
|
|
396
|
+
* LRN-20260512-002:column.key 是业务字段名(如 `'deliverable_v2'`),
|
|
397
|
+
* 而 `getHeaderInfo()[i].colId` 是数字字符串——很多 e2e helper 把两者混用,
|
|
398
|
+
* `getCellLayout` / `getCellInteractiveAreas` 入参拿到 colId 永远 null。
|
|
399
|
+
*
|
|
400
|
+
* 这里把"displayName → column.key"映射收敛进表本体,e2e 端不再需要先打到
|
|
401
|
+
* 宿主 controller。
|
|
402
|
+
*
|
|
403
|
+
* @param displayName - 列的显示名(如 "交付物")
|
|
404
|
+
* @param options.exact - 默认 false:includes 匹配;true 时仅做精确相等
|
|
405
|
+
* @returns column.key 或 null
|
|
406
|
+
*/
|
|
407
|
+
getColumnKeyByName: (displayName: string, options?: {
|
|
408
|
+
exact?: boolean;
|
|
409
|
+
}) => string | null;
|
|
410
|
+
/**
|
|
411
|
+
* 程序化滚动让指定 rowId 的行进入可见视口。
|
|
412
|
+
*
|
|
413
|
+
* 仅做"垂直方向"滚动,水平滚动位置保持不变(避免破坏 e2e 中已对齐到目标列的滚动状态)。
|
|
414
|
+
* 行不存在 / rowId 找不到 → 返回 false;已可见或滚动到位 → 返回 true。
|
|
415
|
+
*
|
|
416
|
+
* 用于替代 e2e helper 里 N 次 wheel-scroll 的循环——直接 jump 到目标行,
|
|
417
|
+
* 避开"刚上传/刚刷新"导致 visibleCells 短时间没有目标行的窗口。
|
|
418
|
+
*/
|
|
419
|
+
scrollToRow: (rowId: string | number) => boolean;
|
|
420
|
+
/**
|
|
421
|
+
* 一次性把"任务名 + 列显示名 → cell interactive areas"全套查清楚的复合 hook。
|
|
422
|
+
*
|
|
423
|
+
* task=3f7204c0/iter#2 加固:把 spec 端的多步 evaluate 压成单次浏览器 tick,
|
|
424
|
+
* 避免 batch-download / deliverable-upload-download 等 spec 在每两步之间被
|
|
425
|
+
* setExtraTableData 触发的 row-reorder / re-layout 打断(旧路径需要 ① findRowIdByName
|
|
426
|
+
* → ② getColumnKeyByName → ③ scrollToRow → ④ getCellInteractiveAreas,每一步都
|
|
427
|
+
* 跨一个 tick)。
|
|
428
|
+
*
|
|
429
|
+
* 内部行为:
|
|
430
|
+
* 1) `findRowIdByName(taskName)` 拿 rowId(含 body.data → _fullData fallback)
|
|
431
|
+
* 2) `getColumnKeyByName(columnDisplayName)` 拿 column.key
|
|
432
|
+
* 3) 若 `scrollIfNeeded !== false`,自动调 `scrollRowIntoViewById` 让行进入视口
|
|
433
|
+
* 4) `getCellInteractiveAreas(rowId, columnKey)` 取交互区域(cell / editIcon /
|
|
434
|
+
* linkIcon / errorIcon / reminderTriangle / viewerAreas / meta)
|
|
435
|
+
*
|
|
436
|
+
* 失败时返回 `{ ok: false, reason }`,reason 详细分类:
|
|
437
|
+
* - `'row-not-found'` : taskName 在 body.data 与 _fullData 都查不到
|
|
438
|
+
* - `'column-not-found'` : 列显示名找不到对应 column.key
|
|
439
|
+
* - `'row-not-visible'` : rowId 在 _fullData 里有但被分组折叠隐藏(spec 应先展开分组)
|
|
440
|
+
* - `'cell-not-found'` : 极端情况(cell 在 row.allCells 里缺失,通常是 rowCache 异步未就绪)
|
|
441
|
+
*
|
|
442
|
+
* 成功时返回 `{ ok: true, rowId, columnKey, areas }`,areas 字段格式与
|
|
443
|
+
* `getCellInteractiveAreas` 完全一致。
|
|
444
|
+
*/
|
|
445
|
+
/**
|
|
446
|
+
* 诊断 hook:返回 grid 内部状态摘要。
|
|
447
|
+
*
|
|
448
|
+
* task=cdc1b0da/iter#4 加固:spec 在 canvas 模式下连续 3 轮出现 `findRowIdByName`
|
|
449
|
+
* 0/10 命中——既有 hook 全部"找不到就返回 null",观察侧拿不到中间状态。本 hook 给
|
|
450
|
+
* spec / verify 一个"实地探查"入口:可以快速判断 grid 是不是空、rowKey 是不是预期、
|
|
451
|
+
* 任务名是否真的存在但被字段白名单筛掉、前 3 行字段集合是什么样。
|
|
452
|
+
*
|
|
453
|
+
* 返回结构刻意保持小(前 3 行 sample, 字段名列表)—— 整段 console.log 也不会爆。
|
|
454
|
+
*
|
|
455
|
+
* @param name - 可选;提供时做一次 case-insensitive includes 扫描,返回命中数 + 首批样本
|
|
456
|
+
*/
|
|
457
|
+
getDiagnostics: (name?: string) => {
|
|
458
|
+
rowKey: string;
|
|
459
|
+
bodyDataLength: number;
|
|
460
|
+
fullDataLength: number;
|
|
461
|
+
columnsCount: number;
|
|
462
|
+
sampleRowFieldNames: string[];
|
|
463
|
+
sampleRows: Array<Record<string, any>>;
|
|
464
|
+
nameProbe: {
|
|
465
|
+
query: string;
|
|
466
|
+
bodyMatchCount: number;
|
|
467
|
+
fullMatchCount: number;
|
|
468
|
+
bodyHits: Array<{
|
|
469
|
+
rowIndex: number;
|
|
470
|
+
matchedField: string;
|
|
471
|
+
matchedValue: string;
|
|
472
|
+
rowKeyValue: any;
|
|
473
|
+
}>;
|
|
474
|
+
} | null;
|
|
475
|
+
};
|
|
476
|
+
findCellAreasByName: (taskName: string, columnDisplayName: string, options?: {
|
|
477
|
+
scrollIfNeeded?: boolean;
|
|
478
|
+
exact?: boolean;
|
|
479
|
+
}) => {
|
|
480
|
+
ok: boolean;
|
|
481
|
+
reason?: 'row-not-found' | 'column-not-found' | 'row-not-visible' | 'cell-not-found';
|
|
482
|
+
rowId?: string | number;
|
|
483
|
+
columnKey?: string;
|
|
484
|
+
areas?: {
|
|
485
|
+
cell: {
|
|
486
|
+
canvasX: number;
|
|
487
|
+
canvasY: number;
|
|
488
|
+
width: number;
|
|
489
|
+
height: number;
|
|
490
|
+
};
|
|
491
|
+
editIcon: {
|
|
492
|
+
canvasX: number;
|
|
493
|
+
canvasY: number;
|
|
494
|
+
width: number;
|
|
495
|
+
height: number;
|
|
496
|
+
} | null;
|
|
497
|
+
linkIcon: {
|
|
498
|
+
canvasX: number;
|
|
499
|
+
canvasY: number;
|
|
500
|
+
width: number;
|
|
501
|
+
height: number;
|
|
502
|
+
} | null;
|
|
503
|
+
errorIcon: {
|
|
504
|
+
canvasX: number;
|
|
505
|
+
canvasY: number;
|
|
506
|
+
width: number;
|
|
507
|
+
height: number;
|
|
508
|
+
} | null;
|
|
509
|
+
reminderTriangle: {
|
|
510
|
+
canvasX: number;
|
|
511
|
+
canvasY: number;
|
|
512
|
+
width: number;
|
|
513
|
+
height: number;
|
|
514
|
+
} | null;
|
|
515
|
+
viewerAreas: Array<{
|
|
516
|
+
type: string;
|
|
517
|
+
bounds: {
|
|
518
|
+
canvasX: number;
|
|
519
|
+
canvasY: number;
|
|
520
|
+
width: number;
|
|
521
|
+
height: number;
|
|
522
|
+
};
|
|
523
|
+
value?: any;
|
|
524
|
+
}>;
|
|
525
|
+
meta: {
|
|
526
|
+
viewerType: string;
|
|
527
|
+
readonly: boolean;
|
|
528
|
+
isLink: boolean;
|
|
529
|
+
hasError: boolean;
|
|
530
|
+
hasReminder: boolean;
|
|
531
|
+
hasNestedGrid: boolean;
|
|
532
|
+
};
|
|
533
|
+
} | null;
|
|
534
|
+
};
|
|
345
535
|
}
|
|
346
536
|
/**
|
|
347
537
|
* Test info for a single nested grid instance
|
|
@@ -355,6 +545,11 @@ export interface NestedGridTestInfo {
|
|
|
355
545
|
parentColKey: string;
|
|
356
546
|
/** Parent column index */
|
|
357
547
|
parentColIndex: number;
|
|
548
|
+
/**
|
|
549
|
+
* Parent column display name(取自 column.name / title / label,便于 spec 按"配置/美宣"等业务名匹配)。
|
|
550
|
+
* 如果都没配置,可能是空字符串。
|
|
551
|
+
*/
|
|
552
|
+
parentColName: string;
|
|
358
553
|
/** Nested grid position/size relative to canvas */
|
|
359
554
|
bounds: {
|
|
360
555
|
x: number;
|
|
@@ -396,6 +591,13 @@ export interface NestedGridTestInfo {
|
|
|
396
591
|
} | null;
|
|
397
592
|
/** Row height */
|
|
398
593
|
rowHeight: number;
|
|
594
|
+
/**
|
|
595
|
+
* 该嵌套表的 NestedGrid 实例是否已创建(即至少 draw 过一次)。
|
|
596
|
+
* - true :所有字段(bounds/selectionRange/...)来自实例,可直接交互
|
|
597
|
+
* - false:实例尚未 lazy-create,bounds/dataRowCount/columnCount 由配置推导,
|
|
598
|
+
* 交互前需要让 cell 进入视口并触发一次 draw
|
|
599
|
+
*/
|
|
600
|
+
rendered: boolean;
|
|
399
601
|
}
|
|
400
602
|
/**
|
|
401
603
|
* Bounding box in canvas coordinates (can be converted to viewport coords)
|
|
@@ -492,6 +694,32 @@ export interface GroupRowAreas {
|
|
|
492
694
|
rowId: string;
|
|
493
695
|
};
|
|
494
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Row checkbox area for normal (non-group) data rows.
|
|
699
|
+
*
|
|
700
|
+
* 与 `GroupRowAreas.checkbox` 区分:那个是组头行的 checkbox;本接口是普通数据
|
|
701
|
+
* 行 RowHeader 内部的 checkbox。两者命中检测代码各走一套路径,几何也不一样。
|
|
702
|
+
*/
|
|
703
|
+
export interface RowCheckboxArea {
|
|
704
|
+
/** Canvas-relative X coordinate (16×16 px square). */
|
|
705
|
+
canvasX: number;
|
|
706
|
+
/** Canvas-relative Y coordinate. */
|
|
707
|
+
canvasY: number;
|
|
708
|
+
/** Width — always 16 (CHECKBOX_SIZE). */
|
|
709
|
+
width: number;
|
|
710
|
+
/** Height — always 16 (CHECKBOX_SIZE). */
|
|
711
|
+
height: number;
|
|
712
|
+
meta: {
|
|
713
|
+
/** Row index in body.data. */
|
|
714
|
+
rowIndex: number;
|
|
715
|
+
/** Whether the checkbox is currently rendered as checked. */
|
|
716
|
+
checked: boolean;
|
|
717
|
+
/** Grid maxGroupLevel snapshot (for diagnostic purposes only). */
|
|
718
|
+
maxGroupLevel: number;
|
|
719
|
+
/** Parent group level if this row is inside a group, null otherwise. */
|
|
720
|
+
parentGroupLevel: number | null;
|
|
721
|
+
};
|
|
722
|
+
}
|
|
495
723
|
/**
|
|
496
724
|
* Current selection/editing state of the table
|
|
497
725
|
*/
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { CellLayout, VisibleCell, TestColumnConfig, ScrollPosition, TableDimensions, SnapshotDifference, SnapshotCompareResult, TableSnapshot, TestHooksAPI, AreaBounds, ViewerArea, CellInteractiveAreas, HeaderInteractiveAreas, GroupRowAreas, SelectionState, VisibleGroupRowInfo, TestHooksRegistry, } from './TestHooks';
|
|
1
|
+
export type { CellLayout, VisibleCell, TestColumnConfig, ScrollPosition, TableDimensions, SnapshotDifference, SnapshotCompareResult, TableSnapshot, TestHooksAPI, AreaBounds, ViewerArea, CellInteractiveAreas, HeaderInteractiveAreas, GroupRowAreas, SelectionState, VisibleGroupRowInfo, TestHooksRegistry, NestedGridTestInfo, } from './TestHooks';
|
|
2
2
|
export { installTestHooks, removeTestHooks } from './installTestHooks';
|
|
3
3
|
export { SnapshotManager } from './SnapshotManager';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arthub-table",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "High-performance canvas-based table/grid component for Vue 3 with TypeScript support, featuring virtual scrolling, cell viewers, grouped rows, and nested grids.",
|
|
5
5
|
"main": "dist/arthub-table.common.js",
|
|
6
6
|
"module": "dist/arthub-table.umd.min.js",
|