@nfcard/validation 0.22.0 → 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 +15 -21
- package/src/elements.test.ts +76 -76
package/package.json
CHANGED
|
@@ -1,45 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nfcard/validation",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"description": "Shared Zod validation schemas for the NFCard product family.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"files": [
|
|
12
15
|
"dist",
|
|
13
16
|
"src"
|
|
14
17
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"test": "vitest run",
|
|
17
|
-
"test:watch": "vitest",
|
|
18
|
-
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
19
|
-
"prepublishOnly": "pnpm build"
|
|
20
|
-
},
|
|
21
18
|
"dependencies": {
|
|
22
19
|
"zod": "^3.24.0",
|
|
23
|
-
"@nfcard/types": "
|
|
20
|
+
"@nfcard/types": "0.16.1"
|
|
24
21
|
},
|
|
25
22
|
"devDependencies": {
|
|
26
23
|
"tsup": "^8.0.0"
|
|
27
24
|
},
|
|
28
25
|
"publishConfig": {
|
|
29
26
|
"access": "public",
|
|
30
|
-
"registry": "https://registry.npmjs.org/"
|
|
31
|
-
"main": "./dist/index.js",
|
|
32
|
-
"types": "./dist/index.d.ts",
|
|
33
|
-
"exports": {
|
|
34
|
-
".": {
|
|
35
|
-
"import": "./dist/index.js",
|
|
36
|
-
"types": "./dist/index.d.ts"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
39
28
|
},
|
|
40
29
|
"repository": {
|
|
41
30
|
"type": "git",
|
|
42
31
|
"url": "https://bitbucket.org/fox-hole/nfcard-shared.git",
|
|
43
32
|
"directory": "validation"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"build": "tsup src/index.ts --format esm --dts --clean"
|
|
44
38
|
}
|
|
45
|
-
}
|
|
39
|
+
}
|
package/src/elements.test.ts
CHANGED
|
@@ -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
|
+
})
|