mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
refactor: Apply PR #6112 review feedback for Media Assets feature
- Move composables to platform/assets directory structure - Extract interface-based abstraction (IAssetsProvider) for cloud/internal implementations - Move constants to module scope to avoid re-initialization - Extract helper functions (truncateFilename, assetMappers) for reusability - Rename getMediaTypeFromFilename to return singular form (image/video/audio) - Add deprecated plural version for backward compatibility - Add comprehensive test coverage for new utility functions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -475,50 +475,92 @@ export function formatDuration(milliseconds: number): string {
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
// Module scope constants to avoid re-initialization on every call
|
||||
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']
|
||||
const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi']
|
||||
const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'flac']
|
||||
const THREE_D_EXTENSIONS = ['obj', 'fbx', 'gltf', 'glb']
|
||||
|
||||
/**
|
||||
* Determines the media type from a filename's extension
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media type: 'images', 'videos', 'audios', '3D' for gallery compatibility
|
||||
* Truncates a filename while preserving the extension
|
||||
* @param filename The filename to truncate
|
||||
* @param maxLength Maximum length for the filename without extension
|
||||
* @returns Truncated filename with extension preserved
|
||||
*/
|
||||
export function getMediaTypeFromFilename(filename: string): string {
|
||||
if (!filename) return 'images'
|
||||
const ext = filename.split('.').pop()?.toLowerCase()
|
||||
if (!ext) return 'images'
|
||||
export function truncateFilename(
|
||||
filename: string,
|
||||
maxLength: number = 20
|
||||
): string {
|
||||
if (!filename || filename.length <= maxLength) {
|
||||
return filename
|
||||
}
|
||||
|
||||
const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']
|
||||
const videoExts = ['mp4', 'webm', 'mov', 'avi']
|
||||
const audioExts = ['mp3', 'wav', 'ogg', 'flac']
|
||||
const threeDExts = ['obj', 'fbx', 'gltf', 'glb']
|
||||
const lastDotIndex = filename.lastIndexOf('.')
|
||||
const nameWithoutExt =
|
||||
lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename
|
||||
const extension = lastDotIndex > -1 ? filename.substring(lastDotIndex) : ''
|
||||
|
||||
if (imageExts.includes(ext)) return 'images'
|
||||
if (videoExts.includes(ext)) return 'videos'
|
||||
if (audioExts.includes(ext)) return 'audios'
|
||||
if (threeDExts.includes(ext)) return '3D'
|
||||
// If the name without extension is short enough, return as is
|
||||
if (nameWithoutExt.length <= maxLength) {
|
||||
return filename
|
||||
}
|
||||
|
||||
return 'images'
|
||||
// Calculate how to split the truncation
|
||||
const halfLength = Math.floor((maxLength - 3) / 2) // -3 for '...'
|
||||
const start = nameWithoutExt.substring(0, halfLength)
|
||||
const end = nameWithoutExt.substring(nameWithoutExt.length - halfLength)
|
||||
|
||||
return `${start}...${end}${extension}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the media kind from a filename's extension
|
||||
* Determines the media type from a filename's extension (singular form)
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media kind: 'image', 'video', 'audio', or '3D'
|
||||
* @returns The media type: 'image', 'video', 'audio', or '3D'
|
||||
*/
|
||||
export function getMediaKindFromFilename(
|
||||
export function getMediaTypeFromFilename(
|
||||
filename: string
|
||||
): 'image' | 'video' | 'audio' | '3D' {
|
||||
if (!filename) return 'image'
|
||||
const ext = filename.split('.').pop()?.toLowerCase()
|
||||
if (!ext) return 'image'
|
||||
|
||||
const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']
|
||||
const videoExts = ['mp4', 'webm', 'mov', 'avi']
|
||||
const audioExts = ['mp3', 'wav', 'ogg', 'flac']
|
||||
const threeDExts = ['obj', 'fbx', 'gltf', 'glb']
|
||||
|
||||
if (imageExts.includes(ext)) return 'image'
|
||||
if (videoExts.includes(ext)) return 'video'
|
||||
if (audioExts.includes(ext)) return 'audio'
|
||||
if (threeDExts.includes(ext)) return '3D'
|
||||
if (IMAGE_EXTENSIONS.includes(ext)) return 'image'
|
||||
if (VIDEO_EXTENSIONS.includes(ext)) return 'video'
|
||||
if (AUDIO_EXTENSIONS.includes(ext)) return 'audio'
|
||||
if (THREE_D_EXTENSIONS.includes(ext)) return '3D'
|
||||
|
||||
return 'image'
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getMediaTypeFromFilename instead - returns plural form for legacy compatibility
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media type in plural form: 'images', 'videos', 'audios', '3D'
|
||||
*/
|
||||
export function getMediaTypeFromFilenamePlural(filename: string): string {
|
||||
const type = getMediaTypeFromFilename(filename)
|
||||
switch (type) {
|
||||
case 'image':
|
||||
return 'images'
|
||||
case 'video':
|
||||
return 'videos'
|
||||
case 'audio':
|
||||
return 'audios'
|
||||
case '3D':
|
||||
return '3D'
|
||||
default:
|
||||
return 'images'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getMediaTypeFromFilename instead - kept for backward compatibility
|
||||
* @param filename The filename to analyze
|
||||
* @returns The media kind: 'image', 'video', 'audio', or '3D'
|
||||
*/
|
||||
export function getMediaKindFromFilename(
|
||||
filename: string
|
||||
): 'image' | 'video' | 'audio' | '3D' {
|
||||
return getMediaTypeFromFilename(filename)
|
||||
}
|
||||
|
||||
@@ -65,23 +65,18 @@ import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
|
||||
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
|
||||
import { useCloudMediaAssets } from '@/composables/useCloudMediaAssets'
|
||||
import { useInternalMediaAssets } from '@/composables/useInternalMediaAssets'
|
||||
import MediaAssetCard from '@/platform/assets/components/MediaAssetCard.vue'
|
||||
import { useMediaAssets } from '@/platform/assets/composables/useMediaAssets'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import { getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
import { getMediaTypeFromFilenamePlural } from '@/utils/formatUtil'
|
||||
|
||||
const activeTab = ref<'input' | 'output'>('input')
|
||||
const mediaAssets = ref<AssetItem[]>([])
|
||||
const selectedAsset = ref<AssetItem | null>(null)
|
||||
|
||||
// Use appropriate implementation based on environment
|
||||
const implementation = isCloud
|
||||
? useCloudMediaAssets()
|
||||
: useInternalMediaAssets()
|
||||
const { loading, error, fetchMediaList } = implementation
|
||||
// Use unified media assets implementation that handles cloud/internal automatically
|
||||
const { loading, error, fetchMediaList } = useMediaAssets()
|
||||
|
||||
const galleryActiveIndex = ref(-1)
|
||||
const galleryItems = computed(() => {
|
||||
@@ -92,7 +87,7 @@ const galleryItems = computed(() => {
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: '0',
|
||||
mediaType: getMediaTypeFromFilename(asset.name)
|
||||
mediaType: getMediaTypeFromFilenamePlural(asset.name)
|
||||
})
|
||||
|
||||
// Override the url getter to use asset.preview_url
|
||||
|
||||
@@ -128,7 +128,7 @@ import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
import CardTop from '@/components/card/CardTop.vue'
|
||||
import SquareChip from '@/components/chip/SquareChip.vue'
|
||||
import { formatDuration, getMediaKindFromFilename } from '@/utils/formatUtil'
|
||||
import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { useMediaAssetActions } from '../composables/useMediaAssetActions'
|
||||
@@ -191,7 +191,7 @@ const assetType = computed(() => {
|
||||
|
||||
// Determine file type from extension
|
||||
const fileKind = computed((): MediaKind => {
|
||||
return getMediaKindFromFilename(asset?.name || '') as MediaKind
|
||||
return getMediaTypeFromFilename(asset?.name || '') as MediaKind
|
||||
})
|
||||
|
||||
// Adapt AssetItem to legacy AssetMeta format for existing components
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
|
||||
/**
|
||||
* Interface for media assets providers
|
||||
* Defines the common API for both cloud and internal file implementations
|
||||
*/
|
||||
export interface IAssetsProvider {
|
||||
/** Loading state indicator */
|
||||
loading: Ref<boolean>
|
||||
|
||||
/** Error state, null when no error */
|
||||
error: Ref<string | null>
|
||||
|
||||
/**
|
||||
* Fetch list of media assets from the specified directory
|
||||
* @param directory - 'input' or 'output'
|
||||
* @returns Promise resolving to array of AssetItem
|
||||
*/
|
||||
fetchMediaList: (directory: 'input' | 'output') => Promise<AssetItem[]>
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import type { TaskItemImpl } from '@/stores/queueStore'
|
||||
import { truncateFilename } from '@/utils/formatUtil'
|
||||
|
||||
/**
|
||||
* Maps a TaskItemImpl output to an AssetItem format
|
||||
* @param taskItem The task item containing execution data
|
||||
* @param output The output from the task
|
||||
* @param useDisplayName Whether to truncate the filename for display
|
||||
* @returns AssetItem formatted object
|
||||
*/
|
||||
export function mapTaskOutputToAssetItem(
|
||||
taskItem: TaskItemImpl,
|
||||
output: any,
|
||||
useDisplayName: boolean = false
|
||||
): AssetItem {
|
||||
const metadata: Record<string, any> = {
|
||||
promptId: taskItem.promptId,
|
||||
nodeId: output.nodeId,
|
||||
subfolder: output.subfolder
|
||||
}
|
||||
|
||||
// Add execution time if available
|
||||
if (taskItem.executionTimeInSeconds) {
|
||||
metadata.executionTimeInSeconds = taskItem.executionTimeInSeconds
|
||||
}
|
||||
|
||||
// Add format if available
|
||||
if (output.format) {
|
||||
metadata.format = output.format
|
||||
}
|
||||
|
||||
// Add workflow if available
|
||||
if (taskItem.workflow) {
|
||||
metadata.workflow = taskItem.workflow
|
||||
}
|
||||
|
||||
// Store original filename if using display name
|
||||
if (useDisplayName) {
|
||||
metadata.originalFilename = output.filename
|
||||
}
|
||||
|
||||
return {
|
||||
id: `${taskItem.promptId}-${output.nodeId}-${output.filename}`,
|
||||
name: useDisplayName
|
||||
? truncateFilename(output.filename, 20)
|
||||
: output.filename,
|
||||
size: 0, // Size not available from history API
|
||||
created_at: taskItem.executionStartTimestamp
|
||||
? new Date(taskItem.executionStartTimestamp).toISOString()
|
||||
: new Date().toISOString(),
|
||||
tags: ['output'],
|
||||
preview_url: output.url,
|
||||
user_metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps input directory file to AssetItem format
|
||||
* @param filename The filename
|
||||
* @param index File index for unique ID
|
||||
* @param directory The directory type
|
||||
* @returns AssetItem formatted object
|
||||
*/
|
||||
export function mapInputFileToAssetItem(
|
||||
filename: string,
|
||||
index: number,
|
||||
directory: 'input' | 'output' = 'input'
|
||||
): AssetItem {
|
||||
return {
|
||||
id: `${directory}-${index}-${filename}`,
|
||||
name: filename,
|
||||
size: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
tags: [directory],
|
||||
preview_url: api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(filename)}&type=${directory}`
|
||||
)
|
||||
}
|
||||
}
|
||||
17
src/platform/assets/composables/useMediaAssets/index.ts
Normal file
17
src/platform/assets/composables/useMediaAssets/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
import type { IAssetsProvider } from './IAssetsProvider'
|
||||
import { useAssetsApi } from './useAssetsApi'
|
||||
import { useInternalFilesApi } from './useInternalFilesApi'
|
||||
|
||||
/**
|
||||
* Factory function that returns the appropriate media assets implementation
|
||||
* based on the current distribution (cloud vs internal)
|
||||
* @returns IAssetsProvider implementation
|
||||
*/
|
||||
export function useMediaAssets(): IAssetsProvider {
|
||||
return isCloud ? useAssetsApi() : useInternalFilesApi()
|
||||
}
|
||||
|
||||
// Re-export the interface for consumers
|
||||
export type { IAssetsProvider } from './IAssetsProvider'
|
||||
@@ -6,11 +6,13 @@ import type { HistoryTaskItem } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
import { mapTaskOutputToAssetItem } from './assetMappers'
|
||||
|
||||
/**
|
||||
* Composable for fetching media assets from cloud environment
|
||||
* Includes execution time from history API
|
||||
*/
|
||||
export function useCloudMediaAssets() {
|
||||
export function useAssetsApi() {
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -53,62 +55,16 @@ export function useCloudMediaAssets() {
|
||||
|
||||
// Only process completed tasks
|
||||
if (taskItem.displayStatus === 'Completed' && taskItem.outputs) {
|
||||
// Get execution time
|
||||
const executionTimeInSeconds = taskItem.executionTimeInSeconds
|
||||
|
||||
// Process each output
|
||||
taskItem.flatOutputs.forEach((output) => {
|
||||
// Only include output type files (not temp previews)
|
||||
if (output.type === 'output' && output.supportsPreview) {
|
||||
// Truncate filename if longer than 15 characters
|
||||
let displayName = output.filename
|
||||
if (output.filename.length > 20) {
|
||||
// Get file extension
|
||||
const lastDotIndex = output.filename.lastIndexOf('.')
|
||||
const nameWithoutExt =
|
||||
lastDotIndex > -1
|
||||
? output.filename.substring(0, lastDotIndex)
|
||||
: output.filename
|
||||
const extension =
|
||||
lastDotIndex > -1
|
||||
? output.filename.substring(lastDotIndex)
|
||||
: ''
|
||||
|
||||
// If name without extension is still long, truncate it
|
||||
if (nameWithoutExt.length > 10) {
|
||||
displayName =
|
||||
nameWithoutExt.substring(0, 10) +
|
||||
'...' +
|
||||
nameWithoutExt.substring(nameWithoutExt.length - 10) +
|
||||
extension
|
||||
}
|
||||
}
|
||||
|
||||
assetItems.push({
|
||||
id: `${taskItem.promptId}-${output.nodeId}-${output.filename}`,
|
||||
name: displayName,
|
||||
size: 0, // We don't have size info from history
|
||||
created_at: taskItem.executionStartTimestamp
|
||||
? new Date(taskItem.executionStartTimestamp).toISOString()
|
||||
: new Date().toISOString(),
|
||||
tags: ['output'],
|
||||
preview_url: output.url,
|
||||
user_metadata: {
|
||||
originalFilename: output.filename, // Store original filename
|
||||
promptId: taskItem.promptId,
|
||||
nodeId: output.nodeId,
|
||||
subfolder: output.subfolder,
|
||||
...(executionTimeInSeconds && {
|
||||
executionTimeInSeconds
|
||||
}),
|
||||
...(output.format && {
|
||||
format: output.format
|
||||
}),
|
||||
...(taskItem.workflow && {
|
||||
workflow: taskItem.workflow
|
||||
})
|
||||
}
|
||||
})
|
||||
const assetItem = mapTaskOutputToAssetItem(
|
||||
taskItem,
|
||||
output,
|
||||
true // Use display name for cloud
|
||||
)
|
||||
assetItems.push(assetItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,11 +5,16 @@ import type { HistoryTaskItem } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
import {
|
||||
mapInputFileToAssetItem,
|
||||
mapTaskOutputToAssetItem
|
||||
} from './assetMappers'
|
||||
|
||||
/**
|
||||
* Composable for fetching media assets from local environment
|
||||
* Uses the same logic as QueueSidebarTab for history processing
|
||||
*/
|
||||
export function useInternalMediaAssets() {
|
||||
export function useInternalFilesApi() {
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -37,16 +42,9 @@ export function useInternalMediaAssets() {
|
||||
}
|
||||
const filenames: string[] = await response.json()
|
||||
|
||||
return filenames.map((name, index) => ({
|
||||
id: `${directory}-${index}-${name}`,
|
||||
name,
|
||||
size: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
tags: [directory],
|
||||
preview_url: api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(name)}&type=${directory}`
|
||||
)
|
||||
}))
|
||||
return filenames.map((name, index) =>
|
||||
mapInputFileToAssetItem(name, index, directory)
|
||||
)
|
||||
}
|
||||
|
||||
// For output directory, use history data like QueueSidebarTab
|
||||
@@ -70,37 +68,16 @@ export function useInternalMediaAssets() {
|
||||
|
||||
// Only process completed tasks
|
||||
if (taskItem.displayStatus === 'Completed' && taskItem.outputs) {
|
||||
const executionTimeInSeconds = taskItem.executionTimeInSeconds
|
||||
const executionStartTimestamp = taskItem.executionStartTimestamp
|
||||
|
||||
// Process each output using flatOutputs like QueueSidebarTab
|
||||
taskItem.flatOutputs.forEach((output) => {
|
||||
// Only include output type files (not temp previews)
|
||||
if (output.type === 'output' && output.supportsPreview) {
|
||||
assetItems.push({
|
||||
id: `${taskItem.promptId}-${output.nodeId}-${output.filename}`,
|
||||
name: output.filename,
|
||||
size: 0,
|
||||
created_at: executionStartTimestamp
|
||||
? new Date(executionStartTimestamp).toISOString()
|
||||
: new Date().toISOString(),
|
||||
tags: ['output'],
|
||||
preview_url: output.url,
|
||||
user_metadata: {
|
||||
promptId: taskItem.promptId,
|
||||
nodeId: output.nodeId,
|
||||
subfolder: output.subfolder,
|
||||
...(executionTimeInSeconds && {
|
||||
executionTimeInSeconds
|
||||
}),
|
||||
...(output.format && {
|
||||
format: output.format
|
||||
}),
|
||||
...(taskItem.workflow && {
|
||||
workflow: taskItem.workflow
|
||||
})
|
||||
}
|
||||
})
|
||||
const assetItem = mapTaskOutputToAssetItem(
|
||||
taskItem,
|
||||
output,
|
||||
false // Don't use display name for internal
|
||||
)
|
||||
assetItems.push(assetItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
147
tests-ui/tests/utils/formatUtil.test.ts
Normal file
147
tests-ui/tests/utils/formatUtil.test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
getMediaTypeFromFilename,
|
||||
getMediaTypeFromFilenamePlural,
|
||||
truncateFilename
|
||||
} from '@/utils/formatUtil'
|
||||
|
||||
describe('formatUtil', () => {
|
||||
describe('truncateFilename', () => {
|
||||
it('should not truncate short filenames', () => {
|
||||
expect(truncateFilename('test.png')).toBe('test.png')
|
||||
expect(truncateFilename('short.jpg', 10)).toBe('short.jpg')
|
||||
})
|
||||
|
||||
it('should truncate long filenames while preserving extension', () => {
|
||||
const longName = 'this-is-a-very-long-filename-that-needs-truncation.png'
|
||||
const truncated = truncateFilename(longName, 20)
|
||||
expect(truncated).toContain('...')
|
||||
expect(truncated.endsWith('.png')).toBe(true)
|
||||
expect(truncated.length).toBeLessThanOrEqual(25) // 20 + '...' + extension
|
||||
})
|
||||
|
||||
it('should handle filenames without extensions', () => {
|
||||
const longName = 'this-is-a-very-long-filename-without-extension'
|
||||
const truncated = truncateFilename(longName, 20)
|
||||
expect(truncated).toContain('...')
|
||||
expect(truncated.length).toBeLessThanOrEqual(23) // 20 + '...'
|
||||
})
|
||||
|
||||
it('should handle empty strings', () => {
|
||||
expect(truncateFilename('')).toBe('')
|
||||
expect(truncateFilename('', 10)).toBe('')
|
||||
})
|
||||
|
||||
it('should preserve the start and end of the filename', () => {
|
||||
const longName = 'ComfyUI_00001_timestamp_2024_01_01.png'
|
||||
const truncated = truncateFilename(longName, 20)
|
||||
expect(truncated).toMatch(/^ComfyUI.*01\.png$/)
|
||||
expect(truncated).toContain('...')
|
||||
})
|
||||
|
||||
it('should handle files with multiple dots', () => {
|
||||
const filename = 'my.file.with.multiple.dots.txt'
|
||||
const truncated = truncateFilename(filename, 15)
|
||||
expect(truncated.endsWith('.txt')).toBe(true)
|
||||
expect(truncated).toContain('...')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMediaTypeFromFilename', () => {
|
||||
describe('image files', () => {
|
||||
it('should identify image extensions correctly', () => {
|
||||
expect(getMediaTypeFromFilename('test.png')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('photo.jpg')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('image.jpeg')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('animation.gif')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('web.webp')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('bitmap.bmp')).toBe('image')
|
||||
})
|
||||
|
||||
it('should handle uppercase extensions', () => {
|
||||
expect(getMediaTypeFromFilename('test.PNG')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('photo.JPG')).toBe('image')
|
||||
})
|
||||
})
|
||||
|
||||
describe('video files', () => {
|
||||
it('should identify video extensions correctly', () => {
|
||||
expect(getMediaTypeFromFilename('video.mp4')).toBe('video')
|
||||
expect(getMediaTypeFromFilename('clip.webm')).toBe('video')
|
||||
expect(getMediaTypeFromFilename('movie.mov')).toBe('video')
|
||||
expect(getMediaTypeFromFilename('film.avi')).toBe('video')
|
||||
})
|
||||
})
|
||||
|
||||
describe('audio files', () => {
|
||||
it('should identify audio extensions correctly', () => {
|
||||
expect(getMediaTypeFromFilename('song.mp3')).toBe('audio')
|
||||
expect(getMediaTypeFromFilename('sound.wav')).toBe('audio')
|
||||
expect(getMediaTypeFromFilename('music.ogg')).toBe('audio')
|
||||
expect(getMediaTypeFromFilename('audio.flac')).toBe('audio')
|
||||
})
|
||||
})
|
||||
|
||||
describe('3D files', () => {
|
||||
it('should identify 3D file extensions correctly', () => {
|
||||
expect(getMediaTypeFromFilename('model.obj')).toBe('3D')
|
||||
expect(getMediaTypeFromFilename('scene.fbx')).toBe('3D')
|
||||
expect(getMediaTypeFromFilename('asset.gltf')).toBe('3D')
|
||||
expect(getMediaTypeFromFilename('binary.glb')).toBe('3D')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle empty strings', () => {
|
||||
expect(getMediaTypeFromFilename('')).toBe('image')
|
||||
})
|
||||
|
||||
it('should handle files without extensions', () => {
|
||||
expect(getMediaTypeFromFilename('README')).toBe('image')
|
||||
})
|
||||
|
||||
it('should handle unknown extensions', () => {
|
||||
expect(getMediaTypeFromFilename('document.pdf')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('data.json')).toBe('image')
|
||||
})
|
||||
|
||||
it('should handle files with multiple dots', () => {
|
||||
expect(getMediaTypeFromFilename('my.file.name.png')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('archive.tar.gz')).toBe('image')
|
||||
})
|
||||
|
||||
it('should handle paths with directories', () => {
|
||||
expect(getMediaTypeFromFilename('/path/to/image.png')).toBe('image')
|
||||
expect(getMediaTypeFromFilename('C:\\Windows\\video.mp4')).toBe('video')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getMediaTypeFromFilenamePlural', () => {
|
||||
it('should return plural form for images', () => {
|
||||
expect(getMediaTypeFromFilenamePlural('test.png')).toBe('images')
|
||||
expect(getMediaTypeFromFilenamePlural('photo.jpg')).toBe('images')
|
||||
})
|
||||
|
||||
it('should return plural form for videos', () => {
|
||||
expect(getMediaTypeFromFilenamePlural('video.mp4')).toBe('videos')
|
||||
expect(getMediaTypeFromFilenamePlural('clip.webm')).toBe('videos')
|
||||
})
|
||||
|
||||
it('should return plural form for audios', () => {
|
||||
expect(getMediaTypeFromFilenamePlural('song.mp3')).toBe('audios')
|
||||
expect(getMediaTypeFromFilenamePlural('sound.wav')).toBe('audios')
|
||||
})
|
||||
|
||||
it('should return 3D as is (no plural)', () => {
|
||||
expect(getMediaTypeFromFilenamePlural('model.obj')).toBe('3D')
|
||||
expect(getMediaTypeFromFilenamePlural('scene.fbx')).toBe('3D')
|
||||
})
|
||||
|
||||
it('should default to images for unknown types', () => {
|
||||
expect(getMediaTypeFromFilenamePlural('document.pdf')).toBe('images')
|
||||
expect(getMediaTypeFromFilenamePlural('')).toBe('images')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user