diff --git a/server/routes/utils/__tests__/dataReportHelpers.test.ts b/server/routes/utils/__tests__/dataReportHelpers.test.ts index 30f7e786..692720b4 100644 --- a/server/routes/utils/__tests__/dataReportHelpers.test.ts +++ b/server/routes/utils/__tests__/dataReportHelpers.test.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { getOpenSearchData } from '../dataReportHelpers'; +import { getOpenSearchData, convertToCSV } from '../dataReportHelpers'; jest.mock('../excelBuilder', () => ({ ExcelBuilder: jest.fn().mockImplementation(() => ({ @@ -361,3 +361,21 @@ describe('test traverse preserves selected field order', () => { expect(keys2).toEqual(['field_b', 'field_a', 'field_c']); }); }); + +describe('convertToCSV', () => { + test('should prepend UTF-8 BOM to CSV output', async () => { + const dataset = [[{ name: 'test', value: 'hello' }]]; + const result = await convertToCSV(dataset, ','); + expect(result.charCodeAt(0)).toBe(0xfeff); + expect(result.startsWith('\uFEFF')).toBe(true); + }); + + test('should contain correct CSV content after BOM', async () => { + const dataset = [[{ name: "it's", city: 'Zürich' }]]; + const result = await convertToCSV(dataset, ','); + const csvContent = result.substring(1); // skip BOM + expect(csvContent).toContain('name'); + expect(csvContent).toContain("it's"); + expect(csvContent).toContain('Zürich'); + }); +}); diff --git a/server/routes/utils/dataReportHelpers.ts b/server/routes/utils/dataReportHelpers.ts index 0eac1be3..981f6be7 100644 --- a/server/routes/utils/dataReportHelpers.ts +++ b/server/routes/utils/dataReportHelpers.ts @@ -231,7 +231,8 @@ export const convertToCSV = async (dataset, csvSeparator) => { await converter.json2csvAsync(dataset[0], options).then((csv) => { convertedData = csv; }); - return convertedData; + // Prepend UTF-8 BOM so Excel correctly detects encoding + return '\uFEFF' + convertedData; }; function flattenHits(