diff --git a/packages/design-system/src/components/N8nInput/Input.vue b/packages/design-system/src/components/N8nInput/Input.vue index b15fcb3d3..d5419d231 100644 --- a/packages/design-system/src/components/N8nInput/Input.vue +++ b/packages/design-system/src/components/N8nInput/Input.vue @@ -26,14 +26,12 @@ import { computed, ref } from 'vue'; import { ElInput } from 'element-plus'; import { uid } from '../../utils'; - -const INPUT = ['text', 'textarea', 'number', 'password', 'email'] as const; -const SIZE = ['mini', 'small', 'medium', 'large', 'xlarge'] as const; +import type { InputSize, InputType } from '@/types/input'; interface InputProps { modelValue?: string | number; - type?: (typeof INPUT)[number]; - size?: (typeof SIZE)[number]; + type?: InputType; + size?: InputSize; placeholder?: string; disabled?: boolean; readonly?: boolean; diff --git a/packages/design-system/src/types/index.ts b/packages/design-system/src/types/index.ts index 75df13c12..eec00e115 100644 --- a/packages/design-system/src/types/index.ts +++ b/packages/design-system/src/types/index.ts @@ -2,6 +2,7 @@ export * from './button'; export * from './datatable'; export * from './form'; export * from './i18n'; +export * from './input'; export * from './menu'; export * from './select'; export * from './user'; diff --git a/packages/design-system/src/types/input.ts b/packages/design-system/src/types/input.ts new file mode 100644 index 000000000..5217b3217 --- /dev/null +++ b/packages/design-system/src/types/input.ts @@ -0,0 +1,5 @@ +const INPUT_TYPES = ['text', 'textarea', 'number', 'password', 'email'] as const; +const INPUT_SIZES = ['mini', 'small', 'medium', 'large', 'xlarge'] as const; + +export type InputType = (typeof INPUT_TYPES)[number]; +export type InputSize = (typeof INPUT_SIZES)[number]; diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index c3d952cd0..c5564ef64 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -1,14 +1,8 @@ - + - + @@ -133,14 +128,14 @@ > @@ -158,12 +153,12 @@ @@ -180,11 +175,11 @@ @@ -201,10 +196,10 @@ @@ -221,10 +216,10 @@ @@ -242,25 +237,26 @@ - - + @@ -295,7 +291,7 @@ @blur="onBlur" @update:model-value="valueChanged" /> - - - + - - + - - diff --git a/packages/editor-ui/src/components/__tests__/ParameterInput.test.ts b/packages/editor-ui/src/components/__tests__/ParameterInput.test.ts new file mode 100644 index 000000000..06b3e415a --- /dev/null +++ b/packages/editor-ui/src/components/__tests__/ParameterInput.test.ts @@ -0,0 +1,124 @@ +import { renderComponent } from '@/__tests__/render'; +import ParameterInput from '@/components/ParameterInput.vue'; +import type { useNDVStore } from '@/stores/ndv.store'; +import type { CompletionResult } from '@codemirror/autocomplete'; +import { createTestingPinia } from '@pinia/testing'; +import { faker } from '@faker-js/faker'; + +let mockNdvState: Partial>; +let mockCompletionResult: Partial; +import { waitFor } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; + +vi.mock('@/stores/ndv.store', () => { + return { + useNDVStore: vi.fn(() => mockNdvState), + }; +}); + +vi.mock('@/plugins/codemirror/completions/datatype.completions', () => { + return { + datatypeCompletions: vi.fn(() => mockCompletionResult), + }; +}); + +vi.mock('vue-router', () => { + const push = vi.fn(); + return { + useRouter: () => ({ + push, + }), + RouterLink: vi.fn(), + }; +}); + +describe('ParameterInput.vue', () => { + beforeEach(() => { + mockNdvState = { + hasInputData: true, + activeNode: { + id: faker.string.uuid(), + name: faker.word.words(3), + parameters: {}, + position: [faker.number.int(), faker.number.int()], + type: 'test', + typeVersion: 1, + }, + }; + }); + + test('should render an options parameter (select)', async () => { + const { container, baseElement, emitted } = renderComponent(ParameterInput, { + pinia: createTestingPinia(), + props: { + path: 'operation', + parameter: { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { show: { resource: ['sheet'] } }, + options: [ + { + name: 'Append or Update Row', + value: 'appendOrUpdate', + description: 'Append a new row or update an existing one (upsert)', + action: 'Append or update row in sheet', + }, + { + name: 'Append Row', + value: 'append', + description: 'Create a new row in a sheet', + action: 'Append row in sheet', + }, + ], + default: 'appendOrUpdate', + }, + modelValue: 'appendOrUpdate', + }, + }); + const select = container.querySelector('input') as HTMLInputElement; + const selectTrigger = container.querySelector('.select-trigger') as HTMLElement; + expect(select).toBeInTheDocument(); + expect(selectTrigger).toBeInTheDocument(); + await waitFor(() => expect(select).toHaveValue('Append or Update Row')); + + await userEvent.click(selectTrigger); + + const options = baseElement.querySelectorAll('.list-option'); + expect(options.length).toEqual(2); + expect(options[0].querySelector('.option-headline')).toHaveTextContent('Append or Update Row'); + expect(options[0].querySelector('.option-description')).toHaveTextContent( + 'Append a new row or update an existing one (upsert)', + ); + expect(options[1].querySelector('.option-headline')).toHaveTextContent('Append Row'); + expect(options[1].querySelector('.option-description')).toHaveTextContent( + 'Create a new row in a sheet', + ); + + await userEvent.click(options[1]); + + expect(emitted('update')).toContainEqual([expect.objectContaining({ value: 'append' })]); + }); + + test('should render a string parameter', async () => { + const { container, emitted } = renderComponent(ParameterInput, { + pinia: createTestingPinia(), + props: { + path: 'tag', + parameter: { + displayName: 'Tag', + name: 'tag', + type: 'string', + }, + modelValue: '', + }, + }); + const input = container.querySelector('input') as HTMLInputElement; + expect(input).toBeInTheDocument(); + + await userEvent.type(input, 'foo'); + + expect(emitted('update')).toContainEqual([expect.objectContaining({ value: 'foo' })]); + }); +});