@arroyavecommerce/theme-kinetic 0.2.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/docs/design/README.md +22 -0
- package/docs/design/kinetic-street-concept.html +558 -0
- package/package.json +45 -0
- package/src/blocks/_shared.tsx +174 -0
- package/src/blocks/carrusel.tsx +109 -0
- package/src/blocks/category-tiles.test.tsx +59 -0
- package/src/blocks/category-tiles.tsx +70 -0
- package/src/blocks/editorial-banner.test.tsx +96 -0
- package/src/blocks/editorial-banner.tsx +205 -0
- package/src/blocks/faq.test.tsx +68 -0
- package/src/blocks/faq.tsx +122 -0
- package/src/blocks/featured-collection.test.tsx +80 -0
- package/src/blocks/featured-collection.tsx +97 -0
- package/src/blocks/features.test.tsx +71 -0
- package/src/blocks/features.tsx +159 -0
- package/src/blocks/hero.test.tsx +75 -0
- package/src/blocks/hero.tsx +290 -0
- package/src/blocks/index.tsx +35 -0
- package/src/blocks/lookbook.test.tsx +80 -0
- package/src/blocks/lookbook.tsx +117 -0
- package/src/blocks/marquee.test.tsx +54 -0
- package/src/blocks/marquee.tsx +114 -0
- package/src/blocks/newsletter.test.tsx +60 -0
- package/src/blocks/newsletter.tsx +105 -0
- package/src/blocks/product-grid.test.tsx +160 -0
- package/src/blocks/product-grid.tsx +76 -0
- package/src/blocks/promo-countdown.tsx +75 -0
- package/src/blocks/promo.test.tsx +71 -0
- package/src/blocks/promo.tsx +120 -0
- package/src/blocks/rich-text.test.tsx +67 -0
- package/src/blocks/rich-text.tsx +75 -0
- package/src/blocks/spacer.test.tsx +54 -0
- package/src/blocks/spacer.tsx +56 -0
- package/src/blocks/testimonials.test.tsx +74 -0
- package/src/blocks/testimonials.tsx +104 -0
- package/src/blocks/video.test.tsx +159 -0
- package/src/blocks/video.tsx +226 -0
- package/src/catalog/buy-box.test.tsx +225 -0
- package/src/catalog/buy-box.tsx +353 -0
- package/src/catalog/config.test.ts +115 -0
- package/src/catalog/config.ts +167 -0
- package/src/catalog/filter-controls.test.tsx +149 -0
- package/src/catalog/filter-controls.tsx +187 -0
- package/src/catalog/gallery.test.tsx +201 -0
- package/src/catalog/gallery.tsx +212 -0
- package/src/catalog/lightbox.test.tsx +95 -0
- package/src/catalog/lightbox.tsx +184 -0
- package/src/catalog/pdp.test.tsx +309 -0
- package/src/catalog/pdp.tsx +354 -0
- package/src/catalog/plp-arreglos.test.tsx +235 -0
- package/src/catalog/plp-inline.tsx +54 -0
- package/src/catalog/plp-overlay.tsx +129 -0
- package/src/catalog/plp-parts.test.tsx +146 -0
- package/src/catalog/plp-parts.tsx +292 -0
- package/src/catalog/plp-pills.tsx +44 -0
- package/src/catalog/plp-sidebar.tsx +47 -0
- package/src/catalog/plp.test.tsx +319 -0
- package/src/catalog/plp.tsx +259 -0
- package/src/catalog/product-card.test.tsx +221 -0
- package/src/catalog/product-card.tsx +411 -0
- package/src/catalog/registry.tsx +16 -0
- package/src/catalog/related.test.tsx +52 -0
- package/src/catalog/related.tsx +40 -0
- package/src/catalog/reviews.test.tsx +56 -0
- package/src/catalog/reviews.tsx +116 -0
- package/src/catalog/skeletons.test.tsx +61 -0
- package/src/catalog/skeletons.tsx +103 -0
- package/src/chrome/announcement-bar.test.tsx +86 -0
- package/src/chrome/announcement-bar.tsx +173 -0
- package/src/chrome/drawer-movil.test.tsx +113 -0
- package/src/chrome/drawer-movil.tsx +342 -0
- package/src/chrome/footer.test.tsx +133 -0
- package/src/chrome/footer.tsx +603 -0
- package/src/chrome/header.test.tsx +165 -0
- package/src/chrome/header.tsx +592 -0
- package/src/chrome/index.ts +26 -0
- package/src/chrome/mega-menu.test.tsx +161 -0
- package/src/chrome/mega-menu.tsx +317 -0
- package/src/chrome/mini-cart.test.tsx +111 -0
- package/src/chrome/mini-cart.tsx +320 -0
- package/src/content/home.ts +19 -0
- package/src/content/seed.test.ts +33 -0
- package/src/content/seed.ts +28 -0
- package/src/context.ts +104 -0
- package/src/icons/icons.test.tsx +118 -0
- package/src/icons/index.ts +136 -0
- package/src/icons/payment-icons.tsx +241 -0
- package/src/icons/social-icons.tsx +108 -0
- package/src/icons/svg-base.tsx +68 -0
- package/src/index.ts +49 -0
- package/src/puck-config.tsx +404 -0
- package/src/smoke.test.ts +8 -0
- package/src/template.test.tsx +88 -0
- package/src/test/mock-ctx.test.tsx +77 -0
- package/src/test/mock-ctx.tsx +236 -0
- package/src/theme-defaults.test.ts +9 -0
- package/src/theme.test.ts +82 -0
- package/src/theme.ts +195 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
import { describe, expect, it } from "vitest"
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
5
|
+
import { headerInline, headerCentrado, headerSplit, headerMinimal, type HeaderProps } from "./header"
|
|
6
|
+
import { mockCtx } from "../test/mock-ctx"
|
|
7
|
+
|
|
8
|
+
const menu = [
|
|
9
|
+
{ etiqueta: "Novedades", href: "/novedades" },
|
|
10
|
+
{ etiqueta: "Sale", href: "/sale" },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const variantes = [
|
|
14
|
+
{ nombre: "headerInline", variante: headerInline, layout: "inline" as const },
|
|
15
|
+
{ nombre: "headerCentrado", variante: headerCentrado, layout: "centrado" as const },
|
|
16
|
+
{ nombre: "headerSplit", variante: headerSplit, layout: "split" as const },
|
|
17
|
+
{ nombre: "headerMinimal", variante: headerMinimal, layout: "minimal" as const },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
// El minimal no monta nav de escritorio (todo va al drawer), así que las
|
|
21
|
+
// columnas del megaMenu no aparecen en el HTML estático.
|
|
22
|
+
const conNav = variantes.filter((v) => v.layout !== "minimal")
|
|
23
|
+
|
|
24
|
+
const render = (variante: (typeof variantes)[number]["variante"], props: HeaderProps, ctx = mockCtx()) => {
|
|
25
|
+
const Component = variante.Component
|
|
26
|
+
return renderToStaticMarkup(<Component props={props} ctx={ctx} menu={menu} />)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("header (inline/centrado/split/minimal)", () => {
|
|
30
|
+
for (const { nombre, variante } of variantes) {
|
|
31
|
+
describe(nombre, () => {
|
|
32
|
+
const defaults = variante.defaults
|
|
33
|
+
|
|
34
|
+
it("defaults pasan el schema", () => {
|
|
35
|
+
expect(variante.schema.safeParse(defaults).success).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("renderiza sin crash e incluye <header> sticky", () => {
|
|
39
|
+
const html = render(variante, defaults)
|
|
40
|
+
expect(html).toContain("<header")
|
|
41
|
+
expect(html).toContain("sticky")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("logo modo texto pinta el wordmark", () => {
|
|
45
|
+
const props = { ...defaults, logo: { modo: "texto" as const, valor: "KINETIC", tamaño: 24 } }
|
|
46
|
+
expect(render(variante, props)).toContain("KINETIC")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("logo modo imagen resuelve el logo real via ctx.componentes.Imagen", () => {
|
|
50
|
+
const props = {
|
|
51
|
+
...defaults,
|
|
52
|
+
logo: { modo: "imagen" as const, valor: "https://ejemplo.com/logo.png", tamaño: 28 },
|
|
53
|
+
}
|
|
54
|
+
expect(render(variante, props)).toContain('src="https://ejemplo.com/logo.png"')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("con anuncio.activo:true muestra la barra de anuncios", () => {
|
|
58
|
+
const props = { ...defaults, anuncio: { ...defaults.anuncio, activo: true } }
|
|
59
|
+
expect(render(variante, props)).toContain(props.anuncio.mensajes[0]!.texto)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("con anuncio.activo:false oculta la barra de anuncios", () => {
|
|
63
|
+
const props = { ...defaults, anuncio: { ...defaults.anuncio, activo: false } }
|
|
64
|
+
expect(render(variante, props)).not.toContain(props.anuncio.mensajes[0]!.texto)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("incluye el botón hamburguesa (aria-label 'Abrir menú')", () => {
|
|
68
|
+
expect(render(variante, defaults)).toContain('aria-label="Abrir menú"')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("acciones.buscar:true incluye el disparador de búsqueda", () => {
|
|
72
|
+
const props = { ...defaults, acciones: { ...defaults.acciones, buscar: true } }
|
|
73
|
+
expect(render(variante, props)).toContain('aria-label="Buscar"')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("acciones todas en false no pinta iconos de acción", () => {
|
|
77
|
+
const props = {
|
|
78
|
+
...defaults,
|
|
79
|
+
acciones: { buscar: false, cuenta: false, favoritos: false, carrito: false },
|
|
80
|
+
}
|
|
81
|
+
const html = render(variante, props)
|
|
82
|
+
expect(html).not.toContain('aria-label="Buscar"')
|
|
83
|
+
expect(html).not.toContain("Bolsa")
|
|
84
|
+
expect(html).not.toContain("Favoritos")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("acciones.carrito:true muestra el conteo desde ctx.cartQuantities", () => {
|
|
88
|
+
const props = { ...defaults, acciones: { ...defaults.acciones, carrito: true } }
|
|
89
|
+
const html = render(variante, props, mockCtx({ cartQuantities: { a: 40, b: 2 } }))
|
|
90
|
+
expect(html).toContain("Bolsa")
|
|
91
|
+
expect(html).toContain(">42</span>")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("el botón de la bolsa abre el mini-cart (es <button>, no link a /cart)", () => {
|
|
95
|
+
const props = { ...defaults, acciones: { ...defaults.acciones, carrito: true } }
|
|
96
|
+
const html = render(variante, props, mockCtx({ cartQuantities: { a: 1 } }))
|
|
97
|
+
// El disparador de la bolsa es un botón accesible…
|
|
98
|
+
expect(html).toContain('aria-label="Bolsa"')
|
|
99
|
+
// …y ya no navega a /cart desde el header (el mini-cart, cerrado en el
|
|
100
|
+
// render estático, no aporta markup; /cart vive dentro del drawer).
|
|
101
|
+
expect(html).not.toContain('href="/cart"')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("acciones.favoritos:true muestra el icono de favoritos", () => {
|
|
105
|
+
const props = { ...defaults, acciones: { ...defaults.acciones, favoritos: true } }
|
|
106
|
+
expect(render(variante, props)).toContain("Favoritos")
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("no emite <h1>", () => {
|
|
110
|
+
expect(render(variante, defaults)).not.toMatch(/<h1[\s>]/)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const { nombre, variante } of conNav) {
|
|
116
|
+
it(`${nombre} incluye el MegaMenu (columnas de props.megaMenu)`, () => {
|
|
117
|
+
const html = render(variante, variante.defaults)
|
|
118
|
+
for (const columna of variante.defaults.megaMenu.columnas) {
|
|
119
|
+
expect(html).toContain(columna.titulo)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
it("todas las variantes comparten el mismo schema/defaults", () => {
|
|
125
|
+
expect(headerCentrado.defaults).toEqual(headerInline.defaults)
|
|
126
|
+
expect(headerSplit.defaults).toEqual(headerInline.defaults)
|
|
127
|
+
expect(headerMinimal.defaults).toEqual(headerInline.defaults)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("labels en español de cada variante", () => {
|
|
131
|
+
expect(headerInline.label).toBe("Inline")
|
|
132
|
+
expect(headerCentrado.label).toBe("Centrado")
|
|
133
|
+
expect(headerSplit.label).toBe("Split")
|
|
134
|
+
expect(headerMinimal.label).toBe("Minimal")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it("el disparador de búsqueda navega a /search (form GET con name=q)", () => {
|
|
138
|
+
const rutaFuente = fileURLToPath(new URL("./header.tsx", import.meta.url))
|
|
139
|
+
const fuente = readFileSync(rutaFuente, "utf-8")
|
|
140
|
+
expect(fuente).toContain('action="/search"')
|
|
141
|
+
expect(fuente).toContain('name="q"')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("reemplaza los glifos unicode por SVG inline (sin ⌕ ☰ ◫)", () => {
|
|
145
|
+
const html = render(headerInline, headerInline.defaults)
|
|
146
|
+
expect(html).toContain("<svg")
|
|
147
|
+
expect(html).not.toContain("⌕")
|
|
148
|
+
expect(html).not.toContain("☰")
|
|
149
|
+
expect(html).not.toContain("◫")
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("no emite <a> ni <img> directos: todo pasa por ctx.componentes / sub-componentes", () => {
|
|
153
|
+
const rutaFuente = fileURLToPath(new URL("./header.tsx", import.meta.url))
|
|
154
|
+
const fuente = readFileSync(rutaFuente, "utf-8")
|
|
155
|
+
expect(fuente).not.toMatch(/<a[\s>]/)
|
|
156
|
+
expect(fuente).not.toMatch(/<img[\s>]/)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("monta el mini-cart drawer alimentado por ctx.carrito", () => {
|
|
160
|
+
const rutaFuente = fileURLToPath(new URL("./header.tsx", import.meta.url))
|
|
161
|
+
const fuente = readFileSync(rutaFuente, "utf-8")
|
|
162
|
+
expect(fuente).toContain("<MiniCart")
|
|
163
|
+
expect(fuente).toContain("carrito={ctx.carrito}")
|
|
164
|
+
})
|
|
165
|
+
})
|