Release Label Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 11:52:44 +02:00
parent 4e3df8d4a4
commit d3e43bea98
37 changed files with 1733 additions and 119 deletions
+4 -4
View File
@@ -60,17 +60,17 @@ test("merges inert CSV and exports vector SVG and deterministic ZIP", async ({
"merge-report.json",
]);
expect(JSON.parse(strFromU8(files["merge-report.json"]!))).toMatchObject({
application: "Label Tools 0.1.0",
application: "Label Tools 0.2.0",
labels: 2,
});
});
test("exports a local PDF with an explicit raster boundary", async ({
test("exports a local vector PDF with an explicit font boundary", async ({
page,
}) => {
await page.goto("/deep/nested/label/");
const pending = page.waitForEvent("download", { timeout: 120_000 });
await page.getByRole("button", { name: "PDF (144 DPI)" }).click();
await page.getByRole("button", { name: "Vector PDF" }).click();
const download = await pending;
expect(download.suggestedFilename()).toBe("label-tools-merge.pdf");
const bytes = await readFile((await download.path())!);
@@ -107,7 +107,7 @@ test("integrates help, dark theme, PWA identity and hardened headers", async ({
const manifest = await request.get("/deep/nested/label/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.label-tools",
version: "0.1.0",
version: "0.2.0",
privacy: { processing: "local", telemetry: false },
});
});
+18
View File
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";
test("keeps the primary workspace inside a narrow viewport", async ({
page,
}) => {
await page.goto("/deep/nested/label/");
await expect(page.locator("main").first()).toBeVisible();
await expect(
page.locator("main .loading, main .workbench-loading"),
).toHaveCount(0);
const widths = await page.evaluate(() => ({
content: document.documentElement.scrollWidth,
viewport: document.documentElement.clientWidth,
}));
expect(widths.viewport).toBeLessThanOrEqual(430);
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
});
+52
View File
@@ -0,0 +1,52 @@
import { PDFDocument } from "pdf-lib";
import { describe, expect, it } from "vitest";
import { createPdf } from "../../src/labels/pdf";
import { renderLabelPages } from "../../src/labels/render";
import { STOCKS } from "../../src/labels/stocks";
import type { RenderOptions } from "../../src/labels/types";
describe("vector PDF export", () => {
it("keeps the physical page geometry and emits vector page operators", async () => {
const stock = STOCKS[0]!;
const options: RenderOptions = {
stock,
calibration: { offsetXmm: 0, offsetYmm: 0, scaleX: 1, scaleY: 1 },
layout: "address",
mapping: {
title: "name",
subtitle: "subtitle",
body: "body",
barcode: "code",
image: "image",
},
barcodeFormat: "qrcode",
includeBarcodeText: false,
fontFamily: "Arial, Helvetica, sans-serif",
records: [
{
name: "Vector label",
subtitle: "Local",
body: "Physical units",
code: "SAFE-1",
},
],
images: [],
defaultImage: "",
cutMarks: true,
registrationMarks: true,
startPosition: 0,
};
const blob = await createPdf(renderLabelPages(options), stock);
expect(blob.type).toBe("application/pdf");
const document = await PDFDocument.load(await blob.arrayBuffer());
expect(document.getPageCount()).toBe(1);
expect(document.getPage(0).getWidth()).toBeCloseTo(
(stock.pageWidthMm * 72) / 25.4,
4,
);
expect(document.getPage(0).getHeight()).toBeCloseTo(
(stock.pageHeightMm * 72) / 25.4,
4,
);
});
});
+14
View File
@@ -6,6 +6,7 @@ import {
toMillimetres,
} from "../../src/labels/stocks";
import type { RenderOptions } from "../../src/labels/types";
import { defaultTemplate } from "../../src/labels/template";
function options(): RenderOptions {
return {
@@ -67,4 +68,17 @@ describe("label SVG renderer", () => {
expect(fromMillimetres(25.4, "pt")).toBeCloseTo(72);
expect(toMillimetres(fromMillimetres(42, "in"), "in")).toBeCloseTo(42);
});
it("renders merged designer elements and preserves barcode quiet space", () => {
const input = options();
input.layout = "designer";
input.template = defaultTemplate(
input.stock.labelWidthMm,
input.stock.labelHeightMm,
);
const page = renderLabelPages(input).pages[0]!;
expect(page).toContain("<script>aler");
expect(page).toContain("designer-0-");
expect(page).not.toContain("<script>");
});
});
+39
View File
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import {
defaultTemplate,
exportTemplate,
importTemplate,
mergeContent,
validateTemplate,
} from "../../src/labels/template";
describe("label template model", () => {
it("round-trips a bounded reusable template", () => {
const template = defaultTemplate(63.5, 33.9);
expect(importTemplate(exportTemplate(template))).toEqual(template);
expect(mergeContent("{{name}} / {{missing}}", { name: "Ada" })).toBe(
"Ada / ",
);
});
it("reports quiet zones and rejects out-of-bounds elements", () => {
const template = defaultTemplate(40, 20);
const barcode = template.elements.find(
(element) => element.kind === "barcode",
)!;
if (barcode.kind !== "barcode") throw new Error("fixture mismatch");
barcode.quietZoneMm = 0;
barcode.xMm = 100;
const issues = validateTemplate(template);
expect(issues.some((issue) => issue.severity === "warning")).toBe(true);
expect(issues.some((issue) => issue.severity === "error")).toBe(true);
});
it("rejects malformed imported element collections without iterating them", () => {
const malformed = JSON.stringify({
...defaultTemplate(40, 20),
elements: { unexpected: true },
});
expect(() => importTemplate(malformed)).toThrow(/at most 100 elements/iu);
});
});