This commit is contained in:
Your Name committed 2026-10-01 06:59:38 +07:00
1 parent 0ceee3dbff
commit 2d39edbd57
18 files changed
+804 -153

No files matched your search

+32
View File
@@ -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')
})
})
+51
View File
@@ -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