From e7b44acc04dffb3fe72b7e3da6a863dd0073bb4d Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 9 Sep 2025 21:32:34 -0700 Subject: [PATCH] fix color picker value prefix and add component tests --- .../components/WidgetColorPicker.test.ts | 246 ++++++++++++++++++ .../widgets/components/WidgetColorPicker.vue | 4 +- 2 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts new file mode 100644 index 0000000000..4ea8edf07f --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.test.ts @@ -0,0 +1,246 @@ +import { mount } from '@vue/test-utils' +import ColorPicker from 'primevue/colorpicker' +import type { ColorPickerProps } from 'primevue/colorpicker' +import PrimeVue from 'primevue/config' +import { describe, expect, it } from 'vitest' + +import type { SimplifiedWidget } from '@/types/simplifiedWidget' + +import WidgetColorPicker from './WidgetColorPicker.vue' +import WidgetLayoutField from './layout/WidgetLayoutField.vue' + +describe('WidgetColorPicker Value Binding', () => { + const createMockWidget = ( + value: string = '#000000', + options: Partial = {}, + callback?: (value: string) => void + ): SimplifiedWidget => ({ + name: 'test_color_picker', + type: 'color', + value, + options, + callback + }) + + const mountComponent = ( + widget: SimplifiedWidget, + modelValue: string, + readonly = false + ) => { + return mount(WidgetColorPicker, { + global: { + plugins: [PrimeVue], + components: { + ColorPicker, + WidgetLayoutField + } + }, + props: { + widget, + modelValue, + readonly + } + }) + } + + const setColorPickerValue = async ( + wrapper: ReturnType, + value: string + ) => { + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + await colorPicker.setValue(value) + return wrapper.emitted('update:modelValue') + } + + describe('Vue Event Emission', () => { + it('emits Vue event when color changes', async () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const emitted = await setColorPickerValue(wrapper, '#00ff00') + + expect(emitted).toBeDefined() + expect(emitted![0]).toContain('#00ff00') + }) + + it('handles different color formats', async () => { + const widget = createMockWidget('#ffffff') + const wrapper = mountComponent(widget, '#ffffff') + + const emitted = await setColorPickerValue(wrapper, '#123abc') + + expect(emitted).toBeDefined() + expect(emitted![0]).toContain('#123abc') + }) + + it('handles missing callback gracefully', async () => { + const widget = createMockWidget('#000000', {}, undefined) + const wrapper = mountComponent(widget, '#000000') + + const emitted = await setColorPickerValue(wrapper, '#ff00ff') + + // Should still emit Vue event + expect(emitted).toBeDefined() + expect(emitted![0]).toContain('#ff00ff') + }) + }) + + describe('Component Rendering', () => { + it('renders color picker component', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.exists()).toBe(true) + }) + + it('renders layout field wrapper', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) + expect(layoutField.exists()).toBe(true) + }) + + it('displays current color value as text', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const colorText = wrapper.find('span') + expect(colorText.text()).toBe('#ff0000') + }) + + it('updates color text when value changes', async () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + await setColorPickerValue(wrapper, '#00ff00') + + // Need to check the local state update + const colorText = wrapper.find('span') + // Note: The actual text update depends on the component's reactive state + expect(colorText.exists()).toBe(true) + }) + + it('uses default color when no value provided', () => { + const widget = createMockWidget('') + const wrapper = mountComponent(widget, '') + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + // Should use the default value from the composable + expect(colorPicker.exists()).toBe(true) + }) + }) + + describe('Readonly Mode', () => { + it('disables color picker when readonly', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000', true) + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.props('disabled')).toBe(true) + }) + + it('enables color picker when not readonly', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000', false) + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.props('disabled')).toBe(false) + }) + }) + + describe('Color Formats', () => { + it('handles valid hex colors', async () => { + const validHexColors = [ + '#000000', + '#ffffff', + '#ff0000', + '#00ff00', + '#0000ff', + '#123abc' + ] + + for (const color of validHexColors) { + const widget = createMockWidget(color) + const wrapper = mountComponent(widget, color) + + const colorText = wrapper.find('span') + expect(colorText.text()).toBe(color) + } + }) + + it('handles short hex colors', () => { + const widget = createMockWidget('#fff') + const wrapper = mountComponent(widget, '#fff') + + const colorText = wrapper.find('span') + expect(colorText.text()).toBe('#fff') + }) + + it('passes widget options to color picker', () => { + const colorOptions = { + format: 'hex' as const, + inline: true + } + const widget = createMockWidget('#ff0000', colorOptions) + const wrapper = mountComponent(widget, '#ff0000') + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.props('format')).toBe('hex') + expect(colorPicker.props('inline')).toBe(true) + }) + }) + + describe('Widget Layout Integration', () => { + it('passes widget to layout field', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) + expect(layoutField.props('widget')).toEqual(widget) + }) + + it('maintains proper component structure', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + // Should have layout field containing label with color picker and text + const layoutField = wrapper.findComponent({ name: 'WidgetLayoutField' }) + const label = wrapper.find('label') + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + const colorText = wrapper.find('span') + + expect(layoutField.exists()).toBe(true) + expect(label.exists()).toBe(true) + expect(colorPicker.exists()).toBe(true) + expect(colorText.exists()).toBe(true) + }) + }) + + describe('Edge Cases', () => { + it('handles empty color value', () => { + const widget = createMockWidget('') + const wrapper = mountComponent(widget, '') + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.exists()).toBe(true) + }) + + it('handles invalid color formats gracefully', () => { + const widget = createMockWidget('invalid-color') + const wrapper = mountComponent(widget, 'invalid-color') + + const colorText = wrapper.find('span') + expect(colorText.text()).toBe('#invalid-color') + }) + + it('handles widget with no options', () => { + const widget = createMockWidget('#ff0000') + const wrapper = mountComponent(widget, '#ff0000') + + const colorPicker = wrapper.findComponent({ name: 'ColorPicker' }) + expect(colorPicker.exists()).toBe(true) + }) + }) +}) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue index ed5f2b0ecc..7202bd9592 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue @@ -16,7 +16,9 @@ }" @update:model-value="onChange" /> - #{{ localValue }} + {{ + localValue.startsWith('#') ? localValue : '#' + localValue + }}