@nfcard/validation 0.4.0 → 0.6.0

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/dist/index.js CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nfcard/validation",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
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.4.0"
20
+ "@nfcard/types": "0.5.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsup": "^8.0.0"
package/src/index.test.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import {
3
+ cardDesignSchema,
3
4
  cardElementsSchema,
4
5
  comboIdSchema,
5
6
  configurationCreateSchema,
@@ -482,12 +483,21 @@ describe('profileCreateSchema', () => {
482
483
  expect(r.success).toBe(false);
483
484
  });
484
485
 
485
- it('rejects missing label', () => {
486
+ it('accepts a missing label (NFCARD-128 — blank profile, label set later in the editor)', () => {
486
487
  const r = profileCreateSchema.safeParse({
487
488
  userData: { ...validUserData, email: '' },
488
489
  digitalConfig: validDigitalConfig,
489
490
  });
490
- expect(r.success).toBe(false);
491
+ expect(r.success).toBe(true);
492
+ });
493
+
494
+ it('accepts an empty label', () => {
495
+ const r = profileCreateSchema.safeParse({
496
+ label: '',
497
+ userData: { ...validUserData, email: '' },
498
+ digitalConfig: validDigitalConfig,
499
+ });
500
+ expect(r.success).toBe(true);
491
501
  });
492
502
  });
493
503
 
@@ -676,3 +686,92 @@ describe('hubConfigSchema', () => {
676
686
  expect(hubConfigSchema.safeParse({ avatarUrl: '' }).success).toBe(true);
677
687
  });
678
688
  });
689
+
690
+ // ── cardDesign (cardgen interop, FOXHOLE-810) ───────────────────
691
+
692
+ const validCardDesign = {
693
+ material: '3dprint_standard' as const,
694
+ designType: 'custom' as const,
695
+ thicknessMm: 1.8,
696
+ pattern: {
697
+ family: 'spiral' as const,
698
+ variant: 'pulse' as const,
699
+ density: 0.55,
700
+ holeMinMm: 0.6,
701
+ holeMaxMm: 2.0,
702
+ intensity: 0.5,
703
+ cellShape: 0 as const,
704
+ cellRotationDeg: 0,
705
+ chipShape: 0 as const,
706
+ chipRotationDeg: 0,
707
+ },
708
+ layout: { chipAnchor: { col: 0 as const, row: 1 as const } },
709
+ filaments: { background: '#F2F1ED', text: '#1A1A1A', accent: '#3FA34D' },
710
+ };
711
+
712
+ const valid3dPhysicalConfig = {
713
+ material: '3dprint_standard' as const,
714
+ colour: '#F2F1ED',
715
+ comboId: 'A1' as const,
716
+ frontElements: validCardElements,
717
+ backElements: validCardElements,
718
+ };
719
+
720
+ describe('cardDesignSchema', () => {
721
+ it('accepts a valid 3D card design', () => {
722
+ expect(cardDesignSchema.safeParse(validCardDesign).success).toBe(true);
723
+ });
724
+
725
+ it('accepts a solid card (pattern: null)', () => {
726
+ expect(
727
+ cardDesignSchema.safeParse({ ...validCardDesign, pattern: null }).success,
728
+ ).toBe(true);
729
+ });
730
+
731
+ it('rejects plastic (cardgen has no FDM mesh for PVC)', () => {
732
+ expect(
733
+ cardDesignSchema.safeParse({ ...validCardDesign, material: 'plastic' }).success,
734
+ ).toBe(false);
735
+ });
736
+
737
+ it('rejects holeMin > holeMax', () => {
738
+ const bad = { ...validCardDesign, pattern: { ...validCardDesign.pattern, holeMinMm: 3, holeMaxMm: 1 } };
739
+ expect(cardDesignSchema.safeParse(bad).success).toBe(false);
740
+ });
741
+
742
+ it('rejects an unknown pattern family', () => {
743
+ const bad = { ...validCardDesign, pattern: { ...validCardDesign.pattern, family: 'mosaic' } };
744
+ expect(cardDesignSchema.safeParse(bad).success).toBe(false);
745
+ });
746
+
747
+ it('rejects a chip anchor outside 0..2', () => {
748
+ const bad = { ...validCardDesign, layout: { chipAnchor: { col: 3, row: 1 } } };
749
+ expect(cardDesignSchema.safeParse(bad).success).toBe(false);
750
+ });
751
+
752
+ it('rejects a non-hex filament', () => {
753
+ const bad = { ...validCardDesign, filaments: { ...validCardDesign.filaments, accent: 'green' } };
754
+ expect(cardDesignSchema.safeParse(bad).success).toBe(false);
755
+ });
756
+ });
757
+
758
+ describe('physicalConfig.cardDesign (nested, not stripped)', () => {
759
+ it('accepts a 3D physicalConfig carrying a matching cardDesign', () => {
760
+ const res = physicalConfigSchema.safeParse({ ...valid3dPhysicalConfig, cardDesign: validCardDesign });
761
+ expect(res.success).toBe(true);
762
+ // and it survives (not stripped):
763
+ if (res.success) expect(res.data.cardDesign?.material).toBe('3dprint_standard');
764
+ });
765
+
766
+ it('rejects a cardDesign whose material disagrees with the card', () => {
767
+ const res = physicalConfigSchema.safeParse({
768
+ ...valid3dPhysicalConfig,
769
+ cardDesign: { ...validCardDesign, material: 'metal' },
770
+ });
771
+ expect(res.success).toBe(false);
772
+ });
773
+
774
+ it('still accepts a legacy physicalConfig with no cardDesign', () => {
775
+ expect(physicalConfigSchema.safeParse(validPhysicalConfig).success).toBe(true);
776
+ });
777
+ });
package/src/index.ts CHANGED
Binary file