x
This commit is contained in:
1 parent
75770ca122
commit
f258e3e176
16 files changed
+1207
-348
No files matched your search
@@ -98,9 +98,45 @@ export interface BomItem {
|
||||
export interface ProductImage {
|
||||
id: number
|
||||
url: string
|
||||
mobileUrl?: string | null
|
||||
order: number
|
||||
}
|
||||
|
||||
const OptionalProductImageMobileUrlSchema = z
|
||||
.union([z.string(), z.null()])
|
||||
.optional()
|
||||
.transform((value) => {
|
||||
if (value === undefined) return undefined
|
||||
if (value === null) return null
|
||||
const normalized = value.trim()
|
||||
return normalized.length > 0 ? normalized : null
|
||||
})
|
||||
.refine((value) => value === undefined || value === null || !/^data:video\//i.test(value), 'Product image URL must reference an image')
|
||||
.refine((value) => value === undefined || value === null || !/\.(?:mp4|webm|mov|ogv|ogg)(?:[?#]|$)/i.test(value), 'Product image URL must reference an image')
|
||||
|
||||
const ProductImageUrlSchema = z.string()
|
||||
.trim()
|
||||
.min(1, 'Product image URL is required')
|
||||
.refine((value) => !/^data:video\//i.test(value), 'Product image URL must reference an image')
|
||||
.refine((value) => !/\.(?:mp4|webm|mov|ogv|ogg)(?:[?#]|$)/i.test(value), 'Product image URL must reference an image')
|
||||
|
||||
export const ProductImageObjectInputSchema = z.object({
|
||||
id: z.number().int().positive().optional(),
|
||||
url: ProductImageUrlSchema,
|
||||
mobileUrl: OptionalProductImageMobileUrlSchema,
|
||||
order: z.number().int().optional(),
|
||||
})
|
||||
|
||||
export const ProductImageInputSchema = z.union([
|
||||
ProductImageUrlSchema.transform((url) => ({
|
||||
url,
|
||||
})),
|
||||
ProductImageObjectInputSchema,
|
||||
])
|
||||
|
||||
export type ProductImageInput = z.input<typeof ProductImageInputSchema>
|
||||
export type NormalizedProductImageInput = z.output<typeof ProductImageInputSchema>
|
||||
|
||||
export type TranslatableString = string | Record<string, string>;
|
||||
|
||||
export interface AttributeDefinitionTranslation {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { ProductImageInputSchema } from './index'
|
||||
|
||||
describe('product image input contract', () => {
|
||||
test('preserves the legacy string input shape', () => {
|
||||
expect(ProductImageInputSchema.parse('/uploads/products/desktop.webp')).toEqual({
|
||||
url: '/uploads/products/desktop.webp',
|
||||
})
|
||||
})
|
||||
|
||||
test('accepts a paired desktop and mobile image', () => {
|
||||
expect(ProductImageInputSchema.parse({
|
||||
id: 12,
|
||||
url: '/uploads/products/desktop.webp',
|
||||
mobileUrl: '/uploads/products/mobile.webp',
|
||||
order: 2,
|
||||
})).toEqual({
|
||||
id: 12,
|
||||
url: '/uploads/products/desktop.webp',
|
||||
mobileUrl: '/uploads/products/mobile.webp',
|
||||
order: 2,
|
||||
})
|
||||
})
|
||||
|
||||
test('normalizes an empty mobile image URL to null', () => {
|
||||
expect(ProductImageInputSchema.parse({
|
||||
url: '/uploads/products/desktop.webp',
|
||||
mobileUrl: ' ',
|
||||
}).mobileUrl).toBeNull()
|
||||
})
|
||||
|
||||
test('rejects non-string image URLs without rejecting relative upload paths', () => {
|
||||
expect(ProductImageInputSchema.safeParse({ url: 123 }).success).toBe(false)
|
||||
expect(ProductImageInputSchema.safeParse({
|
||||
url: '/uploads/products/desktop.webp',
|
||||
mobileUrl: { path: '/uploads/products/mobile.webp' },
|
||||
}).success).toBe(false)
|
||||
})
|
||||
|
||||
test('rejects known video URLs in desktop and mobile image slots', () => {
|
||||
expect(ProductImageInputSchema.safeParse('/uploads/product.mp4').success).toBe(false)
|
||||
expect(ProductImageInputSchema.safeParse({
|
||||
url: '/uploads/desktop.webp',
|
||||
mobileUrl: '/uploads/mobile.webm?version=2',
|
||||
}).success).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user