@nfcard/validation 0.22.1 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nfcard/validation",
3
- "version": "0.22.1",
3
+ "version": "0.22.2",
4
4
  "description": "Shared Zod validation schemas for the NFCard product family.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "zod": "^3.24.0",
20
- "@nfcard/types": "0.16.0"
20
+ "@nfcard/types": "0.16.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsup": "^8.0.0"
@@ -270,79 +270,79 @@ describe('per-element override fields survive validation (#1  P0 contract)', ()
270
270
  expect(a).toMatchObject({ rasterBgDataUrl: 'data:,bg', rasterAspect: 1.5 })
271
271
  })
272
272
  })
273
-
274
- // ── NFCARD-561: qr.mode makes "point to profile" data, not a string match ──
275
- //
276
- // The API used to detect it with a QR_PROFILE_TARGETS sentinel set
277
- // ('' / https://nfcard.hu / http://nfcard.hu). Any near-miss — a www. host, a
278
- // trailing path, a stale buyer-typed value — printed verbatim, and a printed
279
- // card cannot be corrected afterwards.
280
-
281
- describe('qr.mode (NFCARD-561)', () => {
282
- it("accepts mode 'redirect' with NO url — the target is resolved at card creation", () => {
283
- const res = cardDesignSchema.safeParse(
284
- withElements([chipEl(), { ...qrEl(), url: undefined, mode: 'redirect' }]),
285
- )
286
- expect(res.success).toBe(true)
287
- })
288
-
289
- it("accepts mode 'redirect' with an empty-string url (the pre-561 free-layout spelling)", () => {
290
- const res = cardDesignSchema.safeParse(
291
- withElements([chipEl(), { ...qrEl(), url: '', mode: 'redirect' }]),
292
- )
293
- expect(res.success).toBe(true)
294
- })
295
-
296
- it("accepts mode 'raw' alongside a buyer-typed url", () => {
297
- const res = cardDesignSchema.safeParse(
298
- withElements([chipEl(), { ...qrEl(), url: 'https://acme.example/team', mode: 'raw' }]),
299
- )
300
- expect(res.success).toBe(true)
301
- })
302
-
303
- it('rejects an unknown mode', () => {
304
- const res = cardDesignSchema.safeParse(
305
- withElements([chipEl(), { ...qrEl(), mode: 'profile' }]),
306
- )
307
- expect(res.success).toBe(false)
308
- })
309
-
310
- it('still rejects a malformed url whatever the mode', () => {
311
- const res = cardDesignSchema.safeParse(
312
- withElements([chipEl(), { ...qrEl(), url: 'not-a-url', mode: 'raw' }]),
313
- )
314
- expect(codes(res)).toContain('invalidUrl')
315
- })
316
-
317
- it('a mode-less QR element still validates (pre-561 designs are untouched)', () => {
318
- expect(cardDesignSchema.safeParse(withElements([chipEl(), qrEl()])).success).toBe(true)
319
- })
320
-
321
- it('carries mode through the legacy qr channel too', () => {
322
- const parsed = cardDesignSchema.parse({
323
- ...legacyDesign,
324
- qr: { enabled: true, mode: 'redirect' },
325
- }) as { qr: Record<string, unknown> }
326
- expect(parsed.qr).toMatchObject({ enabled: true, mode: 'redirect' })
327
- })
328
- })
329
-
330
- describe('printability — a redirect QR needs no url (NFCARD-561)', () => {
331
- const at = (over = {}) =>
332
- ({ ...qrEl(), transform: { xMm: 0, yMm: 0, rotationDeg: 0, scale: 1, z: 2 }, ...over }) as never
333
-
334
- it("does not raise qrEmptyUrl for mode 'redirect' with no url", () => {
335
- const codes = elementPrintability(at({ url: undefined, mode: 'redirect' })).map((v) => v.code)
336
- expect(codes).not.toContain('qrEmptyUrl')
337
- })
338
-
339
- it("still raises qrEmptyUrl for mode 'raw' with no url", () => {
340
- const codes = elementPrintability(at({ url: '', mode: 'raw' })).map((v) => v.code)
341
- expect(codes).toContain('qrEmptyUrl')
342
- })
343
-
344
- it('still raises qrEmptyUrl for a mode-less QR with no url (legacy behaviour)', () => {
345
- const codes = elementPrintability(at({ url: '' })).map((v) => v.code)
346
- expect(codes).toContain('qrEmptyUrl')
347
- })
348
- })
273
+
274
+ // ── NFCARD-561: qr.mode makes "point to profile" data, not a string match ──
275
+ //
276
+ // The API used to detect it with a QR_PROFILE_TARGETS sentinel set
277
+ // ('' / https://nfcard.hu / http://nfcard.hu). Any near-miss — a www. host, a
278
+ // trailing path, a stale buyer-typed value — printed verbatim, and a printed
279
+ // card cannot be corrected afterwards.
280
+
281
+ describe('qr.mode (NFCARD-561)', () => {
282
+ it("accepts mode 'redirect' with NO url — the target is resolved at card creation", () => {
283
+ const res = cardDesignSchema.safeParse(
284
+ withElements([chipEl(), { ...qrEl(), url: undefined, mode: 'redirect' }]),
285
+ )
286
+ expect(res.success).toBe(true)
287
+ })
288
+
289
+ it("accepts mode 'redirect' with an empty-string url (the pre-561 free-layout spelling)", () => {
290
+ const res = cardDesignSchema.safeParse(
291
+ withElements([chipEl(), { ...qrEl(), url: '', mode: 'redirect' }]),
292
+ )
293
+ expect(res.success).toBe(true)
294
+ })
295
+
296
+ it("accepts mode 'raw' alongside a buyer-typed url", () => {
297
+ const res = cardDesignSchema.safeParse(
298
+ withElements([chipEl(), { ...qrEl(), url: 'https://acme.example/team', mode: 'raw' }]),
299
+ )
300
+ expect(res.success).toBe(true)
301
+ })
302
+
303
+ it('rejects an unknown mode', () => {
304
+ const res = cardDesignSchema.safeParse(
305
+ withElements([chipEl(), { ...qrEl(), mode: 'profile' }]),
306
+ )
307
+ expect(res.success).toBe(false)
308
+ })
309
+
310
+ it('still rejects a malformed url whatever the mode', () => {
311
+ const res = cardDesignSchema.safeParse(
312
+ withElements([chipEl(), { ...qrEl(), url: 'not-a-url', mode: 'raw' }]),
313
+ )
314
+ expect(codes(res)).toContain('invalidUrl')
315
+ })
316
+
317
+ it('a mode-less QR element still validates (pre-561 designs are untouched)', () => {
318
+ expect(cardDesignSchema.safeParse(withElements([chipEl(), qrEl()])).success).toBe(true)
319
+ })
320
+
321
+ it('carries mode through the legacy qr channel too', () => {
322
+ const parsed = cardDesignSchema.parse({
323
+ ...legacyDesign,
324
+ qr: { enabled: true, mode: 'redirect' },
325
+ }) as { qr: Record<string, unknown> }
326
+ expect(parsed.qr).toMatchObject({ enabled: true, mode: 'redirect' })
327
+ })
328
+ })
329
+
330
+ describe('printability — a redirect QR needs no url (NFCARD-561)', () => {
331
+ const at = (over = {}) =>
332
+ ({ ...qrEl(), transform: { xMm: 0, yMm: 0, rotationDeg: 0, scale: 1, z: 2 }, ...over }) as never
333
+
334
+ it("does not raise qrEmptyUrl for mode 'redirect' with no url", () => {
335
+ const codes = elementPrintability(at({ url: undefined, mode: 'redirect' })).map((v) => v.code)
336
+ expect(codes).not.toContain('qrEmptyUrl')
337
+ })
338
+
339
+ it("still raises qrEmptyUrl for mode 'raw' with no url", () => {
340
+ const codes = elementPrintability(at({ url: '', mode: 'raw' })).map((v) => v.code)
341
+ expect(codes).toContain('qrEmptyUrl')
342
+ })
343
+
344
+ it('still raises qrEmptyUrl for a mode-less QR with no url (legacy behaviour)', () => {
345
+ const codes = elementPrintability(at({ url: '' })).map((v) => v.code)
346
+ expect(codes).toContain('qrEmptyUrl')
347
+ })
348
+ })