mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Publish corrected 0.4.1 pack, preserve old cactus objects as a read-only alias, and cover the catalog, renderers, editor, PDF and visual scene. Issue: #606 User-Visible: yes
84 lines
5.9 KiB
JavaScript
84 lines
5.9 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { readdirSync, readFileSync } from 'node:fs';
|
||
import { joinFurniturePaths } from '../scripts/furniture-path-join.mjs';
|
||
import { svgPathBounds } from '../scripts/svg-path-bounds.mjs';
|
||
|
||
// #584. У каждого исходного `<path>` своя текущая точка, и начинается она в
|
||
// нуле. Генератор склеивал строки `d` простым `join(' ')`, поэтому второй путь,
|
||
// начинающийся относительной `m`, продолжал координаты первого. Рисунок уезжал
|
||
// молча: `viewBox` совпадал, метаданные совпадали, тесты были зелёными.
|
||
|
||
test('AC4: каждый исходный путь начинает свою текущую точку заново', () => {
|
||
assert.equal(joinFurniturePaths(['M3.765 40h2', 'm56.26 26.96l-3-3']),
|
||
'M3.765 40h2 M 56.26 26.96 l-3-3');
|
||
});
|
||
|
||
test('AC4: неявные пары после относительного m остаются относительными линиями', () => {
|
||
// `m10 20 5 6` — это перенос и ОТНОСИТЕЛЬНАЯ линия. Без явной `l` пара
|
||
// досталась бы канонизированной `M` и стала бы абсолютной точкой (5, 6).
|
||
assert.equal(joinFurniturePaths(['m10 20 5 6-2-3z']), 'M 10 20 l 5 6-2-3z');
|
||
assert.deepEqual(svgPathBounds(joinFurniturePaths(['m10 20 5 6-2-3z'])),
|
||
svgPathBounds('M10 20 l5 6-2-3z'));
|
||
});
|
||
|
||
test('AC4: относительные команды внутри пути смысла не меняют', () => {
|
||
assert.equal(joinFurniturePaths(['m1e1,-2.5m.5.6']), 'M 1e1 -2.5 m.5.6');
|
||
assert.equal(joinFurniturePaths(['M10 20 5 6Z']), 'M10 20 5 6Z');
|
||
});
|
||
|
||
test('AC4: путь без начального moveto — ошибка, а не тихая склейка', () => {
|
||
assert.throws(() => joinFurniturePaths(['L1 2']), /must begin with M or m/);
|
||
assert.throws(() => joinFurniturePaths([]), /has no paths/);
|
||
assert.throws(() => joinFurniturePaths(null), /has no paths/);
|
||
});
|
||
|
||
// #593/#606: текущий пакет 0.4.1 рисует каждый символ ОДНИМ путём — многопутёвых файлов,
|
||
// на которых дефект #584 был виден в числах, в нём не осталось. Поэтому
|
||
// свидетель разделён надвое: факт о поставке проверяется на поставке, а
|
||
// арифметика склейки — на реальных байтах пакета плюс относительный путь,
|
||
// который такая поставка однажды снова принесёт. Удалять свидетеля нельзя:
|
||
// склейка живёт в генераторе и обязана остаться правильной.
|
||
test('#606: в поставке 0.4.1 каждый символ — один путь, и склейка на нём тождественна', () => {
|
||
const dir = new URL('../assets/furniture/houseplan-0.4.1/svg/', import.meta.url);
|
||
let checked = 0;
|
||
for (const kind of ['menu', 'plan']) {
|
||
for (const file of readdirSync(new URL(`${kind}/`, dir))) {
|
||
const source = readFileSync(new URL(`${kind}/${file}`, dir), 'utf8');
|
||
const paths = [...source.matchAll(/(?:^|\s)d="([^"]*)"/g)].map((match) => match[1]);
|
||
assert.equal(paths.length, 1, `${kind}/${file}: ожидался ровно один путь`);
|
||
assert.equal(joinFurniturePaths(paths), paths[0].trim(),
|
||
`${kind}/${file}: склейка одного пути обязана быть тождественной`);
|
||
checked += 1;
|
||
}
|
||
}
|
||
assert.equal(checked, 93, 'проверены все файлы поставки');
|
||
});
|
||
|
||
test('AC4: на реальных байтах пакета склейка не даёт второму пути уехать', () => {
|
||
// Первый путь — настоящий, из поставки; второй начинается относительной `m`,
|
||
// как это было у `stairs` и `tv` в 0.3.0. Простой `join(' ')` продолжил бы
|
||
// координаты первого, и деталь ушла бы далеко за `viewBox` 110 × 110.
|
||
const source = readFileSync(
|
||
new URL('../assets/furniture/houseplan-0.4.1/svg/menu/stairs.svg', import.meta.url), 'utf8');
|
||
const real = [...source.matchAll(/(?:^|\s)d="([^"]*)"/g)].map((match) => match[1])[0];
|
||
const tail = 'm100 100 h5';
|
||
const own = svgPathBounds(`M ${tail.slice(1)}`);
|
||
assert.deepEqual([own.minX, own.minY, own.maxX, own.maxY], [100, 100, 105, 100],
|
||
'хвост сам по себе лежит там, где написан');
|
||
|
||
const naive = svgPathBounds([real, tail].join(' '));
|
||
const fixed = svgPathBounds(joinFurniturePaths([real, tail]));
|
||
// Простая конкатенация продолжает координаты первого пути: хвост уезжает
|
||
// ровно на его конечную точку, и обе стороны уходят за 110.
|
||
assert.ok(naive.maxX > 110 && naive.maxY > 110,
|
||
`прежняя склейка обязана уводить хвост за viewBox: ${naive.maxX} × ${naive.maxY}`);
|
||
// Правильная склейка кладёт хвост туда, где он написан, не трогая первый путь.
|
||
const realBounds = svgPathBounds(real);
|
||
assert.equal(fixed.maxX, Math.max(realBounds.maxX, own.maxX));
|
||
assert.equal(fixed.maxY, Math.max(realBounds.maxY, own.maxY));
|
||
assert.equal(fixed.minX, Math.min(realBounds.minX, own.minX));
|
||
assert.equal(fixed.minY, Math.min(realBounds.minY, own.minY));
|
||
assert.ok(fixed.maxX <= 110 && fixed.maxY <= 110, 'рисунок обязан лежать внутри viewBox');
|
||
});
|