x
This commit is contained in:
1 parent
0ceee3dbff
commit
2d39edbd57
18 files changed
+804
-153
No files matched your search
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { getCategoryMedia, resolveCategoryMediaUrl, type Category } from './index'
|
||||
|
||||
const category: Category = {
|
||||
id: 1,
|
||||
name: 'Example',
|
||||
imageUrl: '/legacy.jpg',
|
||||
media: [
|
||||
{ role: 'thumbnail', url: '/square.jpg' },
|
||||
{ role: 'hero_desktop', url: '/wide.jpg', focalX: 0.25, focalY: 0.75 },
|
||||
],
|
||||
}
|
||||
|
||||
describe('category media resolver', () => {
|
||||
test('selects the exact semantic role', () => {
|
||||
expect(resolveCategoryMediaUrl(category, 'hero_desktop')).toBe('/wide.jpg')
|
||||
expect(getCategoryMedia(category, 'hero_desktop')?.focalX).toBe(0.25)
|
||||
})
|
||||
|
||||
test('uses the documented role fallback order', () => {
|
||||
expect(resolveCategoryMediaUrl(category, 'hero_mobile')).toBe('/wide.jpg')
|
||||
expect(resolveCategoryMediaUrl(category, 'card_portrait')).toBe('/square.jpg')
|
||||
})
|
||||
|
||||
test('keeps the legacy imageUrl fallback for migrated clients', () => {
|
||||
expect(resolveCategoryMediaUrl({ imageUrl: '/legacy.jpg' }, 'thumbnail')).toBe('/legacy.jpg')
|
||||
})
|
||||
|
||||
test('uses the caller fallback when no media exists', () => {
|
||||
expect(resolveCategoryMediaUrl({}, 'social_share', '/default.jpg')).toBe('/default.jpg')
|
||||
})
|
||||
})
|
||||
@@ -6,10 +6,33 @@ export interface Brand {
|
||||
description?: string | null
|
||||
}
|
||||
|
||||
export const CATEGORY_MEDIA_ROLES = [
|
||||
'thumbnail',
|
||||
'hero_desktop',
|
||||
'hero_mobile',
|
||||
'card_portrait',
|
||||
'social_share',
|
||||
] as const
|
||||
|
||||
export type CategoryMediaRole = typeof CATEGORY_MEDIA_ROLES[number]
|
||||
|
||||
export interface CategoryMedia {
|
||||
id?: number
|
||||
categoryId?: number
|
||||
role: CategoryMediaRole
|
||||
url: string
|
||||
width?: number | null
|
||||
height?: number | null
|
||||
focalX?: number | null
|
||||
focalY?: number | null
|
||||
sortOrder?: number
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: number
|
||||
name: TranslatableString
|
||||
imageUrl?: string | null
|
||||
media?: CategoryMedia[]
|
||||
layoutType?: 'split_showcase' | 'editorial_hero' | 'dense_catalog' | string | null
|
||||
_count?: { products: number }
|
||||
translations?: { langCode: string; name: string }[]
|
||||
@@ -17,6 +40,34 @@ export interface Category {
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
const CATEGORY_MEDIA_FALLBACKS: Record<CategoryMediaRole, CategoryMediaRole[]> = {
|
||||
thumbnail: ['thumbnail'],
|
||||
hero_desktop: ['hero_desktop', 'thumbnail'],
|
||||
hero_mobile: ['hero_mobile', 'hero_desktop', 'thumbnail'],
|
||||
card_portrait: ['card_portrait', 'thumbnail'],
|
||||
social_share: ['social_share', 'hero_desktop', 'thumbnail'],
|
||||
}
|
||||
|
||||
export function getCategoryMedia(
|
||||
category: Pick<Category, 'media'> | null | undefined,
|
||||
role: CategoryMediaRole,
|
||||
): CategoryMedia | undefined {
|
||||
const media = Array.isArray(category?.media) ? category.media : []
|
||||
for (const candidateRole of CATEGORY_MEDIA_FALLBACKS[role]) {
|
||||
const candidate = media.find((item) => item.role === candidateRole && item.url?.trim())
|
||||
if (candidate) return candidate
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function resolveCategoryMediaUrl(
|
||||
category: Pick<Category, 'media' | 'imageUrl'> | null | undefined,
|
||||
role: CategoryMediaRole,
|
||||
fallbackUrl = '',
|
||||
): string {
|
||||
return getCategoryMedia(category, role)?.url || category?.imageUrl || fallbackUrl
|
||||
}
|
||||
|
||||
|
||||
export interface Variant {
|
||||
id: number
|
||||
|
||||
Reference in new issue
Block a user