mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
133ec073be
* Initial plan * Add CSV export functionality to members page Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Align Export CSV button to the right Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Address code review feedback: improve type safety, error handling, and date formatting Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Improve CSV utility and date handling consistency Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Improve error messages and fix useCallback dependencies Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Add comprehensive tests for CSV utility Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Refactor: reduce limit to 100, replace lastActiveIp with role, extract ExportCSV component Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * Improve type safety and extract pagination constant Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> * refactor --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com> Co-authored-by: Tom Moor <tom@getoutline.com>
157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
import { CSVHelper } from "./csv";
|
||
|
||
describe("CSVHelper", () => {
|
||
describe("sanitizeValue", () => {
|
||
it("should leave a value unchanged", () => {
|
||
const value = "Hello, World!";
|
||
const sanitizedValue = CSVHelper.sanitizeValue(value);
|
||
expect(sanitizedValue).toBe(value);
|
||
});
|
||
|
||
it("should escape formula trigger character", () => {
|
||
expect(CSVHelper.sanitizeValue("@1x2")).toBe(`'@1x2`);
|
||
expect(CSVHelper.sanitizeValue("=1x2")).toBe(`'=1x2`);
|
||
expect(CSVHelper.sanitizeValue("=1x2")).toBe(`'=1x2`);
|
||
expect(CSVHelper.sanitizeValue("≠1x2")).toBe(`'≠1x2`);
|
||
expect(CSVHelper.sanitizeValue("+1x2")).toBe(`'+1x2`);
|
||
expect(CSVHelper.sanitizeValue("∑1x2")).toBe(`'∑1x2`);
|
||
});
|
||
|
||
it("should remove control characters", () => {
|
||
expect(CSVHelper.sanitizeValue("\u00011x2")).toBe(`1x2`);
|
||
});
|
||
|
||
it("should remove zero-width characters", () => {
|
||
expect(CSVHelper.sanitizeValue("\u200B1x2")).toBe(`1x2`);
|
||
});
|
||
|
||
it("should remove whitespace characters", () => {
|
||
expect(CSVHelper.sanitizeValue("\u200B1x2")).toBe(`1x2`);
|
||
});
|
||
});
|
||
|
||
describe("escapeCSVField", () => {
|
||
it("should escape fields with commas", () => {
|
||
expect(CSVHelper.escapeCSVField("Doe, John")).toBe('"Doe, John"');
|
||
});
|
||
|
||
it("should escape fields with quotes", () => {
|
||
expect(CSVHelper.escapeCSVField('John "Johnny" Doe')).toBe(
|
||
'"John ""Johnny"" Doe"'
|
||
);
|
||
});
|
||
|
||
it("should escape fields with newlines", () => {
|
||
expect(CSVHelper.escapeCSVField("John\nDoe")).toBe('"John\nDoe"');
|
||
});
|
||
|
||
it("should handle null values", () => {
|
||
expect(CSVHelper.escapeCSVField(null)).toBe("");
|
||
});
|
||
|
||
it("should handle undefined values", () => {
|
||
expect(CSVHelper.escapeCSVField(undefined)).toBe("");
|
||
});
|
||
|
||
it("should handle empty strings", () => {
|
||
expect(CSVHelper.escapeCSVField("")).toBe("");
|
||
});
|
||
|
||
it("should leave simple values unchanged", () => {
|
||
expect(CSVHelper.escapeCSVField("John")).toBe("John");
|
||
});
|
||
});
|
||
|
||
describe("convertToCSV", () => {
|
||
it("should convert simple data to CSV", () => {
|
||
const data = [
|
||
{ id: "1", name: "John Doe", email: "john@example.com" },
|
||
{ id: "2", name: "Jane Smith", email: "jane@example.com" },
|
||
];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe(
|
||
"id,name,email\n1,John Doe,john@example.com\n2,Jane Smith,jane@example.com"
|
||
);
|
||
});
|
||
|
||
it("should escape fields with commas", () => {
|
||
const data = [{ id: "1", name: "Doe, John", email: "john@example.com" }];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe('id,name,email\n1,"Doe, John",john@example.com');
|
||
});
|
||
|
||
it("should escape fields with quotes", () => {
|
||
const data = [
|
||
{ id: "1", name: 'John "Johnny" Doe', email: "john@example.com" },
|
||
];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe(
|
||
'id,name,email\n1,"John ""Johnny"" Doe",john@example.com'
|
||
);
|
||
});
|
||
|
||
it("should escape fields with newlines", () => {
|
||
const data = [{ id: "1", name: "John\nDoe", email: "john@example.com" }];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe('id,name,email\n1,"John\nDoe",john@example.com');
|
||
});
|
||
|
||
it("should handle empty values", () => {
|
||
const data = [{ id: "1", name: "", email: null as unknown as string }];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe("id,name,email\n1,,");
|
||
});
|
||
|
||
it("should handle undefined values", () => {
|
||
const data = [
|
||
{
|
||
id: "1",
|
||
name: undefined as unknown as string,
|
||
email: "john@example.com",
|
||
},
|
||
];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe("id,name,email\n1,,john@example.com");
|
||
});
|
||
|
||
it("should handle empty data array", () => {
|
||
const data: { id: string; name: string }[] = [];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe("id,name\n");
|
||
});
|
||
|
||
it("should handle different data types", () => {
|
||
const data = [{ id: 1, active: true, date: new Date("2024-01-01") }];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "active", "date"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
// Should convert all types to strings
|
||
expect(result).toContain("1");
|
||
expect(result).toContain("true");
|
||
expect(result).toContain("2024");
|
||
});
|
||
|
||
it("should sanitize formula trigger characters", () => {
|
||
const data = [{ id: "1", name: "=John", email: "+john@example.com" }];
|
||
const headers: (keyof (typeof data)[0])[] = ["id", "name", "email"];
|
||
const result = CSVHelper.convertToCSV(data, headers);
|
||
|
||
expect(result).toBe("id,name,email\n1,'=John,'+john@example.com");
|
||
});
|
||
});
|
||
});
|