|
1 | 1 | import { describe, it, expect } from "vitest"; |
2 | 2 | import { generateSetupScript, SETUP_SCRIPT_FIELDS } from "./zplSetupScript"; |
3 | 3 | import { parseZPL } from "./zplParser"; |
4 | | -import type { LabelConfig } from "../types/ObjectType"; |
| 4 | +import { labelConfigSchema, type LabelConfig } from "../types/ObjectType"; |
5 | 5 |
|
6 | 6 | const base: LabelConfig = { widthMm: 100, heightMm: 50, dpmm: 8 }; |
7 | 7 |
|
@@ -94,12 +94,12 @@ describe("generateSetupScript — output shape", () => { |
94 | 94 | .toBe("^XA\n^SEE:UHANGUL.DAT\n^XZ"); |
95 | 95 | }); |
96 | 96 |
|
97 | | - it("rejects empty-string encodingTable at the schema layer", async () => { |
| 97 | + it("rejects empty-string encodingTable at the schema layer", () => { |
98 | 98 | // The schema's min(1) makes empty string unreachable through |
99 | 99 | // labelConfigSchema.parse, so the generator no longer carries |
100 | 100 | // its own empty-string defense. This test pins the schema |
101 | 101 | // contract so a future schema loosening would also surface here. |
102 | | - const { labelConfigSchema } = await import("../types/ObjectType"); |
| 102 | + |
103 | 103 | expect(() => labelConfigSchema.parse({ ...base, encodingTable: "" })).toThrow(); |
104 | 104 | }); |
105 | 105 |
|
@@ -137,16 +137,68 @@ describe("generateSetupScript — output shape", () => { |
137 | 137 | expect(parsed.encodingTable).toBe(path); |
138 | 138 | }); |
139 | 139 |
|
140 | | - it("rejects ZPL injection attempts in ^SE values at the schema layer", async () => { |
| 140 | + it("rejects ZPL injection attempts in ^SE values at the schema layer", () => { |
141 | 141 | // `^SE${value}` interpolation makes this a real injection |
142 | 142 | // surface for imported / pasted ZPL. Schema must reject any |
143 | 143 | // value carrying the command-introducer chars. |
144 | | - const { labelConfigSchema } = await import("../types/ObjectType"); |
| 144 | + |
145 | 145 | expect(() => labelConfigSchema.parse({ ...base, encodingTable: "^SDXY" })).toThrow(); |
146 | 146 | expect(() => labelConfigSchema.parse({ ...base, encodingTable: "E:F.DAT~JR" })).toThrow(); |
147 | 147 | expect(() => labelConfigSchema.parse({ ...base, encodingTable: "E:F.DAT\n^MD30" })).toThrow(); |
148 | 148 | }); |
149 | 149 |
|
| 150 | + it("emits ^KN with name only when description is unset", () => { |
| 151 | + expect(generateSetupScript({ ...base, printerName: "Lab-01" })) |
| 152 | + .toBe("^XA\n^KNLab-01\n^XZ"); |
| 153 | + }); |
| 154 | + |
| 155 | + it("folds ^KN description into the same line when both are set", () => { |
| 156 | + expect(generateSetupScript({ ...base, printerName: "Lab-01", printerDescription: "front desk" })) |
| 157 | + .toBe("^XA\n^KNLab-01,front desk\n^XZ"); |
| 158 | + }); |
| 159 | + |
| 160 | + it("skips ^KN emit when only the description is set (name is the anchor)", () => { |
| 161 | + expect(generateSetupScript({ ...base, printerDescription: "orphan" })).toBe(""); |
| 162 | + }); |
| 163 | + |
| 164 | + it("skips ^KN emit when name is whitespace-only after trim", () => { |
| 165 | + // Schema's min(1) accepts " "; the emit-side trim-guard |
| 166 | + // prevents an empty ^KN from going out and silently dropping |
| 167 | + // both fields on re-import. |
| 168 | + expect(generateSetupScript({ ...base, printerName: " " })).toBe(""); |
| 169 | + expect(generateSetupScript({ ...base, printerName: " ", printerDescription: "desc" })) |
| 170 | + .toBe(""); |
| 171 | + }); |
| 172 | + |
| 173 | + it("round-trips ^KN via the parser without loss", () => { |
| 174 | + const orig: LabelConfig = { |
| 175 | + ...base, |
| 176 | + printerName: "Lab-01", |
| 177 | + printerDescription: "front desk", |
| 178 | + }; |
| 179 | + const { labelConfig: parsed } = parseZPL(generateSetupScript(orig)); |
| 180 | + expect(parsed.printerName).toBe("Lab-01"); |
| 181 | + expect(parsed.printerDescription).toBe("front desk"); |
| 182 | + }); |
| 183 | + |
| 184 | + it("rejects ZPL injection attempts in ^KN values at the schema layer", () => { |
| 185 | + |
| 186 | + expect(() => labelConfigSchema.parse({ ...base, printerName: "^SD30" })).toThrow(); |
| 187 | + expect(() => labelConfigSchema.parse({ ...base, printerDescription: "front\n^MD0" })).toThrow(); |
| 188 | + }); |
| 189 | + |
| 190 | + it("rejects ^KN names longer than the 16-char spec cap", () => { |
| 191 | + |
| 192 | + expect(() => labelConfigSchema.parse({ ...base, printerName: "abcdefghij0123456" })).toThrow(); |
| 193 | + }); |
| 194 | + |
| 195 | + it("rejects commas in ^KN / ^SE free-string fields (would round-trip-split)", () => { |
| 196 | + |
| 197 | + expect(() => labelConfigSchema.parse({ ...base, printerName: "Lab,01" })).toThrow(); |
| 198 | + expect(() => labelConfigSchema.parse({ ...base, printerDescription: "front,desk" })).toThrow(); |
| 199 | + expect(() => labelConfigSchema.parse({ ...base, encodingTable: "E:F.DAT,extra" })).toThrow(); |
| 200 | + }); |
| 201 | + |
150 | 202 | it("parser drops ^SE values carrying ZPL command-introducer chars", () => { |
151 | 203 | // The parser writes into the store without re-running the |
152 | 204 | // schema; the dangerous-char check mirrors the schema so an |
@@ -176,6 +228,8 @@ describe("generateSetupScript — output shape", () => { |
176 | 228 | "printerLocale", |
177 | 229 | "encodingTable", |
178 | 230 | "zplMode", |
| 231 | + "printerName", |
| 232 | + "printerDescription", |
179 | 233 | ]); |
180 | 234 | }); |
181 | 235 |
|
|
0 commit comments