fix(grid): replace matches in truncated large values
This commit is contained in:
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;
|
||||
}
|
||||
Reference in new issue
Block a user