import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import {
extractTables,
stripHtml,
strikeHtml,
parseNumber,
buildPatchReplace,
buildPatchAppend,
} from '../lib/table.mjs';
const sampleHtml = `
<html><body>
<table data-id="t1">
<tr data-id="r1"><td data-id="c11"><p>Apple</p></td><td data-id="c12">1 234,56 Ft</td></tr>
<tr data-id="r2"><td data-id="c21"><p>Pear</p></td><td data-id="c22">2 000 Ft</td></tr>
<tr data-id="r3"><td data-id="c31"><p>Total</p></td><td data-id="c32"> </td></tr>
</table>
<p data-id="p1">not a table</p>
</body></html>
`;
describe('extractTables', () => {
it('parses table id, row ids, cell ids, cell html and text', () => {
const tables = extractTables(sampleHtml);
assert.equal(tables.length, 1);
const [t] = tables;
assert.equal(t.id, 't1');
assert.equal(t.rows.length, 3);
assert.deepEqual(t.rows.map(r => r.id), ['r1', 'r2', 'r3']);
assert.deepEqual(t.rows[0].cells.map(c => c.id), ['c11', 'c12']);
assert.equal(t.rows[0].cells[0].text, 'Apple');
assert.equal(t.rows[0].cells[1].text, '1 234,56 Ft');
});
it('returns empty array when no tables present', () => {
assert.deepEqual(extractTables('<p>nothing here</p>'), []);
});
});
describe('stripHtml', () => {
it('removes tags, decodes entities, collapses whitespace', () => {
assert.equal(stripHtml('<p>Hello & <b>world</b></p>'), 'Hello & world');
});
it('decodes numeric entities (Hungarian á é ő)', () => {
assert.equal(stripHtml('Lakás biztosítás'), 'Lakás biztosítás');
assert.equal(stripHtml('Mezőföldi'), 'Mezőföldi');
});
it('decodes hex entities', () => {
assert.equal(stripHtml('áé'), 'áé');
});
});
describe('strikeHtml', () => {
it('wraps plain content in <s>', () => {
assert.equal(strikeHtml('<p>Paid</p>'), '<s><p>Paid</p></s>');
});
it('is idempotent when already struck', () => {
assert.equal(strikeHtml('<s>already</s>'), '<s>already</s>');
});
});
describe('parseNumber', () => {
it('parses Hungarian-style "1 234,56 Ft"', () => {
assert.equal(parseNumber('1 234,56 Ft'), 1234.56);
});
it('parses plain integers', () => {
assert.equal(parseNumber('13 519'), 13519);
});
it('parses US-style "1,234.56"', () => {
assert.equal(parseNumber('1,234.56'), 1234.56);
});
it('returns NaN for non-numeric text', () => {
assert.ok(Number.isNaN(parseNumber('paid')));
});
it('parses "k" suffix as thousands (rounded OneNote style)', () => {
assert.equal(parseNumber('5k'), 5000);
assert.equal(parseNumber('508k'), 508_000);
assert.equal(parseNumber('1094k'), 1_094_000);
});
it('parses "M" suffix as millions', () => {
assert.equal(parseNumber('1.2M'), 1_200_000);
assert.equal(parseNumber('2m'), 2_000_000);
});
it('ignores "k" inside longer words (e.g. "kb", "Ft")', () => {
// "13 519" + "Ft" — no suffix, Ft stripped
assert.equal(parseNumber('13 519 Ft'), 13_519);
});
});
describe('buildPatch helpers', () => {
it('buildPatchReplace returns a single replace command array', () => {
assert.deepEqual(
buildPatchReplace('c32', '3 234,56 Ft'),
[{ target: 'c32', action: 'replace', content: '3 234,56 Ft' }],
);
});
it('buildPatchAppend returns a single append command array', () => {
assert.deepEqual(
buildPatchAppend('body', '<p>new</p>'),
[{ target: 'body', action: 'append', content: '<p>new</p>' }],
);
});
});