mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-12 08:30:08 +00:00
Added `with { type: 'json' }` assertions to all JSON imports to ensure compatibility with Node.js ES modules and Playwright environments. This follows the current ESM specification where JSON imports require explicit type assertions.
Affected areas:
- Tailwind config
- i18n locale imports (36 files)
- Test fixtures and spec files
- API client feature flags
- Core color palettes
References:
- https://nodejs.org/api/esm.html
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
import { mount } from '@vue/test-utils'
|
|
import { createPinia } from 'pinia'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { type PropType, defineComponent } from 'vue'
|
|
import { createI18n } from 'vue-i18n'
|
|
|
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
|
import enMessages from '@/locales/en/main.json' with { type: 'json' }
|
|
|
|
import NodeSlots from './NodeSlots.vue'
|
|
|
|
const makeNodeData = (overrides: Partial<VueNodeData> = {}): VueNodeData => ({
|
|
id: '123',
|
|
title: 'Test Node',
|
|
type: 'TestType',
|
|
mode: 0,
|
|
selected: false,
|
|
executing: false,
|
|
inputs: [],
|
|
outputs: [],
|
|
widgets: [],
|
|
flags: { collapsed: false },
|
|
...overrides
|
|
})
|
|
|
|
// Explicit stubs to capture props for assertions
|
|
interface StubSlotData {
|
|
name?: string
|
|
type?: string
|
|
boundingRect?: [number, number, number, number]
|
|
}
|
|
|
|
const InputSlotStub = defineComponent({
|
|
name: 'InputSlot',
|
|
props: {
|
|
slotData: { type: Object as PropType<StubSlotData>, required: true },
|
|
nodeId: { type: String, required: false, default: '' },
|
|
index: { type: Number, required: true },
|
|
readonly: { type: Boolean, required: false, default: false }
|
|
},
|
|
template: `
|
|
<div
|
|
class="stub-input-slot"
|
|
:data-index="index"
|
|
:data-name="slotData && slotData.name ? slotData.name : ''"
|
|
:data-type="slotData && slotData.type ? slotData.type : ''"
|
|
:data-node-id="nodeId"
|
|
:data-readonly="readonly ? 'true' : 'false'"
|
|
/>
|
|
`
|
|
})
|
|
|
|
const OutputSlotStub = defineComponent({
|
|
name: 'OutputSlot',
|
|
props: {
|
|
slotData: { type: Object as PropType<StubSlotData>, required: true },
|
|
nodeId: { type: String, required: false, default: '' },
|
|
index: { type: Number, required: true },
|
|
readonly: { type: Boolean, required: false, default: false }
|
|
},
|
|
template: `
|
|
<div
|
|
class="stub-output-slot"
|
|
:data-index="index"
|
|
:data-name="slotData && slotData.name ? slotData.name : ''"
|
|
:data-type="slotData && slotData.type ? slotData.type : ''"
|
|
:data-node-id="nodeId"
|
|
:data-readonly="readonly ? 'true' : 'false'"
|
|
/>
|
|
`
|
|
})
|
|
|
|
const mountSlots = (nodeData: VueNodeData, readonly = false) => {
|
|
const i18n = createI18n({
|
|
legacy: false,
|
|
locale: 'en',
|
|
messages: { en: enMessages }
|
|
})
|
|
return mount(NodeSlots, {
|
|
global: {
|
|
plugins: [i18n, createPinia()],
|
|
stubs: {
|
|
InputSlot: InputSlotStub,
|
|
OutputSlot: OutputSlotStub
|
|
}
|
|
},
|
|
props: { nodeData, readonly }
|
|
})
|
|
}
|
|
|
|
describe('NodeSlots.vue', () => {
|
|
it('filters out inputs with widget property and maps indexes correctly', () => {
|
|
// Two inputs without widgets (object and string) and one with widget (filtered)
|
|
const inputObjNoWidget = {
|
|
name: 'objNoWidget',
|
|
type: 'number',
|
|
boundingRect: [0, 0, 0, 0]
|
|
}
|
|
const inputObjWithWidget = {
|
|
name: 'objWithWidget',
|
|
type: 'number',
|
|
boundingRect: [0, 0, 0, 0],
|
|
widget: { name: 'objWithWidget' }
|
|
}
|
|
const inputs = [inputObjNoWidget, inputObjWithWidget, 'stringInput']
|
|
|
|
const wrapper = mountSlots(makeNodeData({ inputs }))
|
|
|
|
const inputEls = wrapper
|
|
.findAll('.stub-input-slot')
|
|
.map((w) => w.element as HTMLElement)
|
|
// Should filter out the widget-backed input; expect 2 inputs rendered
|
|
expect(inputEls.length).toBe(2)
|
|
|
|
// Verify expected tuple of {index, name, nodeId}
|
|
const info = inputEls.map((el) => ({
|
|
index: Number(el.dataset.index),
|
|
name: el.dataset.name ?? '',
|
|
nodeId: el.dataset.nodeId ?? '',
|
|
type: el.dataset.type ?? '',
|
|
readonly: el.dataset.readonly === 'true'
|
|
}))
|
|
expect(info).toEqual([
|
|
{
|
|
index: 0,
|
|
name: 'objNoWidget',
|
|
nodeId: '123',
|
|
type: 'number',
|
|
readonly: false
|
|
},
|
|
// string input is converted to object with default type 'any'
|
|
{
|
|
index: 1,
|
|
name: 'stringInput',
|
|
nodeId: '123',
|
|
type: 'any',
|
|
readonly: false
|
|
}
|
|
])
|
|
|
|
// Ensure widget-backed input was indeed filtered out
|
|
expect(wrapper.find('[data-name="objWithWidget"]').exists()).toBe(false)
|
|
})
|
|
|
|
it('maps outputs and passes correct indexes', () => {
|
|
const outputObj = { name: 'outA', type: 'any', boundingRect: [0, 0, 0, 0] }
|
|
const outputs = [outputObj, 'outB']
|
|
|
|
const wrapper = mountSlots(makeNodeData({ outputs }))
|
|
const outputEls = wrapper
|
|
.findAll('.stub-output-slot')
|
|
.map((w) => w.element as HTMLElement)
|
|
|
|
expect(outputEls.length).toBe(2)
|
|
const outInfo = outputEls.map((el) => ({
|
|
index: Number(el.dataset.index),
|
|
name: el.dataset.name ?? '',
|
|
nodeId: el.dataset.nodeId ?? '',
|
|
type: el.dataset.type ?? '',
|
|
readonly: el.dataset.readonly === 'true'
|
|
}))
|
|
expect(outInfo).toEqual([
|
|
{ index: 0, name: 'outA', nodeId: '123', type: 'any', readonly: false },
|
|
// string output mapped to object with type 'any'
|
|
{ index: 1, name: 'outB', nodeId: '123', type: 'any', readonly: false }
|
|
])
|
|
})
|
|
|
|
it('renders nothing when there are no inputs/outputs', () => {
|
|
const wrapper = mountSlots(makeNodeData({ inputs: [], outputs: [] }))
|
|
expect(wrapper.findAll('.stub-input-slot').length).toBe(0)
|
|
expect(wrapper.findAll('.stub-output-slot').length).toBe(0)
|
|
})
|
|
|
|
it('passes readonly to child slots', () => {
|
|
const wrapper = mountSlots(
|
|
makeNodeData({ inputs: ['a'], outputs: ['b'] }),
|
|
/* readonly */ true
|
|
)
|
|
const all = [
|
|
...wrapper
|
|
.findAll('.stub-input-slot')
|
|
.filter((w) => w.element instanceof HTMLElement)
|
|
.map((w) => w.element as HTMLElement),
|
|
...wrapper
|
|
.findAll('.stub-output-slot')
|
|
.filter((w) => w.element instanceof HTMLElement)
|
|
.map((w) => w.element as HTMLElement)
|
|
]
|
|
expect(all.length).toBe(2)
|
|
for (const el of all) {
|
|
expect.soft(el.dataset.readonly).toBe('true')
|
|
}
|
|
})
|
|
})
|