diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts new file mode 100644 index 000000000..72d6726a5 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts @@ -0,0 +1,248 @@ +import { mount } from '@vue/test-utils' +import Button from 'primevue/button' +import type { ButtonProps } from 'primevue/button' +import PrimeVue from 'primevue/config' +import { describe, expect, it, vi } from 'vitest' + +import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue' +import type { SimplifiedWidget } from '@/types/simplifiedWidget' + +describe('WidgetButton Interactions', () => { + const createMockWidget = ( + options: Partial = {}, + callback?: () => void, + name: string = 'test_button' + ): SimplifiedWidget => ({ + name, + type: 'button', + value: undefined, + options, + callback + }) + + const mountComponent = (widget: SimplifiedWidget, readonly = false) => { + return mount(WidgetButton, { + global: { + plugins: [PrimeVue], + components: { Button } + }, + props: { + widget, + readonly + } + }) + } + + const clickButton = async (wrapper: ReturnType) => { + const button = wrapper.findComponent({ name: 'Button' }) + await button.trigger('click') + return button + } + + describe('Click Handling', () => { + it('calls callback when button is clicked', async () => { + const mockCallback = vi.fn() + const widget = createMockWidget({}, mockCallback) + const wrapper = mountComponent(widget) + + await clickButton(wrapper) + + expect(mockCallback).toHaveBeenCalledTimes(1) + }) + + it('does not call callback when button is readonly', async () => { + const mockCallback = vi.fn() + const widget = createMockWidget({}, mockCallback) + const wrapper = mountComponent(widget, true) + + await clickButton(wrapper) + + expect(mockCallback).not.toHaveBeenCalled() + }) + + it('handles missing callback gracefully', async () => { + const widget = createMockWidget({}, undefined) + const wrapper = mountComponent(widget) + + // Should not throw error when clicking without callback + await expect(clickButton(wrapper)).resolves.toBeDefined() + }) + + it('calls callback multiple times when clicked multiple times', async () => { + const mockCallback = vi.fn() + const widget = createMockWidget({}, mockCallback) + const wrapper = mountComponent(widget) + + const numClicks = 8 + + await clickButton(wrapper) + for (let i = 0; i < numClicks; i++) { + await clickButton(wrapper) + } + + expect(mockCallback).toHaveBeenCalledTimes(numClicks) + }) + }) + + describe('Component Rendering', () => { + it('renders button component', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.exists()).toBe(true) + }) + + it('renders widget label when name is provided', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget) + + const label = wrapper.find('label') + expect(label.exists()).toBe(true) + expect(label.text()).toBe('test_button') + }) + + it('does not render label when widget name is empty', () => { + const widget = createMockWidget({}, undefined, '') + const wrapper = mountComponent(widget) + + const label = wrapper.find('label') + expect(label.exists()).toBe(false) + }) + + it('sets button size to small', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('size')).toBe('small') + }) + + it('passes widget options to button component', () => { + const buttonOptions = { + label: 'Custom Label', + icon: 'pi pi-check', + severity: 'success' as const + } + const widget = createMockWidget(buttonOptions) + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('label')).toBe('Custom Label') + expect(button.props('icon')).toBe('pi pi-check') + expect(button.props('severity')).toBe('success') + }) + }) + + describe('Readonly Mode', () => { + it('disables button when readonly', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget, true) + + // Test the actual DOM button element instead of the Vue component props + const buttonElement = wrapper.find('button') + expect(buttonElement.element.disabled).toBe(true) + }) + + it('enables button when not readonly', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget, false) + + // Test the actual DOM button element instead of the Vue component props + const buttonElement = wrapper.find('button') + expect(buttonElement.element.disabled).toBe(false) + }) + }) + + describe('Widget Options', () => { + it('handles button with text only', () => { + const widget = createMockWidget({ label: 'Click Me' }) + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('label')).toBe('Click Me') + expect(button.props('icon')).toBeNull() + }) + + it('handles button with icon only', () => { + const widget = createMockWidget({ icon: 'pi pi-star' }) + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('icon')).toBe('pi pi-star') + }) + + it('handles button with both text and icon', () => { + const widget = createMockWidget({ + label: 'Save', + icon: 'pi pi-save' + }) + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('label')).toBe('Save') + expect(button.props('icon')).toBe('pi pi-save') + }) + + it.for([ + 'secondary', + 'success', + 'info', + 'warning', + 'danger', + 'help', + 'contrast' + ] as const)('handles button severity: %s', (severity) => { + const widget = createMockWidget({ severity }) + const wrapper = mountComponent(widget) + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('severity')).toBe(severity) + }) + + it.for(['outlined', 'text'] as const)( + 'handles button variant: %s', + (variant) => { + const widget = createMockWidget({ variant }) + const wrapper = mountComponent(widget) + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.props('variant')).toBe(variant) + } + ) + }) + + describe('Edge Cases', () => { + it('handles widget with no options', () => { + const widget = createMockWidget() + const wrapper = mountComponent(widget) + + const button = wrapper.findComponent({ name: 'Button' }) + expect(button.exists()).toBe(true) + }) + + it('handles callback that throws error', async () => { + const mockCallback = vi.fn(() => { + throw new Error('Callback error') + }) + const widget = createMockWidget({}, mockCallback) + const wrapper = mountComponent(widget) + + // Should not break the component when callback throws + await expect(clickButton(wrapper)).rejects.toThrow('Callback error') + expect(mockCallback).toHaveBeenCalledTimes(1) + }) + + it('handles rapid consecutive clicks', async () => { + const mockCallback = vi.fn() + const widget = createMockWidget({}, mockCallback) + const wrapper = mountComponent(widget) + + // Simulate rapid clicks + const clickPromises = Array.from({ length: 16 }, () => + clickButton(wrapper) + ) + await Promise.all(clickPromises) + + expect(mockCallback).toHaveBeenCalledTimes(16) + }) + }) +})