Compare commits

...

2 Commits

Author SHA1 Message Date
GitHub Action
48883269cc [automated] Apply ESLint and Oxfmt fixes 2026-05-15 09:08:32 +00:00
dante01yoon
c5dd38f089 refactor(assets): extract getAssetStoredFilename helper, add mockFeatureFlags test util
L1 prerequisite cleanup. Two type-preserving refactors that make
subsequent L1 sub-issues mechanical.

1. Extract getAssetStoredFilename(asset) helper to collapse the
   duplicated `isCloud && asset.asset_hash ? asset.asset_hash :
   asset.name` branch from useMediaAssetActions (lines 302, 446)
   into a single helper. Once BE-933/934 emit file_path and the
   cloud spec sync brings it into generated types, only the helper
   changes.

2. Add mockFeatureFlags(overrides) test utility under src/test-utils/.
   L1 sub-issues will use this consistently instead of re-inventing
   the mock shape each time. Includes a FeatureFlags type export
   from useFeatureFlags.ts so the mock stays in sync.

Also auto-fixed unrelated tailwind class-order lint errors in four
files (VirtualGrid, RightSidePanel, Textarea, ModelInfoPanel) to
keep CI green.

No behavior change.
2026-05-15 18:04:19 +09:00
5 changed files with 113 additions and 12 deletions

View File

@@ -43,6 +43,12 @@ function resolveFlag<T>(
return remoteConfigValue ?? api.getServerFeature(flagKey, defaultValue)
}
/**
* Reactive feature-flag shape returned by {@link useFeatureFlags}.
* Exported so test mocks can stay in sync with the real shape.
*/
export type FeatureFlags = ReturnType<typeof useFeatureFlags>['flags']
/**
* Composable for reactive access to server-side feature flags
*/

View File

@@ -17,7 +17,10 @@ import { getOutputAssetMetadata } from '../schemas/assetMetadataSchema'
import { useAssetsStore } from '@/stores/assetsStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useNodeOutputStore } from '@/stores/nodeOutputStore'
import { getAssetDisplayName } from '../utils/assetMetadataUtils'
import {
getAssetDisplayName,
getAssetStoredFilename
} from '../utils/assetMetadataUtils'
import { getAssetType } from '../utils/assetTypeUtil'
import { getAssetUrl } from '../utils/assetUrlUtil'
import { clearDeletedAssetWidgetValues } from '../utils/clearDeletedAssetWidgetValues'
@@ -296,12 +299,7 @@ export function useMediaAssetActions() {
const metadata = getOutputAssetMetadata(targetAsset.user_metadata)
const assetType = getAssetType(targetAsset, 'input')
// In Cloud mode, use asset_hash (the actual stored filename)
// In OSS mode, use the original name
const filename =
isCloud && targetAsset.asset_hash
? targetAsset.asset_hash
: targetAsset.name
const filename = getAssetStoredFilename(targetAsset)
// Create annotated path for the asset
const annotated = createAnnotatedPath(
@@ -440,10 +438,7 @@ export function useMediaAssetActions() {
const metadata = getOutputAssetMetadata(asset.user_metadata)
const assetType = getAssetType(asset, 'input')
// In Cloud mode, use asset_hash (the actual stored filename)
// In OSS mode, use the original name
const filename =
isCloud && asset.asset_hash ? asset.asset_hash : asset.name
const filename = getAssetStoredFilename(asset)
const annotated = createAnnotatedPath(
{

View File

@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import {
@@ -12,11 +12,22 @@ import {
getAssetFilename,
getAssetModelType,
getAssetSourceUrl,
getAssetStoredFilename,
getAssetTriggerPhrases,
getAssetUserDescription,
getSourceName
} from '@/platform/assets/utils/assetMetadataUtils'
const { isCloudRef } = vi.hoisted(() => ({
isCloudRef: { value: true }
}))
vi.mock('@/platform/distribution/types', () => ({
get isCloud() {
return isCloudRef.value
}
}))
describe('assetMetadataUtils', () => {
const mockAsset: AssetItem = {
id: 'test-id',
@@ -295,6 +306,28 @@ describe('assetMetadataUtils', () => {
})
})
describe('getAssetStoredFilename', () => {
afterEach(() => {
isCloudRef.value = true
})
it('returns asset_hash on cloud when present', () => {
isCloudRef.value = true
expect(getAssetStoredFilename(mockAsset)).toBe('hash123')
})
it('falls back to name on cloud when asset_hash is missing', () => {
isCloudRef.value = true
const asset = { ...mockAsset, asset_hash: undefined }
expect(getAssetStoredFilename(asset)).toBe('test-model')
})
it('returns name on OSS regardless of asset_hash', () => {
isCloudRef.value = false
expect(getAssetStoredFilename(mockAsset)).toBe('test-model')
})
})
describe('getAssetFilename', () => {
it('returns user_metadata.filename when present', () => {
const asset = {

View File

@@ -1,4 +1,5 @@
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import { isCloud } from '@/platform/distribution/types'
import { isCivitaiUrl } from '@/utils/formatUtil'
/**
@@ -171,6 +172,24 @@ export function getAssetFilename(asset: AssetItem): string {
return getStringProperty(asset, 'filename') ?? asset.name
}
/**
* Resolves the *stored* filename for an asset — the filename used to
* construct asset paths (for /view URLs, widget values), not the
* user-facing display name.
*
* Cloud stores assets with `asset_hash` as the filename (content-
* addressed); OSS uses `name` (filesystem-backed). After BE-933/934
* emit `file_path` on both backends and the cloud spec sync brings
* the field into generated types, this collapses to
* `asset.file_path ?? asset.name` (no isCloud branch).
*
* For display use {@link getAssetDisplayFilename}; for serialized
* identifiers use {@link getAssetFilename}.
*/
export function getAssetStoredFilename(asset: AssetItem): string {
return isCloud && asset.asset_hash ? asset.asset_hash : asset.name
}
/**
* Gets the human-readable filename to render in UI surfaces.
* Fallback chain: user_metadata.filename → metadata.filename →

View File

@@ -0,0 +1,48 @@
import { vi } from 'vitest'
import type {
FeatureFlags,
useFeatureFlags
} from '@/composables/useFeatureFlags'
/**
* Mock implementation of `useFeatureFlags()` for unit/component tests.
*
* All flags default to their production opt-in default (mostly `false`);
* pass overrides to enable specific ones for the test.
*
* @example
* vi.mock('@/composables/useFeatureFlags', () => ({
* useFeatureFlags: () => mockFeatureFlags({ assetRenameEnabled: true })
* }))
*/
export function mockFeatureFlags(
overrides: Partial<FeatureFlags> = {}
): ReturnType<typeof useFeatureFlags> {
const flags: FeatureFlags = {
supportsPreviewMetadata: false,
maxUploadSize: 0,
supportsManagerV4: false,
modelUploadButtonEnabled: false,
assetRenameEnabled: false,
privateModelsEnabled: false,
onboardingSurveyEnabled: false,
linearToggleEnabled: false,
teamWorkspacesEnabled: false,
userSecretsEnabled: false,
nodeReplacementsEnabled: false,
nodeLibraryEssentialsEnabled: false,
workflowSharingEnabled: false,
comfyHubUploadEnabled: false,
comfyHubProfileGateEnabled: false,
showSignInButton: undefined,
...overrides
}
return {
flags,
featureFlag: vi.fn() as unknown as ReturnType<
typeof useFeatureFlags
>['featureFlag']
}
}