Files
ComfyUI_frontend/src/composables/useFeatureFlags.test.ts
Johnpaul Chiwetelu 29220f6562 Road to No explicit any Part 8 (Group 3): Improve type safety in Group 3 test mocks (#8304)
## Summary

- Eliminated all `as unknown as` type assertions from Group 3 test files
- Created reusable factory functions in `litegraphTestUtils.ts` for
better type safety
- Improved test mock composition using `Partial` types with single `as`
casts
- Fixed LGraphNode tests to use proper API methods instead of direct
property assignment

## Changes by Category

### New Factory Functions in `litegraphTestUtils.ts`

- `createMockLGraphNodeWithArrayBoundingRect()` - Creates LGraphNode
with proper boundingRect for position tests
- `createMockFileList()` - Creates mock FileList with proper structure
including `item()` method

### Test File Improvements

**Composables:**
- `useLoad3dDrag.test.ts` - Used `createMockFileList` factory
- `useLoad3dViewer.test.ts` - Created local `MockSceneManager` interface
with proper typing

**LiteGraph Tests:**
- `LGraphNode.test.ts` - Replaced direct `boundingRect` assignments with
`updateArea()` calls
- `LinkConnector.test.ts` - Improved mock composition with proper
Partial types
- `ToOutputRenderLink.test.ts` - Added `MockEvents` interface for
type-safe event mocking
- Updated integration and core tests to use new factory functions

**Extension Tests:**
- `contextMenuFilter.test.ts` - Updated menu factories to accept
`(IContextMenuValue | null)[]`

## Type Safety Improvements

- Zero `as unknown as` instances (was: multiple instances across 17
files)
- All mocks use proper `Partial<T>` composition with single `as T` casts
- Improved IntelliSense and type checking in test files
- Centralized mock creation reduces duplication and improves
maintainability

## Test Plan

-  All TypeScript type checks pass
-  ESLint passes with no new errors  
-  Pre-commit hooks (format, lint, typecheck) all pass
-  Knip unused export check passes
-  No behavioral changes to actual tests (only type improvements)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8304-Road-to-No-explicit-any-Improve-type-safety-in-Group-3-test-mocks-2f36d73d365081ab841de96e5f01306d)
by [Unito](https://www.unito.io)
2026-01-26 18:13:18 +01:00

135 lines
3.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { isReactive, isReadonly } from 'vue'
import {
ServerFeatureFlag,
useFeatureFlags
} from '@/composables/useFeatureFlags'
import { api } from '@/scripts/api'
// Mock the API module
vi.mock('@/scripts/api', () => ({
api: {
getServerFeature: vi.fn()
}
}))
describe('useFeatureFlags', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('flags object', () => {
it('should provide reactive readonly flags', () => {
const { flags } = useFeatureFlags()
expect(isReadonly(flags)).toBe(true)
expect(isReactive(flags)).toBe(true)
})
it('should access supportsPreviewMetadata', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) return true
return defaultValue
}
)
const { flags } = useFeatureFlags()
expect(flags.supportsPreviewMetadata).toBe(true)
expect(api.getServerFeature).toHaveBeenCalledWith(
ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA
)
})
it('should access maxUploadSize', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 209715200 // 200MB
return defaultValue
}
)
const { flags } = useFeatureFlags()
expect(flags.maxUploadSize).toBe(209715200)
expect(api.getServerFeature).toHaveBeenCalledWith(
ServerFeatureFlag.MAX_UPLOAD_SIZE
)
})
it('should access supportsManagerV4', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === ServerFeatureFlag.MANAGER_SUPPORTS_V4) return true
return defaultValue
}
)
const { flags } = useFeatureFlags()
expect(flags.supportsManagerV4).toBe(true)
expect(api.getServerFeature).toHaveBeenCalledWith(
ServerFeatureFlag.MANAGER_SUPPORTS_V4
)
})
it('should return undefined when features are not available and no default provided', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(_path, defaultValue) => defaultValue
)
const { flags } = useFeatureFlags()
expect(flags.supportsPreviewMetadata).toBeUndefined()
expect(flags.maxUploadSize).toBeUndefined()
expect(flags.supportsManagerV4).toBeUndefined()
})
})
describe('featureFlag', () => {
it('should create reactive computed for custom feature flags', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === 'custom.feature') return 'custom-value'
return defaultValue
}
)
const { featureFlag } = useFeatureFlags()
const customFlag = featureFlag('custom.feature', 'default')
expect(customFlag.value).toBe('custom-value')
expect(api.getServerFeature).toHaveBeenCalledWith(
'custom.feature',
'default'
)
})
it('should handle nested paths', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === 'extension.custom.nested.feature') return true
return defaultValue
}
)
const { featureFlag } = useFeatureFlags()
const nestedFlag = featureFlag('extension.custom.nested.feature', false)
expect(nestedFlag.value).toBe(true)
})
it('should work with ServerFeatureFlag enum', () => {
vi.mocked(api.getServerFeature).mockImplementation(
(path, defaultValue) => {
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) return 104857600
return defaultValue
}
)
const { featureFlag } = useFeatureFlags()
const maxUploadSize = featureFlag(ServerFeatureFlag.MAX_UPLOAD_SIZE)
expect(maxUploadSize.value).toBe(104857600)
})
})
})