mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 20:51:58 +00:00
[refactor] Reorganize Vue nodes to domain-driven design architecture (#5085)
* refactor: Reorganize Vue nodes system to domain-driven design architecture Move Vue nodes code from scattered technical layers to domain-focused structure: - Widget system → src/renderer/extensions/vueNodes/widgets/ - LOD optimization → src/renderer/extensions/vueNodes/lod/ - Layout logic → src/renderer/extensions/vueNodes/layout/ - Node components → src/renderer/extensions/vueNodes/components/ - Test structure mirrors source organization Benefits: - Clear domain boundaries instead of technical layers - Everything Vue nodes related in renderer domain (not workbench) - camelCase naming (vueNodes vs vue-nodes) - Tests co-located with source domains - All imports updated to new DDD structure * fix: Skip spatial index performance test on CI to avoid flaky timing Performance tests are inherently flaky on CI due to variable system performance. This test should only run locally like the other performance tests.
This commit is contained in:
committed by
Benjamin Lu
parent
0dd4ff2087
commit
bfcbcf4873
@@ -1,270 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import {
|
||||
LODLevel,
|
||||
LOD_THRESHOLDS,
|
||||
supportsFeatureAtZoom,
|
||||
useLOD
|
||||
} from '@/composables/graph/useLOD'
|
||||
|
||||
describe('useLOD', () => {
|
||||
describe('LOD level detection', () => {
|
||||
it('should return MINIMAL for zoom <= 0.4', () => {
|
||||
const zoomRef = ref(0.4)
|
||||
const { lodLevel } = useLOD(zoomRef)
|
||||
expect(lodLevel.value).toBe(LODLevel.MINIMAL)
|
||||
|
||||
zoomRef.value = 0.2
|
||||
expect(lodLevel.value).toBe(LODLevel.MINIMAL)
|
||||
|
||||
zoomRef.value = 0.1
|
||||
expect(lodLevel.value).toBe(LODLevel.MINIMAL)
|
||||
})
|
||||
|
||||
it('should return REDUCED for 0.4 < zoom <= 0.8', () => {
|
||||
const zoomRef = ref(0.5)
|
||||
const { lodLevel } = useLOD(zoomRef)
|
||||
expect(lodLevel.value).toBe(LODLevel.REDUCED)
|
||||
|
||||
zoomRef.value = 0.6
|
||||
expect(lodLevel.value).toBe(LODLevel.REDUCED)
|
||||
|
||||
zoomRef.value = 0.8
|
||||
expect(lodLevel.value).toBe(LODLevel.REDUCED)
|
||||
})
|
||||
|
||||
it('should return FULL for zoom > 0.8', () => {
|
||||
const zoomRef = ref(0.9)
|
||||
const { lodLevel } = useLOD(zoomRef)
|
||||
expect(lodLevel.value).toBe(LODLevel.FULL)
|
||||
|
||||
zoomRef.value = 1.0
|
||||
expect(lodLevel.value).toBe(LODLevel.FULL)
|
||||
|
||||
zoomRef.value = 2.5
|
||||
expect(lodLevel.value).toBe(LODLevel.FULL)
|
||||
})
|
||||
|
||||
it('should be reactive to zoom changes', () => {
|
||||
const zoomRef = ref(0.2)
|
||||
const { lodLevel } = useLOD(zoomRef)
|
||||
|
||||
expect(lodLevel.value).toBe(LODLevel.MINIMAL)
|
||||
|
||||
zoomRef.value = 0.6
|
||||
expect(lodLevel.value).toBe(LODLevel.REDUCED)
|
||||
|
||||
zoomRef.value = 1.0
|
||||
expect(lodLevel.value).toBe(LODLevel.FULL)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rendering decisions', () => {
|
||||
it('should disable all rendering for MINIMAL LOD', () => {
|
||||
const zoomRef = ref(0.2)
|
||||
const {
|
||||
shouldRenderWidgets,
|
||||
shouldRenderSlots,
|
||||
shouldRenderContent,
|
||||
shouldRenderSlotLabels,
|
||||
shouldRenderWidgetLabels
|
||||
} = useLOD(zoomRef)
|
||||
|
||||
expect(shouldRenderWidgets.value).toBe(false)
|
||||
expect(shouldRenderSlots.value).toBe(false)
|
||||
expect(shouldRenderContent.value).toBe(false)
|
||||
expect(shouldRenderSlotLabels.value).toBe(false)
|
||||
expect(shouldRenderWidgetLabels.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should enable widgets/slots but disable labels for REDUCED LOD', () => {
|
||||
const zoomRef = ref(0.6)
|
||||
const {
|
||||
shouldRenderWidgets,
|
||||
shouldRenderSlots,
|
||||
shouldRenderContent,
|
||||
shouldRenderSlotLabels,
|
||||
shouldRenderWidgetLabels
|
||||
} = useLOD(zoomRef)
|
||||
|
||||
expect(shouldRenderWidgets.value).toBe(true)
|
||||
expect(shouldRenderSlots.value).toBe(true)
|
||||
expect(shouldRenderContent.value).toBe(false)
|
||||
expect(shouldRenderSlotLabels.value).toBe(false)
|
||||
expect(shouldRenderWidgetLabels.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should enable all rendering for FULL LOD', () => {
|
||||
const zoomRef = ref(1.0)
|
||||
const {
|
||||
shouldRenderWidgets,
|
||||
shouldRenderSlots,
|
||||
shouldRenderContent,
|
||||
shouldRenderSlotLabels,
|
||||
shouldRenderWidgetLabels
|
||||
} = useLOD(zoomRef)
|
||||
|
||||
expect(shouldRenderWidgets.value).toBe(true)
|
||||
expect(shouldRenderSlots.value).toBe(true)
|
||||
expect(shouldRenderContent.value).toBe(true)
|
||||
expect(shouldRenderSlotLabels.value).toBe(true)
|
||||
expect(shouldRenderWidgetLabels.value).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('CSS classes', () => {
|
||||
it('should return correct CSS class for each LOD level', () => {
|
||||
const zoomRef = ref(0.2)
|
||||
const { lodCssClass } = useLOD(zoomRef)
|
||||
|
||||
expect(lodCssClass.value).toBe('lg-node--lod-minimal')
|
||||
|
||||
zoomRef.value = 0.6
|
||||
expect(lodCssClass.value).toBe('lg-node--lod-reduced')
|
||||
|
||||
zoomRef.value = 1.0
|
||||
expect(lodCssClass.value).toBe('lg-node--lod-full')
|
||||
})
|
||||
})
|
||||
|
||||
describe('essential widgets filtering', () => {
|
||||
it('should return all widgets for FULL LOD', () => {
|
||||
const zoomRef = ref(1.0)
|
||||
const { getEssentialWidgets } = useLOD(zoomRef)
|
||||
|
||||
const widgets = [
|
||||
{ type: 'combo' },
|
||||
{ type: 'text' },
|
||||
{ type: 'button' },
|
||||
{ type: 'slider' }
|
||||
]
|
||||
|
||||
expect(getEssentialWidgets(widgets)).toEqual(widgets)
|
||||
})
|
||||
|
||||
it('should return empty array for MINIMAL LOD', () => {
|
||||
const zoomRef = ref(0.2)
|
||||
const { getEssentialWidgets } = useLOD(zoomRef)
|
||||
|
||||
const widgets = [{ type: 'combo' }, { type: 'text' }, { type: 'button' }]
|
||||
|
||||
expect(getEssentialWidgets(widgets)).toEqual([])
|
||||
})
|
||||
|
||||
it('should filter to essential types for REDUCED LOD', () => {
|
||||
const zoomRef = ref(0.6)
|
||||
const { getEssentialWidgets } = useLOD(zoomRef)
|
||||
|
||||
const widgets = [
|
||||
{ type: 'combo' },
|
||||
{ type: 'text' },
|
||||
{ type: 'button' },
|
||||
{ type: 'slider' },
|
||||
{ type: 'toggle' },
|
||||
{ type: 'number' }
|
||||
]
|
||||
|
||||
const essential = getEssentialWidgets(widgets)
|
||||
expect(essential).toHaveLength(4)
|
||||
expect(essential.map((w: any) => w.type)).toEqual([
|
||||
'combo',
|
||||
'slider',
|
||||
'toggle',
|
||||
'number'
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle case-insensitive widget types', () => {
|
||||
const zoomRef = ref(0.6)
|
||||
const { getEssentialWidgets } = useLOD(zoomRef)
|
||||
|
||||
const widgets = [
|
||||
{ type: 'COMBO' },
|
||||
{ type: 'Select' },
|
||||
{ type: 'TOGGLE' }
|
||||
]
|
||||
|
||||
const essential = getEssentialWidgets(widgets)
|
||||
expect(essential).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('should handle widgets with undefined or missing type', () => {
|
||||
const zoomRef = ref(0.6)
|
||||
const { getEssentialWidgets } = useLOD(zoomRef)
|
||||
|
||||
const widgets = [
|
||||
{ type: 'combo' },
|
||||
{ type: undefined },
|
||||
{},
|
||||
{ type: 'slider' }
|
||||
]
|
||||
|
||||
const essential = getEssentialWidgets(widgets)
|
||||
expect(essential).toHaveLength(2)
|
||||
expect(essential.map((w: any) => w.type)).toEqual(['combo', 'slider'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('performance metrics', () => {
|
||||
it('should provide debug metrics', () => {
|
||||
const zoomRef = ref(0.6)
|
||||
const { lodMetrics } = useLOD(zoomRef)
|
||||
|
||||
expect(lodMetrics.value).toEqual({
|
||||
level: LODLevel.REDUCED,
|
||||
zoom: 0.6,
|
||||
widgetCount: 'full',
|
||||
slotCount: 'full'
|
||||
})
|
||||
})
|
||||
|
||||
it('should update metrics when zoom changes', () => {
|
||||
const zoomRef = ref(0.2)
|
||||
const { lodMetrics } = useLOD(zoomRef)
|
||||
|
||||
expect(lodMetrics.value.level).toBe(LODLevel.MINIMAL)
|
||||
expect(lodMetrics.value.widgetCount).toBe('none')
|
||||
expect(lodMetrics.value.slotCount).toBe('none')
|
||||
|
||||
zoomRef.value = 1.0
|
||||
expect(lodMetrics.value.level).toBe(LODLevel.FULL)
|
||||
expect(lodMetrics.value.widgetCount).toBe('full')
|
||||
expect(lodMetrics.value.slotCount).toBe('full')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('LOD_THRESHOLDS', () => {
|
||||
it('should export correct threshold values', () => {
|
||||
expect(LOD_THRESHOLDS.FULL_THRESHOLD).toBe(0.8)
|
||||
expect(LOD_THRESHOLDS.REDUCED_THRESHOLD).toBe(0.4)
|
||||
expect(LOD_THRESHOLDS.MINIMAL_THRESHOLD).toBe(0.0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('supportsFeatureAtZoom', () => {
|
||||
it('should return correct feature support for different zoom levels', () => {
|
||||
expect(supportsFeatureAtZoom(1.0, 'renderWidgets')).toBe(true)
|
||||
expect(supportsFeatureAtZoom(1.0, 'renderSlots')).toBe(true)
|
||||
expect(supportsFeatureAtZoom(1.0, 'renderContent')).toBe(true)
|
||||
|
||||
expect(supportsFeatureAtZoom(0.6, 'renderWidgets')).toBe(true)
|
||||
expect(supportsFeatureAtZoom(0.6, 'renderSlots')).toBe(true)
|
||||
expect(supportsFeatureAtZoom(0.6, 'renderContent')).toBe(false)
|
||||
|
||||
expect(supportsFeatureAtZoom(0.2, 'renderWidgets')).toBe(false)
|
||||
expect(supportsFeatureAtZoom(0.2, 'renderSlots')).toBe(false)
|
||||
expect(supportsFeatureAtZoom(0.2, 'renderContent')).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle threshold boundary values correctly', () => {
|
||||
expect(supportsFeatureAtZoom(0.8, 'renderWidgets')).toBe(true)
|
||||
expect(supportsFeatureAtZoom(0.8, 'renderContent')).toBe(false)
|
||||
|
||||
expect(supportsFeatureAtZoom(0.81, 'renderContent')).toBe(true)
|
||||
|
||||
expect(supportsFeatureAtZoom(0.4, 'renderWidgets')).toBe(false)
|
||||
expect(supportsFeatureAtZoom(0.41, 'renderWidgets')).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,141 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { WidgetType } from '@/components/graph/vueWidgets/widgetRegistry'
|
||||
import { useWidgetRenderer } from '@/composables/graph/useWidgetRenderer'
|
||||
|
||||
describe('useWidgetRenderer', () => {
|
||||
const { getWidgetComponent, shouldRenderAsVue } = useWidgetRenderer()
|
||||
|
||||
describe('getWidgetComponent', () => {
|
||||
// Test number type mappings
|
||||
describe('number types', () => {
|
||||
it('should map number type to NUMBER widget', () => {
|
||||
expect(getWidgetComponent('number')).toBe(WidgetType.NUMBER)
|
||||
})
|
||||
|
||||
it('should map slider type to SLIDER widget', () => {
|
||||
expect(getWidgetComponent('slider')).toBe(WidgetType.SLIDER)
|
||||
})
|
||||
|
||||
it('should map INT type to INT widget', () => {
|
||||
expect(getWidgetComponent('INT')).toBe(WidgetType.INT)
|
||||
})
|
||||
|
||||
it('should map FLOAT type to FLOAT widget', () => {
|
||||
expect(getWidgetComponent('FLOAT')).toBe(WidgetType.FLOAT)
|
||||
})
|
||||
})
|
||||
|
||||
// Test text type mappings
|
||||
describe('text types', () => {
|
||||
it('should map text variations to STRING widget', () => {
|
||||
expect(getWidgetComponent('text')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('string')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('STRING')).toBe(WidgetType.STRING)
|
||||
})
|
||||
|
||||
it('should map multiline text types to TEXTAREA widget', () => {
|
||||
expect(getWidgetComponent('multiline')).toBe(WidgetType.TEXTAREA)
|
||||
expect(getWidgetComponent('textarea')).toBe(WidgetType.TEXTAREA)
|
||||
expect(getWidgetComponent('MARKDOWN')).toBe(WidgetType.MARKDOWN)
|
||||
expect(getWidgetComponent('customtext')).toBe(WidgetType.TEXTAREA)
|
||||
})
|
||||
})
|
||||
|
||||
// Test selection type mappings
|
||||
describe('selection types', () => {
|
||||
it('should map combo types to COMBO widget', () => {
|
||||
expect(getWidgetComponent('combo')).toBe(WidgetType.COMBO)
|
||||
expect(getWidgetComponent('COMBO')).toBe(WidgetType.COMBO)
|
||||
})
|
||||
})
|
||||
|
||||
// Test boolean type mappings
|
||||
describe('boolean types', () => {
|
||||
it('should map boolean types to appropriate widgets', () => {
|
||||
expect(getWidgetComponent('toggle')).toBe(WidgetType.TOGGLESWITCH)
|
||||
expect(getWidgetComponent('boolean')).toBe(WidgetType.BOOLEAN)
|
||||
expect(getWidgetComponent('BOOLEAN')).toBe(WidgetType.BOOLEAN)
|
||||
})
|
||||
})
|
||||
|
||||
// Test advanced widget mappings
|
||||
describe('advanced widgets', () => {
|
||||
it('should map color types to COLOR widget', () => {
|
||||
expect(getWidgetComponent('color')).toBe(WidgetType.COLOR)
|
||||
expect(getWidgetComponent('COLOR')).toBe(WidgetType.COLOR)
|
||||
})
|
||||
|
||||
it('should map image types to IMAGE widget', () => {
|
||||
expect(getWidgetComponent('image')).toBe(WidgetType.IMAGE)
|
||||
expect(getWidgetComponent('IMAGE')).toBe(WidgetType.IMAGE)
|
||||
})
|
||||
|
||||
it('should map file types to FILEUPLOAD widget', () => {
|
||||
expect(getWidgetComponent('file')).toBe(WidgetType.FILEUPLOAD)
|
||||
expect(getWidgetComponent('FILEUPLOAD')).toBe(WidgetType.FILEUPLOAD)
|
||||
})
|
||||
|
||||
it('should map button types to BUTTON widget', () => {
|
||||
expect(getWidgetComponent('button')).toBe(WidgetType.BUTTON)
|
||||
expect(getWidgetComponent('BUTTON')).toBe(WidgetType.BUTTON)
|
||||
})
|
||||
})
|
||||
|
||||
// Test fallback behavior
|
||||
describe('fallback behavior', () => {
|
||||
it('should return STRING widget for unknown types', () => {
|
||||
expect(getWidgetComponent('unknown')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('custom_widget')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('')).toBe(WidgetType.STRING)
|
||||
})
|
||||
|
||||
it('should return STRING widget for unmapped but valid types', () => {
|
||||
expect(getWidgetComponent('datetime')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('json')).toBe(WidgetType.STRING)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldRenderAsVue', () => {
|
||||
it('should return false for widgets marked as canvas-only', () => {
|
||||
const widget = { type: 'text', options: { canvasOnly: true } }
|
||||
expect(shouldRenderAsVue(widget)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for widgets without a type', () => {
|
||||
const widget = { options: {} }
|
||||
expect(shouldRenderAsVue(widget)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return true for widgets with mapped types', () => {
|
||||
expect(shouldRenderAsVue({ type: 'text' })).toBe(true)
|
||||
expect(shouldRenderAsVue({ type: 'number' })).toBe(true)
|
||||
expect(shouldRenderAsVue({ type: 'combo' })).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true even for unknown types (fallback to STRING)', () => {
|
||||
expect(shouldRenderAsVue({ type: 'unknown_type' })).toBe(true)
|
||||
})
|
||||
|
||||
it('should respect options while checking type', () => {
|
||||
const widget = { type: 'text', options: { someOption: 'value' } }
|
||||
expect(shouldRenderAsVue(widget)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle widgets with empty options', () => {
|
||||
const widget = { type: 'text', options: {} }
|
||||
expect(shouldRenderAsVue(widget)).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle case sensitivity correctly', () => {
|
||||
// Test that both lowercase and uppercase work
|
||||
expect(getWidgetComponent('string')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('STRING')).toBe(WidgetType.STRING)
|
||||
expect(getWidgetComponent('combo')).toBe(WidgetType.COMBO)
|
||||
expect(getWidgetComponent('COMBO')).toBe(WidgetType.COMBO)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user