Files
amitwh b1b72f9a60 chore(release): bump v4.7.1 → v4.8.0; Prettier pass on overnight-session files
12 commits on master since v4.7.1 baseline, taking tests 524 → 914 across
27 new suites. This commit:
- Bumps package.json + README version to v4.8.0 (additive features → semver minor)
- Applies Prettier formatting to all files touched during the overnight
  session so release build is reproducible from a clean repo

New in v4.8.0:
- PDF encryption close-out (D1)
- KaTeX rendering test coverage
- Autosave buffer + crash recovery banner
- Daily notes + workspace search + doc-aware Q&A
- Search sidebar panel + Ask mode
- Status bar: word count, reading time, Flesch-Kincaid grade
- Footnote hover preview
- Smart paste (URL → link, CSV/TSV → table)
- Daily notes menu item + sidebar panel + Q&A deep-link
- Daily templates gallery
- Pluggable DocQA engine (TF-idF default + lazy neural embeddings)

Amit Haridas
2026-09-14 14:56:05 +05:30

180 lines
5.3 KiB
JavaScript

/**
* @jest-environment node
*
* csv-to-table tests — pure module.
*/
const {
detectDelimiter,
csvToTable,
looksLikeCsv,
parseRows,
escapeCell,
} = require('../src/utils/csv-to-table');
describe('detectDelimiter', () => {
test('prefers tabs over commas when both are present', () => {
expect(detectDelimiter('a\tb\tc\n1\t2\t3')).toBe('\t');
});
test('falls back to commas', () => {
expect(detectDelimiter('a,b,c\n1,2,3')).toBe(',');
});
test('uses semicolons when commas are absent', () => {
expect(detectDelimiter('a;b;c\n1;2;3')).toBe(';');
});
test('returns comma for empty / non-string input', () => {
expect(detectDelimiter('')).toBe(',');
expect(detectDelimiter(null)).toBe(',');
});
});
describe('parseRows (RFC 4180 quoting)', () => {
test('parses simple comma-separated rows', () => {
expect(parseRows('a,b,c\n1,2,3', ',')).toEqual([
['a', 'b', 'c'],
['1', '2', '3'],
]);
});
test('handles quoted fields with embedded delimiter', () => {
expect(parseRows('"a, b",c', ',')).toEqual([['a, b', 'c']]);
});
test('handles escaped double-quotes inside quoted fields', () => {
expect(parseRows('"he said ""hi""",ok', ',')).toEqual([['he said "hi"', 'ok']]);
});
test('handles CRLF line endings', () => {
expect(parseRows('a,b\r\n1,2', ',')).toEqual([
['a', 'b'],
['1', '2'],
]);
});
test('handles multi-line quoted fields', () => {
expect(parseRows('"line1\nline2",x', ',')).toEqual([['line1\nline2', 'x']]);
});
});
describe('escapeCell', () => {
test('escapes pipes (table syntax)', () => {
expect(escapeCell('a | b')).toBe('a \\| b');
});
test('escapes backslashes first', () => {
expect(escapeCell('a\\b')).toBe('a\\\\b');
expect(escapeCell('a\\|b')).toBe('a\\\\\\|b');
});
test('strips newlines (cells must be one logical line)', () => {
expect(escapeCell('a\nb')).toBe('ab');
expect(escapeCell('a\r\nb')).toBe('ab');
// Tabs survive — they don't break markdown tables and may carry intent.
expect(escapeCell('a\tb')).toBe('a\tb');
});
test('handles null / undefined / numbers', () => {
expect(escapeCell(null)).toBe('');
expect(escapeCell(undefined)).toBe('');
expect(escapeCell(42)).toBe('42');
});
});
describe('csvToTable — basic', () => {
test('converts simple CSV with first row as header', () => {
const md = csvToTable('name,age\nAlice,30\nBob,25');
// age column has 2 numeric values (30, 25) → right-aligned
expect(md).toBe('| name | age |\n| --- | ---: |\n| Alice | 30 |\n| Bob | 25 |');
});
test('handles a single row (no header)', () => {
const md = csvToTable('just,one,row');
expect(md).toBe('| just | one | row |\n| --- | --- | --- |');
});
test('TSV (Excel/Sheets paste)', () => {
const md = csvToTable('a\tb\tc\n1\t2\t3');
expect(md).toBe('| a | b | c |\n| --- | --- | --- |\n| 1 | 2 | 3 |');
});
test('returns empty string for empty / whitespace-only input', () => {
expect(csvToTable('')).toBe('');
expect(csvToTable(' \n ')).toBe('');
});
});
describe('csvToTable — alignment', () => {
test('right-aligns numeric columns', () => {
const md = csvToTable('name,count\nalpha,5\nbeta,12');
expect(md).toContain('| name | count |');
expect(md).toContain('| --- | ---: |');
});
test('keeps left alignment for text columns', () => {
const md = csvToTable('name,city\nAlice,Paris');
expect(md).toContain('| --- | --- |');
});
test('recognizes currency-formatted numbers as numeric', () => {
const md = csvToTable('item,price\napple,$1.50\npear,2.30');
expect(md).toContain('| --- | ---: |');
});
test('recognizes percentages', () => {
const md = csvToTable('metric,rate\nuptime,99.5%');
expect(md).toContain('| --- | ---: |');
});
});
describe('csvToTable — escaping', () => {
test('escapes pipes inside cells', () => {
const md = csvToTable('a,b\nfoo|bar,baz');
expect(md).toContain('| foo\\|bar | baz |');
});
test('escapes newlines inside cells', () => {
const md = csvToTable('a,b\n"line1\nline2",x');
expect(md).toContain('| line1line2 | x |');
});
});
describe('csvToTable — ragged rows', () => {
test('pads short rows with empty cells so the table stays rectangular', () => {
const md = csvToTable('a,b,c\n1,2');
const dataLines = md.split('\n').slice(2); // skip header + sep
expect(dataLines[0]).toBe('| 1 | 2 | |');
});
});
describe('looksLikeCsv', () => {
test('returns true for a tab-separated multi-row paste', () => {
expect(looksLikeCsv('a\tb\tc\n1\t2\t3')).toBe(true);
});
test('returns true for a comma-separated multi-row paste', () => {
expect(looksLikeCsv('a,b,c\n1,2,3')).toBe(true);
});
test('returns false for a single row', () => {
expect(looksLikeCsv('a,b,c')).toBe(false);
});
test("returns false when the column counts don't line up at all", () => {
expect(looksLikeCsv('a,b,c\n1\n2,3,4,5')).toBe(false);
});
test('returns false for very short / empty input', () => {
expect(looksLikeCsv('')).toBe(false);
expect(looksLikeCsv('ab')).toBe(false);
expect(looksLikeCsv(null)).toBe(false);
});
test('returns false for prose (no delimiter)', () => {
expect(
looksLikeCsv('The quick brown fox jumps over the lazy dog.\nThe cat sat on the mat.')
).toBe(false);
});
});