Add support for csv + xlsx
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import ExcelJS from 'exceljs';
|
||||
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
||||
import { registerDocumentConverters } from '../../src/converters/document.js';
|
||||
import { resolve } from '../../src/converters/registry.js';
|
||||
import { detectInputMime } from '../../src/mime.js';
|
||||
|
||||
let tmpDir;
|
||||
let csvFixturePath;
|
||||
let xlsxFixturePath;
|
||||
|
||||
beforeAll(async () => {
|
||||
registerDocumentConverters();
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'converter-spreadsheet-'));
|
||||
|
||||
csvFixturePath = path.join(tmpDir, 'fixture.csv');
|
||||
await fs.writeFile(csvFixturePath, 'Name,Age\nAlice,30\nBob,25\n');
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('Sheet1');
|
||||
worksheet.addRows([
|
||||
['Name', 'Age'],
|
||||
['Alice', 30],
|
||||
['Bob', 25],
|
||||
]);
|
||||
xlsxFixturePath = path.join(tmpDir, 'fixture.xlsx');
|
||||
await workbook.xlsx.writeFile(xlsxFixturePath);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('spreadsheet converters', () => {
|
||||
it('converts CSV to XLSX', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.xlsx');
|
||||
const entry = resolve('csv', 'xlsx');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(outputPath);
|
||||
const [worksheet] = workbook.worksheets;
|
||||
expect(worksheet.getRow(2).values.slice(1)).toEqual(['Alice', 30]);
|
||||
});
|
||||
|
||||
it('converts XLSX to CSV', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-xlsx.csv');
|
||||
const entry = resolve('xlsx', 'csv');
|
||||
|
||||
await entry.convert(xlsxFixturePath, outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Name,Age');
|
||||
expect(csv).toContain('Alice,30');
|
||||
});
|
||||
|
||||
it('converts CSV to HTML as a table', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.html');
|
||||
const entry = resolve('csv', 'html');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const html = await fs.readFile(outputPath, 'utf8');
|
||||
expect(html).toContain('<table>');
|
||||
expect(html).toContain('<td>Alice</td>');
|
||||
});
|
||||
|
||||
it('converts HTML table to CSV', async () => {
|
||||
const inputPath = path.join(tmpDir, 'fixture-table.html');
|
||||
await fs.writeFile(
|
||||
inputPath,
|
||||
'<html><body><table><tr><td>Name</td><td>Age</td></tr><tr><td>Alice</td><td>30</td></tr></table></body></html>'
|
||||
);
|
||||
const outputPath = path.join(tmpDir, 'from-html.csv');
|
||||
const entry = resolve('html', 'csv');
|
||||
|
||||
await entry.convert(inputPath, outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Name,Age');
|
||||
expect(csv).toContain('Alice,30');
|
||||
});
|
||||
|
||||
it('converts CSV to a Markdown table', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.md');
|
||||
const entry = resolve('csv', 'md');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const markdown = await fs.readFile(outputPath, 'utf8');
|
||||
expect(markdown).toContain('| Name | Age |');
|
||||
expect(markdown).toContain('| Alice | 30 |');
|
||||
});
|
||||
|
||||
it('converts a Markdown table to CSV', async () => {
|
||||
const inputPath = path.join(tmpDir, 'fixture-table.md');
|
||||
await fs.writeFile(inputPath, '| Name | Age |\n| --- | --- |\n| Alice | 30 |\n');
|
||||
const outputPath = path.join(tmpDir, 'from-md.csv');
|
||||
const entry = resolve('md', 'csv');
|
||||
|
||||
await entry.convert(inputPath, outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Name,Age');
|
||||
expect(csv).toContain('Alice,30');
|
||||
});
|
||||
|
||||
it('converts CSV to tab-delimited TXT', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.txt');
|
||||
const entry = resolve('csv', 'txt');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const text = await fs.readFile(outputPath, 'utf8');
|
||||
expect(text).toContain('Name\tAge');
|
||||
expect(text).toContain('Alice\t30');
|
||||
});
|
||||
|
||||
it('converts tab-delimited TXT to CSV', async () => {
|
||||
const inputPath = path.join(tmpDir, 'fixture.txt');
|
||||
await fs.writeFile(inputPath, 'Name\tAge\nAlice\t30\n');
|
||||
const outputPath = path.join(tmpDir, 'from-txt.csv');
|
||||
const entry = resolve('txt', 'csv');
|
||||
|
||||
await entry.convert(inputPath, outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Name,Age');
|
||||
expect(csv).toContain('Alice,30');
|
||||
});
|
||||
|
||||
it('converts CSV to DOCX containing a table', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.docx');
|
||||
const entry = resolve('csv', 'docx');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const mammoth = await import('mammoth');
|
||||
const result = await mammoth.default.convertToHtml({ path: outputPath });
|
||||
expect(result.value).toContain('Alice');
|
||||
expect(result.value).toContain('<table>');
|
||||
});
|
||||
|
||||
it('converts DOCX table to CSV', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-docx.csv');
|
||||
const entry = resolve('docx', 'csv');
|
||||
|
||||
await entry.convert(path.join(tmpDir, 'from-csv.docx'), outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Alice');
|
||||
});
|
||||
|
||||
it('converts CSV to a valid PDF', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-csv.pdf');
|
||||
const entry = resolve('csv', 'pdf');
|
||||
|
||||
await entry.convert(csvFixturePath, outputPath);
|
||||
|
||||
const detected = await detectInputMime(outputPath);
|
||||
expect(detected.mime).toBe('application/pdf');
|
||||
}, 20000);
|
||||
|
||||
it('converts XLSX to HTML as a table', async () => {
|
||||
const outputPath = path.join(tmpDir, 'from-xlsx.html');
|
||||
const entry = resolve('xlsx', 'html');
|
||||
|
||||
await entry.convert(xlsxFixturePath, outputPath);
|
||||
|
||||
const html = await fs.readFile(outputPath, 'utf8');
|
||||
expect(html).toContain('<td>Bob</td>');
|
||||
});
|
||||
|
||||
it('converts HTML table to XLSX', async () => {
|
||||
const inputPath = path.join(tmpDir, 'fixture-table-2.html');
|
||||
await fs.writeFile(
|
||||
inputPath,
|
||||
'<html><body><table><tr><td>Name</td><td>Age</td></tr><tr><td>Carol</td><td>40</td></tr></table></body></html>'
|
||||
);
|
||||
const outputPath = path.join(tmpDir, 'from-html.xlsx');
|
||||
const entry = resolve('html', 'xlsx');
|
||||
|
||||
await entry.convert(inputPath, outputPath);
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(outputPath);
|
||||
const [worksheet] = workbook.worksheets;
|
||||
expect(worksheet.getRow(2).values.slice(1)).toEqual(['Carol', '40']);
|
||||
});
|
||||
|
||||
it('extracts PDF text into a CSV row (best-effort)', async () => {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const page = pdfDoc.addPage([600, 400]);
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
page.drawText('Extractable fixture text', { x: 50, y: 350, size: 24, font });
|
||||
const bytes = await pdfDoc.save();
|
||||
const inputPath = path.join(tmpDir, 'fixture.pdf');
|
||||
await fs.writeFile(inputPath, bytes);
|
||||
|
||||
const outputPath = path.join(tmpDir, 'from-pdf.csv');
|
||||
const entry = resolve('pdf', 'csv');
|
||||
|
||||
await entry.convert(inputPath, outputPath);
|
||||
|
||||
const csv = await fs.readFile(outputPath, 'utf8');
|
||||
expect(csv).toContain('Extractable fixture text');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user