Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix unit tests
  • Loading branch information
AustinMroz committed Dec 13, 2025
commit a07097e25d2d511201781832438d3a7241b7a00e
30 changes: 29 additions & 1 deletion src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,34 @@ export interface GraphNodeManager {
cleanup(): void
}

function normalizeWidgetValue(value: unknown): WidgetValue {
if (value === null || value === undefined || value === void 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double check, but...

Suggested change
if (value === null || value === undefined || value === void 0) {
if (!value) {

or == null?

return undefined
}
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
return value
}
if (typeof value === 'object') {
// Check if it's a File array
if (
Array.isArray(value) &&
value.length > 0 &&
value.every((item): item is File => item instanceof File)
) {
return value
}
// Otherwise it's a generic object
return value
}
// If none of the above, return undefined
console.warn(`Invalid widget value type: ${typeof value}`, value)
return undefined
}

function getControlWidget(widget: IBaseWidget): (() => Ref<ControlOptions>)|undefined {
const cagWidget = widget.linkedWidgets?.find(
(w) => w.name == 'control_after_generate'
Expand Down Expand Up @@ -127,7 +155,7 @@ export function safeWidgetMapper(
})
widget.callback = useChainCallback(widget.callback, () => {
if (valueRef.value !== widget.value)
valueRef.value = validateWidgetValue(widget.value) ?? undefined
valueRef.value = normalizeWidgetValue(widget.value) ?? undefined
})
widget.valueRef = () => valueRef
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,7 @@ describe('WidgetButton Interactions', () => {
const widget = createMockWidget({}, mockCallback)
const wrapper = mountComponent(widget)

// Simulate rapid clicks
const clickPromises = Array.from({ length: 16 }, () =>
clickButton(wrapper)
)
await Promise.all(clickPromises)
for (let i = 0; i < 16; i++) await clickButton(wrapper)

expect(mockCallback).toHaveBeenCalledTimes(16)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<script setup lang="ts">
import Button from 'primevue/button'
import { computed, triggerRef } from 'vue'
import { computed } from 'vue'

import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import {
Expand All @@ -37,7 +37,8 @@ const filteredProps = computed(() =>
)

const handleClick = () => {
//FIXME: Will do nothing since backing value is unchanged
triggerRef(props.widget.value())
const ref = props.widget.value()
//@ts-expect-error - need to actually assign value, can't use triggerRef :(
ref.value = !ref.value
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function createMockWidget(
callback?: (value: number) => void
): SimplifiedWidget<number> {
const valueRef = ref(value)
if (callback) watch(valueRef, callback)
if (callback) watch(valueRef, (v) => callback(v))
return {
name: 'test_input_number',
type,
Expand Down Expand Up @@ -52,15 +52,14 @@ describe('WidgetInputNumberInput Value Binding', () => {
})

it('emits update:modelValue when value changes', async () => {
const widget = createMockWidget(10, 'int')
const callback = vi.fn()
const widget = createMockWidget(10, 'int', {}, callback)
const wrapper = mountComponent(widget, 10)

const inputNumber = wrapper.findComponent(InputNumber)
await inputNumber.vm.$emit('update:modelValue', 20)
await inputNumber.setValue(20)

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(20)
expect(callback).toHaveBeenCalledExactlyOnceWith(20)
})

it('handles negative values', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('WidgetInputText Value Binding', () => {
callback?: (value: string) => void
): SimplifiedWidget<string> => {
const valueRef = ref(value)
if (callback) watch(valueRef, callback)
if (callback) watch(valueRef, (v) => callback(v))
return {
name: 'test_input',
type: 'string',
Expand Down Expand Up @@ -58,86 +58,26 @@ describe('WidgetInputText Value Binding', () => {
return input
}

describe('Vue Event Emission', () => {
it('emits Vue event when input value changes on blur', async () => {
const widget = createMockWidget('hello')
const wrapper = mountComponent(widget, 'hello')

await setInputValueAndTrigger(wrapper, 'world', 'blur')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('world')
})

it('emits Vue event when enter key is pressed', async () => {
const widget = createMockWidget('initial')
const wrapper = mountComponent(widget, 'initial')

await setInputValueAndTrigger(wrapper, 'new value', 'keydown.enter')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('new value')
})

describe('Widget Value Callbacks', () => {
it('handles empty string values', async () => {
const widget = createMockWidget('something')
const callback = vi.fn()
const widget = createMockWidget('something', {}, callback)
const wrapper = mountComponent(widget, 'something')

await setInputValueAndTrigger(wrapper, '')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('')
expect(callback).toHaveBeenCalledExactlyOnceWith('')
})

it('handles special characters correctly', async () => {
const widget = createMockWidget('normal')
const callback = vi.fn()
const widget = createMockWidget('normal', {}, callback)
const wrapper = mountComponent(widget, 'normal')

const specialText = 'special @#$%^&*()[]{}|\\:";\'<>?,./'
await setInputValueAndTrigger(wrapper, specialText)

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(specialText)
})

it('handles missing callback gracefully', async () => {
const widget = createMockWidget('test', {}, undefined)
const wrapper = mountComponent(widget, 'test')

await setInputValueAndTrigger(wrapper, 'new value')

// Should still emit Vue event
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('new value')
})
})

describe('User Interactions', () => {
it('emits update:modelValue on blur', async () => {
const widget = createMockWidget('original')
const wrapper = mountComponent(widget, 'original')

await setInputValueAndTrigger(wrapper, 'updated')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('updated')
})

it('emits update:modelValue on enter key', async () => {
const widget = createMockWidget('start')
const wrapper = mountComponent(widget, 'start')

await setInputValueAndTrigger(wrapper, 'finish', 'keydown.enter')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain('finish')
expect(callback).toHaveBeenCalledExactlyOnceWith(specialText)
})
})

Expand All @@ -158,27 +98,25 @@ describe('WidgetInputText Value Binding', () => {

describe('Edge Cases', () => {
it('handles very long strings', async () => {
const widget = createMockWidget('short')
const callback = vi.fn()
const widget = createMockWidget('short', {}, callback)
const wrapper = mountComponent(widget, 'short')

const longString = 'a'.repeat(10000)
await setInputValueAndTrigger(wrapper, longString)

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(longString)
expect(callback).toHaveBeenCalledExactlyOnceWith(longString)
})

it('handles unicode characters', async () => {
const widget = createMockWidget('ascii')
const callback = vi.fn()
const widget = createMockWidget('ascii', {}, callback)
const wrapper = mountComponent(widget, 'ascii')

const unicodeText = '🎨 Unicode: αβγ 中文 العربية 🚀'
await setInputValueAndTrigger(wrapper, unicodeText)

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(unicodeText)
expect(callback).toHaveBeenCalledExactlyOnceWith(unicodeText)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('WidgetMarkdown Dual Mode Display', () => {
callback?: (value: string) => void
): SimplifiedWidget<string> => {
const valueRef = ref(value)
if (callback) watch(valueRef, callback)
if (callback) watch(valueRef, (v) => callback(v))
return {
name: 'test_markdown',
type: 'string',
Expand Down Expand Up @@ -213,7 +213,8 @@ describe('WidgetMarkdown Dual Mode Display', () => {

describe('Value Updates', () => {
it('emits update:modelValue when textarea content changes', async () => {
const widget = createMockWidget('# Original')
const callback = vi.fn()
const widget = createMockWidget('# Original', {}, callback)
const wrapper = mountComponent(widget, '# Original')

await clickToEdit(wrapper)
Expand All @@ -222,9 +223,7 @@ describe('WidgetMarkdown Dual Mode Display', () => {
await textarea.setValue('# Updated Content')
await textarea.trigger('input')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![emitted!.length - 1]).toEqual(['# Updated Content'])
expect(callback).toHaveBeenLastCalledWith('# Updated Content')
})

it('renders updated HTML after value change and blur', async () => {
Expand All @@ -242,38 +241,6 @@ describe('WidgetMarkdown Dual Mode Display', () => {
expect(displayDiv.html()).toContain('<h2>New Heading</h2>')
expect(displayDiv.html()).toContain('<strong>bold</strong>')
})

it('emits update:modelValue for callback handling at parent level', async () => {
const widget = createMockWidget('# Test', {})
const wrapper = mountComponent(widget, '# Test')

await clickToEdit(wrapper)

const textarea = wrapper.find('textarea')
await textarea.setValue('# Changed')
await textarea.trigger('input')

// The widget should emit the change for parent (NodeWidgets) to handle callbacks
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![emitted!.length - 1]).toEqual(['# Changed'])
})

it('handles missing callback gracefully', async () => {
const widget = createMockWidget('# Test', {}, undefined)
const wrapper = mountComponent(widget, '# Test')

await clickToEdit(wrapper)

const textarea = wrapper.find('textarea')
await textarea.setValue('# Changed')

// Should not throw error and should still emit Vue event
await expect(textarea.trigger('input')).resolves.not.toThrow()

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
})
})

describe('Complex Markdown Rendering', () => {
Expand Down Expand Up @@ -340,8 +307,9 @@ Another line with more content.`
})

it('handles unicode characters', async () => {
const callback = vi.fn()
const unicode = '# Unicode: 🎨 αβγ 中文 العربية 🚀'
const widget = createMockWidget(unicode)
const widget = createMockWidget(unicode, {}, callback)
const wrapper = mountComponent(widget, unicode)

await clickToEdit(wrapper)
Expand All @@ -351,9 +319,7 @@ Another line with more content.`
await textarea.setValue(unicode + ' more unicode')
await textarea.trigger('input')

const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![emitted!.length - 1]).toEqual([unicode + ' more unicode'])
expect(callback).toHaveBeenLastCalledWith(unicode + ' more unicode')
})

it('handles rapid edit mode toggling', async () => {
Expand Down
Loading