@@ -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 },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user