fix(grid): copy NULL cells as empty fields

This commit is contained in:
李子凡 authored and GitHub committed 2026-08-31 16:57:33 +08:00
1 parent 27f62fe6e2
commit 0e9f92c917
11 files changed
+113 -16

No files matched your search

@@ -3,6 +3,11 @@ import { describe, expect, it } from "vitest";
import { DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions, resolveDataGridCopyPreference, validateDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor";
describe("data-grid extractor options", () => {
it("defaults DSV NULL output to an empty spreadsheet field", () => {
expect(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS.dsv.nullText).toBe("");
expect(normalizeDataGridExtractorOptions({}).dsv.nullText).toBe("");
});
it("resolves smart copy to raw for one cell and TSV otherwise", () => {
expect(normalizeDataGridCopyPreference(undefined)).toBe("smart");
expect(normalizeDataGridCopyPreference("smart")).toBe("smart");
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { buildDeleteRowConfirmDetails } from "../dataGridDetail";
import { buildDeleteRowConfirmDetails, dataGridColumnDetailTsv, dataGridRowDetailTsv, type DataGridColumnDetail, type DataGridRowDetail } from "../dataGridDetail";
type TestRow = string[];
@@ -131,3 +131,14 @@ describe("buildDeleteRowConfirmDetails", () => {
expect(result).toBe('Table: people\n{"id":"custom:1","name":"custom:Alice"}');
});
});
describe("data grid detail TSV", () => {
it("leaves NULL cells empty while preserving literal NULL text", () => {
const fields = [{ value: null }, { value: "NULL" }] as unknown as DataGridRowDetail["fields"];
const rowDetail = { fields } as DataGridRowDetail;
const columnDetail = { fields } as DataGridColumnDetail;
expect(dataGridRowDetailTsv(rowDetail)).toBe("\tNULL");
expect(dataGridColumnDetailTsv(columnDetail)).toBe("\nNULL");
});
});
@@ -10,6 +10,10 @@ export function displayCellValue(value: CellValue): string {
return String(value);
}
export function clipboardCellValue(value: CellValue): string {
return value === null ? "" : displayCellValue(value);
}
export function firstLineCellDisplayValue(value: string, flatteningMultiLine: boolean): string {
const lineBreakPattern = /\r\n|\r|\n/g;
if (flatteningMultiLine) {
@@ -136,7 +136,7 @@ export const DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS: DataGridExtractorOptions = {
dsv: {
columnSeparator: ",",
rowSeparator: "\n",
nullText: "NULL",
nullText: "",
quote: '"',
quotePolicy: "minimal",
includeColumnHeader: false,
@@ -1,5 +1,5 @@
import { cellImagePreviewUrl } from "@/lib/dataGrid/cellImageUrl";
import { displayCellValue, type CellValue } from "@/lib/dataGrid/cellValue";
import { clipboardCellValue, displayCellValue, type CellValue } from "@/lib/dataGrid/cellValue";
import { formatJsonText } from "@/lib/dataGrid/cellDetailPresentation";
import type { DatabaseType } from "@/types/database";
@@ -215,7 +215,7 @@ export function jsonDetailDisplayValue(value: unknown): unknown {
}
export function dataGridRowDetailTsv(detail: DataGridRowDetail): string {
return detail.fields.map((field) => displayCellValue(field.value)).join("\t");
return detail.fields.map((field) => clipboardCellValue(field.value)).join("\t");
}
export function dataGridColumnDetailJson(detail: DataGridColumnDetail): string {
@@ -230,7 +230,7 @@ export function dataGridColumnDetailJson(detail: DataGridColumnDetail): string {
}
export function dataGridColumnDetailTsv(detail: DataGridColumnDetail): string {
return detail.fields.map((field) => displayCellValue(field.value)).join("\n");
return detail.fields.map((field) => clipboardCellValue(field.value)).join("\n");
}
export interface BuildDeleteRowConfirmDetailsOptions<TRow> {