fix(grid): replace matches in truncated large values

This commit is contained in:
ccq authored and GitHub committed 2026-09-18 22:48:16 +08:00
1 parent 4699295ed6
commit 8bafe0916c
6 files changed
+157 -24

No files matched your search

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { findDataGridReplacementMatches, replaceDataGridText } from "@/lib/dataGrid/dataGridReplace";
import { describe, expect, it, vi } from "vitest";
import { findDataGridReplacementMatches, prepareDataGridCellReplacements, replaceDataGridText } from "@/lib/dataGrid/dataGridReplace";
describe("data grid searched text replacement", () => {
it("replaces repeated JSON fragments without changing the rest of the value", () => {
@@ -42,4 +42,46 @@ describe("data grid searched text replacement", () => {
expect(findDataGridReplacementMatches({ rows, search: "hit", includesCell: () => false })).toEqual([]);
expect(findDataGridReplacementMatches({ rows, search: "" })).toEqual([]);
});
it("loads complete large values once and replaces the complete text", async () => {
const resolveValues = vi.fn(async () => new Map([[7, new Map([[0, "prefix pujiang-pujiang suffix"]])]]));
await expect(
prepareDataGridCellReplacements({
matches: [{ rowId: 7, col: 0, value: "prefix pujiang-pujiang…" }],
search: "pujiang-pujiang",
replacement: "test-pj-charge",
caseSensitive: false,
needsResolution: () => true,
resolveValues,
}),
).resolves.toEqual([
{
rowId: 7,
col: 0,
sourceValue: "prefix pujiang-pujiang…",
previousValue: "prefix pujiang-pujiang suffix",
value: "prefix test-pj-charge suffix",
},
]);
expect(resolveValues).toHaveBeenCalledWith([7], [0]);
});
it("revalidates resolved values and skips stale preview matches", async () => {
const resolveValues = vi.fn(async () => new Map([[7, new Map([[0, "value changed on reload"]])]]));
await expect(
prepareDataGridCellReplacements({
matches: [
{ rowId: 7, col: 0, value: "preview hit" },
{ rowId: 2, col: 1, value: "ordinary hit" },
],
search: "hit",
replacement: "done",
caseSensitive: false,
needsResolution: (rowId) => rowId === 7,
resolveValues,
}),
).resolves.toEqual([{ rowId: 2, col: 1, previousValue: "ordinary hit", value: "ordinary done" }]);
});
});
@@ -1,7 +1,7 @@
import type { CellValue } from "@/lib/dataGrid/cellValue";
export type DataGridReplacementMatch = { rowId: number; col: number; value: string };
export type DataGridCellReplacement = DataGridReplacementMatch & { previousValue: string };
export type DataGridCellReplacement = DataGridReplacementMatch & { previousValue: string; sourceValue?: string };
export type DataGridReplaceScope = "loaded" | "column" | "selection";
type ReplacementMatchOptions = {
@@ -14,6 +14,15 @@ type ReplacementMatchOptions = {
isTruncated?: (rowId: number, col: number) => boolean;
};
type PrepareCellReplacementsOptions = {
matches: readonly DataGridReplacementMatch[];
search: string;
replacement: string;
caseSensitive: boolean;
needsResolution: (rowId: number, col: number) => boolean;
resolveValues: (rowIds: number[], columnIndexes: number[]) => Promise<Map<number, Map<number, CellValue>>>;
};
export function dataGridReplacementPattern(search: string, caseSensitive: boolean): RegExp {
return new RegExp(search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), caseSensitive ? "gu" : "giu");
}
@@ -40,3 +49,33 @@ export function findDataGridReplacementMatches(options: ReplacementMatchOptions)
}
return matches;
}
export async function prepareDataGridCellReplacements(options: PrepareCellReplacementsOptions): Promise<DataGridCellReplacement[]> {
if (!options.search || options.matches.length === 0) return [];
const resolutionMatches = options.matches.filter((match) => options.needsResolution(match.rowId, match.col));
const resolutionKeys = new Set(resolutionMatches.map((match) => `${match.rowId}:${match.col}`));
const resolvedValues = resolutionMatches.length ? await options.resolveValues([...new Set(resolutionMatches.map((match) => match.rowId))], [...new Set(resolutionMatches.map((match) => match.col))]) : new Map<number, Map<number, CellValue>>();
const pattern = dataGridReplacementPattern(options.search, options.caseSensitive);
const replacements: DataGridCellReplacement[] = [];
for (const match of options.matches) {
const needsResolution = resolutionKeys.has(`${match.rowId}:${match.col}`);
const resolvedRow = resolvedValues.get(match.rowId);
if (needsResolution && !resolvedRow?.has(match.col)) continue;
const previousValue = needsResolution ? resolvedRow?.get(match.col) : match.value;
if (typeof previousValue !== "string") continue;
pattern.lastIndex = 0;
if (!pattern.test(previousValue)) continue;
const value = replaceDataGridText(previousValue, options.search, options.replacement, options.caseSensitive);
if (value === previousValue) continue;
replacements.push({
rowId: match.rowId,
col: match.col,
previousValue,
value,
...(needsResolution ? { sourceValue: match.value } : {}),
});
}
return replacements;
}