@thorprovider/blocks 0.1.3 → 2.0.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +228 -68
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  7. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  8. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  9. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  10. package/src/blocks/application/verification/verify-1.tsx +2 -2
  11. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  12. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  13. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  14. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  15. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  16. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  17. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  19. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  20. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  21. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  22. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  23. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  24. package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
  25. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  27. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  29. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  30. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
  31. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
  32. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  33. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  34. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  35. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  36. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  37. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  38. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  39. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  40. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  41. package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
  42. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  43. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  44. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  45. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  46. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  47. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  48. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  49. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  50. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  51. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  52. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  53. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  54. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  55. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  56. package/src/blocks/marketing/features/feature.tsx +195 -0
  57. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  58. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  59. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  60. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  61. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  62. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  63. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  64. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  65. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  66. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  67. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  68. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  69. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  70. package/src/index.ts +156 -128
  71. package/src/lib/contracts-curated.test.ts +47 -29
  72. package/src/lib/contracts.test.ts +198 -2
  73. package/src/lib/contracts.ts +410 -16
  74. package/src/ui/button-group.tsx +1 -1
  75. package/src/ui/carousel.tsx +1 -1
  76. package/src/ui/dialog.tsx +1 -1
  77. package/src/ui/field.tsx +2 -2
  78. package/src/ui/input-group.tsx +3 -3
  79. package/src/ui/pagination.tsx +1 -1
  80. package/src/ui/sidebar.tsx +7 -7
  81. package/src/ui/toggle-group.tsx +1 -1
  82. package/tsconfig.build.json +12 -0
  83. package/tsup.config.ts +11 -7
  84. package/.turbo/turbo-build.log +0 -24
  85. package/.turbo/turbo-test.log +0 -47
  86. package/.turbo/turbo-type-check.log +0 -0
  87. package/dist/.tsbuildinfo +0 -1
  88. package/dist/contracts.d.mts +0 -1732
  89. package/dist/contracts.d.ts +0 -1732
  90. package/dist/contracts.js +0 -423
  91. package/dist/contracts.js.map +0 -1
  92. package/dist/contracts.mjs +0 -365
  93. package/dist/contracts.mjs.map +0 -1
  94. package/dist/index.d.mts +0 -3068
  95. package/dist/index.d.ts +0 -3068
  96. package/dist/index.js +0 -17834
  97. package/dist/index.js.map +0 -1
  98. package/dist/index.mjs +0 -17740
  99. package/dist/index.mjs.map +0 -1
  100. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  101. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  102. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  103. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  104. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  105. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  106. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  107. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  108. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  109. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  110. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  111. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  112. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  113. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  114. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  115. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  116. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  117. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  118. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  119. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  120. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  121. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  122. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  123. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  124. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  125. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  126. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  127. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  128. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  129. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  130. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  131. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  132. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  133. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  134. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  135. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  136. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  137. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  138. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  139. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  140. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  141. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  142. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  143. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  144. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  145. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  146. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  147. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  148. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  149. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  150. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  151. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  152. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  153. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  154. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  155. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  156. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  157. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  158. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  159. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  160. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  161. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  162. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  163. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  164. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  165. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  166. package/src/blocks/marketing/features/feature-2.tsx +0 -82
  167. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  168. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  169. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  170. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  171. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  172. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  173. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  174. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
  175. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  176. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  177. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  178. package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
@@ -1,5 +1,13 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { storeCategorySchema } from './contracts'
2
+ import { z } from 'zod'
3
+ import {
4
+ categoryCardPropsSchema,
5
+ categoryGridPropsSchema,
6
+ navbarPropsSchema,
7
+ productCardPropsSchema,
8
+ productGridPropsSchema,
9
+ storeCategorySchema,
10
+ } from './contracts'
3
11
 
4
12
  describe('storeCategorySchema', () => {
5
13
  it('accepts flat categories (additive backward compatibility)', () => {
@@ -29,4 +37,192 @@ describe('storeCategorySchema', () => {
29
37
  expect(storeCategorySchema.safeParse({ label: 'Solo', href: '/categorias/x', children: [{}] }).success).toBe(false)
30
38
  expect(storeCategorySchema.safeParse({}).success).toBe(false)
31
39
  })
32
- })
40
+ })
41
+
42
+ describe('productCardPropsSchema', () => {
43
+ const canonicalItem = {
44
+ id: 'sku-1',
45
+ title: 'Llave de impacto',
46
+ price: { amount: '129.50', currencyCode: 'EUR' },
47
+ }
48
+
49
+ it('accepts a single canonical Money item with the bounded presentation options', () => {
50
+ expect(productCardPropsSchema.safeParse({ item: canonicalItem }).success).toBe(true)
51
+ expect(
52
+ productCardPropsSchema.safeParse({
53
+ item: canonicalItem,
54
+ presentation: 'classic',
55
+ wishlisted: true,
56
+ }).success,
57
+ ).toBe(true)
58
+ expect(
59
+ productCardPropsSchema.safeParse({ item: canonicalItem, presentation: 'rich' }).success,
60
+ ).toBe(true)
61
+ })
62
+
63
+ it('rejects a collection in place of the single item', () => {
64
+ expect(productCardPropsSchema.safeParse({ items: [canonicalItem] }).success).toBe(false)
65
+ })
66
+
67
+ it('rejects legacy numeric-price items', () => {
68
+ expect(
69
+ productCardPropsSchema.safeParse({ item: { id: 'p1', title: 'Taladro', price: 49.99, image: '/x.svg' } })
70
+ .success,
71
+ ).toBe(false)
72
+ })
73
+
74
+ it('rejects unknown presentation options', () => {
75
+ expect(
76
+ productCardPropsSchema.safeParse({ item: canonicalItem, presentation: 'compact' }).success,
77
+ ).toBe(false)
78
+ })
79
+ })
80
+
81
+ describe('productGridPropsSchema', () => {
82
+ const canonicalItem = {
83
+ id: 'sku-1',
84
+ title: 'Llave de impacto',
85
+ price: { amount: '129.50', currencyCode: 'EUR' },
86
+ }
87
+
88
+ it('accepts a collection with the listing state options', () => {
89
+ expect(productGridPropsSchema.safeParse({ items: [canonicalItem] }).success).toBe(true)
90
+ expect(
91
+ productGridPropsSchema.safeParse({
92
+ items: [canonicalItem],
93
+ presentation: 'minimal',
94
+ heading: 'Destacados',
95
+ maxItems: 6,
96
+ wishlistedIds: ['sku-1'],
97
+ loading: false,
98
+ skeletonCount: 6,
99
+ emptyLabel: 'Sin productos',
100
+ }).success,
101
+ ).toBe(true)
102
+ })
103
+
104
+ it('declares only serializable keys — no callbacks or label strings', () => {
105
+ const runtimeOnlyKeys = [
106
+ 'onAddToCart',
107
+ 'onProductClick',
108
+ 'onToggleWishlist',
109
+ 'onViewSimilar',
110
+ 'addToCartLabel',
111
+ 'unavailablePriceLabel',
112
+ 'addToWishlistLabel',
113
+ 'removeFromWishlistLabel',
114
+ 'viewSimilarLabel',
115
+ ]
116
+ const schemaOwnedLabelKeys = new Set(['emptyLabel'])
117
+ const schemas: Array<{ name: string; schema: z.AnyZodObject }> = [
118
+ { name: 'productCardPropsSchema', schema: productCardPropsSchema },
119
+ { name: 'productGridPropsSchema', schema: productGridPropsSchema },
120
+ ]
121
+
122
+ for (const { name, schema } of schemas) {
123
+ const declaredKeys = Object.keys(schema.shape)
124
+ expect(declaredKeys.length, name).toBeGreaterThan(0)
125
+ for (const key of declaredKeys) {
126
+ expect(runtimeOnlyKeys, `${name}.${key}`).not.toContain(key)
127
+ expect(key, `${name}.${key}`).not.toMatch(/^on[A-Z]/)
128
+ if (!schemaOwnedLabelKeys.has(key)) {
129
+ expect(key, `${name}.${key}`).not.toMatch(/Label$/)
130
+ }
131
+ expect(schema.shape[key], `${name}.${key}`).not.toBeInstanceOf(z.ZodFunction)
132
+ }
133
+ }
134
+ })
135
+
136
+ it('rejects non-positive maxItems and skeletonCount', () => {
137
+ expect(productGridPropsSchema.safeParse({ items: [], maxItems: 0 }).success).toBe(false)
138
+ expect(productGridPropsSchema.safeParse({ items: [], skeletonCount: 0 }).success).toBe(false)
139
+ })
140
+ })
141
+
142
+ describe('category cell and listing contracts', () => {
143
+ const category = { id: 'cat-tools', name: 'Herramientas' }
144
+
145
+ it('keeps category cell and listing props JSON-serializable and callback-free', () => {
146
+ const samples: Array<{ name: string; schema: z.AnyZodObject; props: unknown }> = [
147
+ {
148
+ name: 'categoryCardPropsSchema',
149
+ schema: categoryCardPropsSchema,
150
+ props: { category, presentation: 'grid', browseLabel: 'Explorar' },
151
+ },
152
+ {
153
+ name: 'categoryGridPropsSchema',
154
+ schema: categoryGridPropsSchema,
155
+ props: { categories: [category], presentation: 'grid', title: 'Categorías', maxItems: 4 },
156
+ },
157
+ ]
158
+
159
+ for (const { name, schema, props } of samples) {
160
+ const parsed = schema.parse(props)
161
+ expect(JSON.parse(JSON.stringify(parsed)), name).toEqual(parsed)
162
+ for (const key of Object.keys(schema.shape)) {
163
+ expect(key, `${name}.${key}`).not.toMatch(/^on[A-Z]/)
164
+ expect(schema.shape[key], `${name}.${key}`).not.toBeInstanceOf(z.ZodFunction)
165
+ }
166
+ }
167
+ })
168
+
169
+ it('keeps display labels serializable strings and rejects a collection in the cell contract', () => {
170
+ expect(categoryCardPropsSchema.safeParse({ category, browseLabel: () => 'Explorar' }).success).toBe(false)
171
+ expect(categoryGridPropsSchema.safeParse({ categories: [], emptyLabel: () => 'Vacío' }).success).toBe(false)
172
+ expect(categoryCardPropsSchema.safeParse({ categories: [category] }).success).toBe(false)
173
+ })
174
+ })
175
+
176
+ describe('navbarPropsSchema', () => {
177
+ const full = {
178
+ presentation: 'mega' as const,
179
+ logoText: 'ElectroCuba',
180
+ logoHref: '/',
181
+ currentPath: '/tienda',
182
+ links: [
183
+ {
184
+ label: 'Catálogo',
185
+ groups: [
186
+ {
187
+ title: 'Esenciales',
188
+ items: [{ label: 'Camisas', href: '/camisas', description: 'Cortes diarios', icon: 'shirt' }],
189
+ },
190
+ ],
191
+ },
192
+ { label: 'Nosotros', href: '/nosotros', emphasis: true },
193
+ ],
194
+ featured: {
195
+ eyebrow: 'Nueva temporada',
196
+ title: 'Lino',
197
+ description: 'Capas transpirables',
198
+ href: '/lino',
199
+ linkLabel: 'Descubrir',
200
+ image: '/lino.jpg',
201
+ imageAlt: 'Camisa de lino',
202
+ },
203
+ signIn: { label: 'Entrar', href: '/cuenta' },
204
+ cta: { label: 'Comprar' },
205
+ className: 'bg-brand-nav text-brand-nav-foreground',
206
+ }
207
+
208
+ it('round-trips a fully populated header', () => {
209
+ expect(navbarPropsSchema.parse(full)).toEqual(full)
210
+ })
211
+
212
+ it('requires the brand text and the links list', () => {
213
+ expect(navbarPropsSchema.safeParse({ ...full, logoText: undefined }).success).toBe(false)
214
+ expect(navbarPropsSchema.safeParse({ logoText: 'ElectroCuba' }).success).toBe(false)
215
+ })
216
+
217
+ it('rejects non-string hrefs and unknown presentations', () => {
218
+ expect(navbarPropsSchema.safeParse({ logoText: 'X', links: [{ label: 'Y', href: 42 }] }).success).toBe(false)
219
+ expect(navbarPropsSchema.safeParse({ ...full, presentation: 'sidebar' }).success).toBe(false)
220
+ })
221
+
222
+ it('cannot carry host bindings across the serializable boundary', () => {
223
+ const parsed = navbarPropsSchema.parse({ ...full, iconMap: { shirt: () => null }, onSignIn: () => {} })
224
+
225
+ expect(parsed).not.toHaveProperty('iconMap')
226
+ expect(parsed).not.toHaveProperty('onSignIn')
227
+ })
228
+ })