import saveAs from 'file-saver'; import { dataFrameFromJSON, DataFrameJSON, dateTimeFormat, FieldType, LogRowModel, LogsMetaKind } from '@grafana/data'; import { downloadAsJson, downloadDataFrameAsCsv, downloadLogsModelAsTxt } from './download'; jest.mock('file-saver', () => jest.fn()); describe('inspector download', () => { beforeEach(() => { jest.clearAllMocks(); jest.useFakeTimers({ now: new Date(1400000000000) }); }); afterEach(() => { jest.useRealTimers(); }); describe('downloadDataFrameAsCsv', () => { const json: DataFrameJSON = { schema: { fields: [ { name: 'time', type: FieldType.time }, { name: 'name', type: FieldType.string }, { name: 'value', type: FieldType.number }, ], }, data: { values: [[100], ['a'], [1]], }, }; it.each([[dataFrameFromJSON(json), 'test', '"time","name","value"\r\n100,a,1']])( 'should, when logsModel is %s and title is %s, resolve in %s', async (dataFrame, title, expected) => { downloadDataFrameAsCsv(dataFrame, title); const call = (saveAs as unknown as jest.Mock).mock.calls[0]; const blob = call[0]; const filename = call[1]; const text = await blob.text(); // By default the BOM character should not be included expect(await hasBOM(blob)).toBe(false); expect(text).toEqual(expected); expect(filename).toEqual(`${title}-data-${dateTimeFormat(1400000000000)}.csv`); } ); it('should include the BOM character when useExcelHeader is true', async () => { downloadDataFrameAsCsv(dataFrameFromJSON(json), 'test', { useExcelHeader: true }); const call = (saveAs as unknown as jest.Mock).mock.calls[0]; const blob = call[0]; const filename = call[1]; const text = await blob.text(); expect(await hasBOM(blob)).toBe(true); expect(text).toEqual('sep=,\r\n"time","name","value"\r\n100,a,1'); expect(filename).toEqual(`test-data-${dateTimeFormat(1400000000000)}.csv`); }); }); describe('downloadAsJson', () => { it.each([ ['foo', 'test', '"foo"'], [1, 'test', '1'], [{ foo: 'bar' }, 'test', '{"foo":"bar"}'], ])('should, when logsModel is %s and title is %s, resolve in %s', async (logsModel, title, expected) => { downloadAsJson(logsModel, title); const call = (saveAs as unknown as jest.Mock).mock.calls[0]; const blob = call[0]; const filename = call[1]; const text = await blob.text(); expect(text).toEqual(expected); expect(filename).toEqual(`${title}-${dateTimeFormat(1400000000000)}.json`); }); }); describe('downloadLogsModelAsTxt', () => { it.each([ [{ meta: [], rows: [] }, 'test', '\n\n'], [ { meta: [{ label: 'testLabel', value: 'testValue', kind: LogsMetaKind.String }], rows: [] }, 'test', 'testLabel: "testValue"\n\n\n', ], [{ meta: [{ label: 'testLabel', value: 1, kind: LogsMetaKind.Number }], rows: [] }, 'test', 'testLabel: 1\n\n\n'], [ { meta: [ { label: 'testLabel', value: 1, kind: LogsMetaKind.String }, { label: 'secondTestLabel', value: 2, kind: LogsMetaKind.String }, ], rows: [], }, 'test', 'testLabel: 1\nsecondTestLabel: 2\n\n\n', ], [ { meta: [ { label: 'testLabel', value: 1, kind: LogsMetaKind.String }, { label: 'secondTestLabel', value: 2, kind: LogsMetaKind.String }, ], rows: [{ timeEpochMs: 100, entry: 'testEntry' } as unknown as LogRowModel], }, 'test', `testLabel: 1\nsecondTestLabel: 2\n\n\n100\ttestEntry\n`, ], ])('should, when logsModel is %s and title is %s, resolve in %s', async (logsModel, title, expected) => { downloadLogsModelAsTxt(logsModel, title); const call = (saveAs as unknown as jest.Mock).mock.calls[0]; const blob = call[0]; const filename = call[1]; const text = await blob.text(); expect(text).toEqual(expected); expect(filename).toEqual(`${title}-logs-${dateTimeFormat(1400000000000)}.txt`); }); }); }); async function hasBOM(blob: Blob) { const reader = new FileReader(); return new Promise((resolve, reject) => { reader.onload = (event: ProgressEvent) => { if (event.target?.result instanceof ArrayBuffer) { const arr = new Uint8Array(event.target.result); resolve(arr[0] === 0xef && arr[1] === 0xbb && arr[2] === 0xbf); // Check for UTF-8 BOM } else { reject(new Error('Unexpected FileReader result type')); } }; reader.onerror = reject; reader.readAsArrayBuffer(blob.slice(0, 3)); // Read only the first 3 bytes }); }