Files
houseplan-card/test/pdf-compass.test.mjs
Matysh 15b060ac38 fix: polish PDF export
Issue: #482
User-Visible: yes
2026-09-07 11:06:28 +03:00

93 lines
3.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import {
PDF_COMPASS_PATHS,
PDF_COMPASS_VIEWBOX_SIZE,
pdfCompassOps,
pdfCompassTransform,
} from '../test-build/pdf/pdf-compass.js';
const EXPECTED_PATHS = [
'M20.82,15.31h0L10.46,9c-.46-.26-1.11.37-.86.84l6.15,10.56,10.56,6.15a.66.66,0,0,0,.84-.86Zm-4,4,3-3,4.55,7.44Z',
'M18,2A16,16,0,1,0,34,18,16,16,0,0,0,18,2Zm1,29.95V29.53H17v2.42A14,14,0,0,1,4.05,19H6.47V17H4.05A14,14,0,0,1,17,4.05V6.47h2V4.05A14,14,0,0,1,31.95,17H29.53v2h2.42A14,14,0,0,1,19,31.95Z',
];
const apply = (matrix, x, y) => [
matrix.a * x + matrix.c * y + matrix.e,
matrix.b * x + matrix.d * y + matrix.f,
];
const closeTo = (actual, expected) => {
assert.ok(Math.abs(actual - expected) < 1e-10, `${actual} != ${expected}`);
};
test('compass keeps the two canonical Clarity paths pinned to the reviewed commit', () => {
assert.deepEqual(PDF_COMPASS_PATHS, EXPECTED_PATHS);
assert.equal(PDF_COMPASS_VIEWBOX_SIZE, 36);
});
test('compass transform preserves its center, size and cardinal north semantics', () => {
const center = [120, 80];
const cardinal = [
{ northDeg: 0, direction: [0, -1] },
{ northDeg: 90, direction: [1, 0] },
{ northDeg: 180, direction: [0, 1] },
{ northDeg: 270, direction: [-1, 0] },
];
for (const { northDeg, direction } of cardinal) {
const matrix = pdfCompassTransform({ centerX: center[0], centerY: center[1], size: 36, northDeg });
const transformedCenter = apply(matrix, 18, 18);
closeTo(transformedCenter[0], center[0]);
closeTo(transformedCenter[1], center[1]);
// The canonical diagonal from (18,18) to (17,17) is the needle's north axis.
const transformedNorth = apply(matrix, 17, 17);
const dx = transformedNorth[0] - center[0];
const dy = transformedNorth[1] - center[1];
closeTo(dx / Math.SQRT2, direction[0]);
closeTo(dy / Math.SQRT2, direction[1]);
}
});
test('compass operations are finite, deterministic and normalize equivalent angles', () => {
const placement = { centerX: 70, centerY: 45, size: 24, northDeg: -90 };
const first = pdfCompassOps(placement);
const second = pdfCompassOps({ ...placement, northDeg: 270 });
assert.deepEqual(first, second);
assert.ok(first.length > 20);
assert.equal(first.filter((entry) => entry.op === 'Z').length, 4);
for (const entry of first) {
for (const value of Object.values(entry).filter((item) => typeof item === 'number')) {
assert.ok(Number.isFinite(value));
}
}
assert.deepEqual(
pdfCompassTransform({ centerX: 0, centerY: 0, size: 18, northDeg: 360 }),
pdfCompassTransform({ centerX: 0, centerY: 0, size: 18, northDeg: 0 }),
);
});
test('compass rejects non-finite placement and non-positive sizes', () => {
assert.throws(
() => pdfCompassTransform({ centerX: 0, centerY: 0, size: 0, northDeg: 0 }),
{ name: 'RangeError' },
);
assert.throws(
() => pdfCompassTransform({ centerX: Number.NaN, centerY: 0, size: 10, northDeg: 0 }),
{ name: 'RangeError' },
);
});
test('tracked notice preserves Clarity source, commit and MIT attribution', async () => {
const notice = await readFile(new URL('../THIRD_PARTY_NOTICES.md', import.meta.url), 'utf8');
const shippedNotice = await readFile(
new URL('../custom_components/houseplan/THIRD_PARTY_NOTICES.md', import.meta.url), 'utf8',
);
assert.match(notice, /bf6bdd0dd3f247f1a320d44d13fecdeda18c071c/);
assert.match(notice, /Copyright \(c\) 2018 VMware, Inc\./);
assert.match(notice, /MIT License/);
assert.match(notice, /copies or substantial portions of the Software/);
assert.equal(shippedNotice, notice, 'the HACS zip ships the exact reviewed notice');
});