@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.
- package/CHANGELOG.md +155 -0
- package/README.md +228 -68
- package/docs/standards/data-contracts.md +13 -2
- package/package.json +26 -10
- package/scripts/fix-esm-extensions.mjs +40 -0
- package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
- package/src/blocks/application/login-forms/login-page.tsx +240 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
- package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
- package/src/blocks/application/verification/verify-1.tsx +2 -2
- package/src/blocks/application/viewport-ownership.test.tsx +41 -0
- package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
- package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
- package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
- package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
- package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
- package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
- package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
- package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
- package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
- package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
- package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
- package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
- package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
- package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
- package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
- package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
- package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
- package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
- package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
- package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
- package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
- package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
- package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
- package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
- package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
- package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
- package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
- package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
- package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
- package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
- package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
- package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
- package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
- package/src/blocks/marketing/features/feature.test.tsx +153 -0
- package/src/blocks/marketing/features/feature.tsx +195 -0
- package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
- package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
- package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
- package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
- package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
- package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
- package/src/blocks/marketing/footers/footer-section.tsx +532 -0
- package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
- package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
- package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
- package/src/blocks/marketing/navbars/navbar.tsx +735 -0
- package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
- package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
- package/src/index.ts +156 -128
- package/src/lib/contracts-curated.test.ts +47 -29
- package/src/lib/contracts.test.ts +198 -2
- package/src/lib/contracts.ts +410 -16
- package/src/ui/button-group.tsx +1 -1
- package/src/ui/carousel.tsx +1 -1
- package/src/ui/dialog.tsx +1 -1
- package/src/ui/field.tsx +2 -2
- package/src/ui/input-group.tsx +3 -3
- package/src/ui/pagination.tsx +1 -1
- package/src/ui/sidebar.tsx +7 -7
- package/src/ui/toggle-group.tsx +1 -1
- package/tsconfig.build.json +12 -0
- package/tsup.config.ts +11 -7
- package/.turbo/turbo-build.log +0 -24
- package/.turbo/turbo-test.log +0 -47
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/contracts.d.mts +0 -1732
- package/dist/contracts.d.ts +0 -1732
- package/dist/contracts.js +0 -423
- package/dist/contracts.js.map +0 -1
- package/dist/contracts.mjs +0 -365
- package/dist/contracts.mjs.map +0 -1
- package/dist/index.d.mts +0 -3068
- package/dist/index.d.ts +0 -3068
- package/dist/index.js +0 -17834
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -17740
- package/dist/index.mjs.map +0 -1
- package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
- package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
- package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
- package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
- package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
- package/src/blocks/marketing/features/feature-1.tsx +0 -97
- package/src/blocks/marketing/features/feature-2.tsx +0 -82
- package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
- package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
- package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
- package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
- package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
- package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
- package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
- package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { beforeAll, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { Navbar, type NavbarProps } from './navbar'
|
|
7
|
+
|
|
8
|
+
/** jsdom lacks the layout and pointer APIs Radix's navigation menu relies on. */
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
if (!window.matchMedia) {
|
|
11
|
+
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
|
|
12
|
+
matches: false,
|
|
13
|
+
media: query,
|
|
14
|
+
onchange: null,
|
|
15
|
+
addListener: vi.fn(),
|
|
16
|
+
removeListener: vi.fn(),
|
|
17
|
+
addEventListener: vi.fn(),
|
|
18
|
+
removeEventListener: vi.fn(),
|
|
19
|
+
dispatchEvent: vi.fn(),
|
|
20
|
+
})) as unknown as typeof window.matchMedia
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
window.ResizeObserver = class {
|
|
24
|
+
observe() {}
|
|
25
|
+
unobserve() {}
|
|
26
|
+
disconnect() {}
|
|
27
|
+
} as unknown as typeof ResizeObserver
|
|
28
|
+
|
|
29
|
+
Element.prototype.scrollIntoView = vi.fn()
|
|
30
|
+
Element.prototype.hasPointerCapture = vi.fn(() => false)
|
|
31
|
+
Element.prototype.releasePointerCapture = vi.fn()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const ShirtIcon = () => <svg data-testid='shirt-icon' aria-hidden='true' />
|
|
35
|
+
|
|
36
|
+
const megaLinks: NavbarProps['links'] = [
|
|
37
|
+
{
|
|
38
|
+
label: 'Catalog',
|
|
39
|
+
groups: [
|
|
40
|
+
{
|
|
41
|
+
title: 'Essentials',
|
|
42
|
+
items: [
|
|
43
|
+
{ label: 'Shirts', href: '/shirts', description: 'Everyday cuts', icon: 'shirt' },
|
|
44
|
+
{ label: 'Hats', href: '/hats', icon: 'unknown-key' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{ label: 'About', href: '/about' },
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
const featured: NavbarProps['featured'] = {
|
|
53
|
+
eyebrow: 'New season',
|
|
54
|
+
title: 'Linen drop',
|
|
55
|
+
description: 'Breathable layers',
|
|
56
|
+
href: '/linen',
|
|
57
|
+
linkLabel: 'Discover',
|
|
58
|
+
image: '/linen.jpg',
|
|
59
|
+
imageAlt: 'Linen shirt on a hanger',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderMega(props: Partial<NavbarProps> = {}) {
|
|
63
|
+
return render(
|
|
64
|
+
<Navbar
|
|
65
|
+
presentation='mega'
|
|
66
|
+
logoText='Acme'
|
|
67
|
+
links={megaLinks}
|
|
68
|
+
featured={featured}
|
|
69
|
+
iconMap={{ shirt: ShirtIcon }}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('Navbar — mega presentation interaction', () => {
|
|
76
|
+
it('opens a flyout from the keyboard', async () => {
|
|
77
|
+
const user = userEvent.setup()
|
|
78
|
+
renderMega()
|
|
79
|
+
|
|
80
|
+
const trigger = screen.getByRole('button', { name: 'Catalog' })
|
|
81
|
+
trigger.focus()
|
|
82
|
+
await user.keyboard('{Enter}')
|
|
83
|
+
|
|
84
|
+
await waitFor(() => {
|
|
85
|
+
expect(screen.getByRole('link', { name: /Shirts/ })).toBeTruthy()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('resolves icons for mapped keys and renders nothing for unmapped keys', async () => {
|
|
90
|
+
const user = userEvent.setup()
|
|
91
|
+
renderMega()
|
|
92
|
+
|
|
93
|
+
const trigger = screen.getByRole('button', { name: 'Catalog' })
|
|
94
|
+
trigger.focus()
|
|
95
|
+
await user.keyboard('{Enter}')
|
|
96
|
+
|
|
97
|
+
const shirts = await screen.findByRole('link', { name: /Shirts/ })
|
|
98
|
+
const hats = screen.getByRole('link', { name: /Hats/ })
|
|
99
|
+
|
|
100
|
+
expect(shirts.querySelector('[data-testid="shirt-icon"]')).not.toBeNull()
|
|
101
|
+
expect(hats.querySelector('svg')).toBeNull()
|
|
102
|
+
expect(hats.textContent).toContain('Hats')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('dismisses the flyout with Escape and returns focus to the trigger', async () => {
|
|
106
|
+
const user = userEvent.setup()
|
|
107
|
+
renderMega()
|
|
108
|
+
|
|
109
|
+
const trigger = screen.getByRole('button', { name: 'Catalog' })
|
|
110
|
+
trigger.focus()
|
|
111
|
+
await user.keyboard('{Enter}')
|
|
112
|
+
await screen.findByRole('link', { name: /Shirts/ })
|
|
113
|
+
|
|
114
|
+
await user.keyboard('{Escape}')
|
|
115
|
+
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(screen.queryByRole('link', { name: /Shirts/ })).toBeNull()
|
|
118
|
+
})
|
|
119
|
+
expect(document.activeElement).toBe(trigger)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('moves between top-level entries with the arrow keys', async () => {
|
|
123
|
+
const user = userEvent.setup()
|
|
124
|
+
renderMega()
|
|
125
|
+
|
|
126
|
+
const trigger = screen.getByRole('button', { name: 'Catalog' })
|
|
127
|
+
trigger.focus()
|
|
128
|
+
await user.keyboard('{ArrowRight}')
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(document.activeElement?.textContent).toBe('About')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('renders the featured promo with a semantic surface and alt text', async () => {
|
|
136
|
+
const user = userEvent.setup()
|
|
137
|
+
renderMega()
|
|
138
|
+
|
|
139
|
+
await user.click(screen.getByRole('button', { name: 'Catalog' }))
|
|
140
|
+
|
|
141
|
+
const promo = await screen.findByRole('link', { name: /Linen drop/ })
|
|
142
|
+
const image = promo.querySelector('img')
|
|
143
|
+
|
|
144
|
+
expect(image?.getAttribute('alt')).toBe('Linen shirt on a hanger')
|
|
145
|
+
expect(promo.className).not.toContain('text-white')
|
|
146
|
+
expect(promo.innerHTML).not.toContain('from-black')
|
|
147
|
+
expect(promo.innerHTML).toContain('from-background')
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { Navbar, type NavbarProps } from './navbar'
|
|
5
|
+
|
|
6
|
+
const links: NavbarProps['links'] = [
|
|
7
|
+
{ label: 'Shop', href: '/shop' },
|
|
8
|
+
{ label: 'Journal', href: '/journal' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
function render(props: Partial<NavbarProps> = {}) {
|
|
12
|
+
return renderToStaticMarkup(<Navbar logoText='Acme' links={links} {...props} />)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('Navbar — page-level header', () => {
|
|
16
|
+
describe('bar presentation', () => {
|
|
17
|
+
it('renders every top-level link in a labeled navigation landmark', () => {
|
|
18
|
+
const html = render()
|
|
19
|
+
|
|
20
|
+
expect(html).toMatch(/<nav[^>]*aria-label="Main"/)
|
|
21
|
+
expect(html).toMatch(/<a[^>]*href="\/shop"[^>]*>Shop<\/a>/)
|
|
22
|
+
expect(html).toMatch(/<a[^>]*href="\/journal"[^>]*>Journal<\/a>/)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('indicates the current link with aria-current and a non-color cue', () => {
|
|
26
|
+
const html = render({ currentPath: '/shop' })
|
|
27
|
+
|
|
28
|
+
const current = html.match(/<a[^>]*href="\/shop"[^>]*>/)?.[0] ?? ''
|
|
29
|
+
|
|
30
|
+
expect(current).toContain('aria-current="page"')
|
|
31
|
+
expect(current).toContain('underline')
|
|
32
|
+
expect(current).toContain('decoration-2')
|
|
33
|
+
expect(html.match(/aria-current="page"/g)).toHaveLength(1)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('renders the brand as a link only when a logo destination is supplied', () => {
|
|
37
|
+
expect(render({ logoHref: '/' })).toMatch(/<a[^>]*href="\/"[^>]*>.*Acme/s)
|
|
38
|
+
expect(render()).not.toMatch(/<a[^>]*href="\/"[^>]*>/)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('renders the sign-in and primary actions', () => {
|
|
42
|
+
const html = render({
|
|
43
|
+
signIn: { label: 'Sign in', href: '/account' },
|
|
44
|
+
cta: { label: 'Shop now', href: '/shop' },
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
expect(html).toMatch(/<a[^>]*href="\/account"[^>]*>Sign in<\/a>/)
|
|
48
|
+
expect(html).toMatch(/<a[^>]*href="\/shop"[^>]*>Shop now<\/a>/)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('floating presentation', () => {
|
|
53
|
+
it('stays in document flow so it reserves its own space', () => {
|
|
54
|
+
const html = render({ presentation: 'floating' })
|
|
55
|
+
|
|
56
|
+
expect(html).toContain('sticky top-0')
|
|
57
|
+
expect(html).not.toContain('fixed')
|
|
58
|
+
expect(html).not.toContain('pt-16')
|
|
59
|
+
expect(html).not.toContain('scroll-padding')
|
|
60
|
+
expect(html).toMatch(/aria-label="Main"/)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('renders the scroll sentinel out of flow', () => {
|
|
64
|
+
const html = render({ presentation: 'floating' })
|
|
65
|
+
|
|
66
|
+
expect(html).toMatch(/<div[^>]*aria-hidden="true"[^>]*pointer-events-none absolute inset-x-0 top-0 h-2/)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('mega presentation', () => {
|
|
71
|
+
it('renders grouped entries as flyout triggers and plain entries inline', () => {
|
|
72
|
+
const html = render({
|
|
73
|
+
presentation: 'mega',
|
|
74
|
+
links: [
|
|
75
|
+
{
|
|
76
|
+
label: 'Catalog',
|
|
77
|
+
groups: [
|
|
78
|
+
{
|
|
79
|
+
title: 'Essentials',
|
|
80
|
+
items: [{ label: 'Shirts', href: '/shirts', description: 'Everyday cuts' }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{ label: 'About', href: '/about' },
|
|
85
|
+
],
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(html).toMatch(/<button[^>]*>Catalog/)
|
|
89
|
+
expect(html).toMatch(/<a[^>]*href="\/about"/)
|
|
90
|
+
// Flyout content is mounted on demand, so it is absent until opened.
|
|
91
|
+
expect(html).not.toContain('/shirts')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('absent optional content', () => {
|
|
96
|
+
it('renders no action region when neither action is supplied', () => {
|
|
97
|
+
const html = render()
|
|
98
|
+
|
|
99
|
+
expect(html).not.toContain('<hr')
|
|
100
|
+
expect(html).not.toContain('Sign in')
|
|
101
|
+
expect(html).not.toMatch(/<a[^>]*href="\/account"/)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('renders no navigation container when there are no links', () => {
|
|
105
|
+
const html = render({ links: [] })
|
|
106
|
+
|
|
107
|
+
expect(html).not.toContain('aria-label="Main"')
|
|
108
|
+
expect(html).not.toContain('aria-label="Mobile"')
|
|
109
|
+
// The mobile trigger would open an empty drawer, so it is absent too.
|
|
110
|
+
expect(html).not.toContain('aria-label="Open menu"')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('keeps the mobile trigger while actions exist without links', () => {
|
|
114
|
+
const html = render({ links: [], cta: { label: 'Shop now', href: '/shop' } })
|
|
115
|
+
|
|
116
|
+
expect(html).toContain('aria-label="Open menu"')
|
|
117
|
+
expect(html).not.toContain('aria-label="Main"')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('renders no featured promo when none is supplied', () => {
|
|
121
|
+
const html = render({ presentation: 'mega' })
|
|
122
|
+
|
|
123
|
+
expect(html).not.toContain('min-h-56')
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('commerce ownership', () => {
|
|
128
|
+
it.each<NavbarProps['presentation']>(['bar', 'mega', 'floating'])(
|
|
129
|
+
'renders no cart, account, or search affordance for %s',
|
|
130
|
+
presentation => {
|
|
131
|
+
const html = render({
|
|
132
|
+
presentation,
|
|
133
|
+
signIn: { label: 'Sign in', href: '/account' },
|
|
134
|
+
cta: { label: 'Shop now', href: '/shop' },
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
expect(html).not.toContain('aria-label="Cart"')
|
|
138
|
+
expect(html).not.toContain('ShoppingCart')
|
|
139
|
+
expect(html).not.toContain('Search')
|
|
140
|
+
expect(html).not.toContain('/cart')
|
|
141
|
+
expect(html).not.toContain('artículos')
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
it('does not accept cart props on the type boundary', () => {
|
|
146
|
+
// @ts-expect-error cart ownership belongs to the commerce shell, not this header
|
|
147
|
+
const withCart: NavbarProps = { logoText: 'Acme', links: [], cartCount: 3 }
|
|
148
|
+
// @ts-expect-error cart ownership belongs to the commerce shell, not this header
|
|
149
|
+
const withCartHandler: NavbarProps = { logoText: 'Acme', links: [], onOpenCart: () => {} }
|
|
150
|
+
|
|
151
|
+
expect(withCart.logoText).toBe('Acme')
|
|
152
|
+
expect(withCartHandler.logoText).toBe('Acme')
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
})
|