diff --git a/packages/frontend/editor-ui/src/components/ImportCurlModal.test.ts b/packages/frontend/editor-ui/src/components/ImportCurlModal.test.ts new file mode 100644 index 0000000000..ce2beab6ea --- /dev/null +++ b/packages/frontend/editor-ui/src/components/ImportCurlModal.test.ts @@ -0,0 +1,157 @@ +import { createComponentRenderer } from '@/__tests__/render'; +import ImportCurlModal from './ImportCurlModal.vue'; +import { createTestingPinia } from '@pinia/testing'; +import { IMPORT_CURL_MODAL_KEY } from '@/constants'; +import { cleanupAppModals, createAppModals, mockedStore } from '@/__tests__/utils'; +import { nextTick } from 'vue'; +import { useUIStore } from '@/stores/ui.store'; +import { useNDVStore } from '@/stores/ndv.store'; +import userEvent from '@testing-library/user-event'; + +const mockTelemetryTrack = vi.fn(); +vi.mock('@/composables/useTelemetry', () => ({ + useTelemetry: () => ({ + track: mockTelemetryTrack, + }), +})); + +vi.mock('@/composables/useImportCurlCommand', () => ({ + useImportCurlCommand: (options: { + onImportSuccess: () => void; + onAfterImport: () => void; + }) => ({ + importCurlCommand: () => { + options.onImportSuccess(); + options.onAfterImport(); + }, + }), +})); + +const renderModal = createComponentRenderer(ImportCurlModal, { + pinia: createTestingPinia(), +}); + +const testNode = { + id: 'node-1', + name: 'HTTP Request', + type: 'n8n-nodes-base.httpRequest', + position: [0, 0] as [number, number], + typeVersion: 1, + parameters: {}, +}; + +describe('ImportCurlModal', () => { + beforeEach(() => { + vi.clearAllMocks(); + createAppModals(); + }); + + afterEach(() => { + cleanupAppModals(); + }); + + it('should show empty input when no curl command exists for active node', async () => { + const uiStore = mockedStore(useUIStore); + uiStore.modalsById = { + [IMPORT_CURL_MODAL_KEY]: { + open: true, + data: { + curlCommands: { + 'node-2': 'curl -X GET https://api.example.com/data', + }, + }, + }, + }; + uiStore.modalStack = [IMPORT_CURL_MODAL_KEY]; + const ndvStore = mockedStore(useNDVStore); + ndvStore.activeNode = testNode; + + const { getByTestId } = renderModal(); + await nextTick(); + + const input = getByTestId('import-curl-modal-input'); + expect(input).toHaveValue(''); + }); + + it('should show curl command for active node', async () => { + const uiStore = mockedStore(useUIStore); + uiStore.modalsById = { + [IMPORT_CURL_MODAL_KEY]: { + open: true, + data: { + curlCommands: { + 'node-1': 'curl -X GET https://api.example.com/data', + 'node-2': 'curl -X POST https://api.example.com/submit', + }, + }, + }, + }; + uiStore.modalStack = [IMPORT_CURL_MODAL_KEY]; + const ndvStore = mockedStore(useNDVStore); + ndvStore.activeNode = testNode; + + const { getByTestId } = renderModal(); + await nextTick(); + + const input = getByTestId('import-curl-modal-input'); + expect(input).toHaveValue('curl -X GET https://api.example.com/data'); + }); + + it('should set the input value when the import button is clicked', async () => { + const uiStore = mockedStore(useUIStore); + uiStore.modalsById = { + [IMPORT_CURL_MODAL_KEY]: { + open: true, + data: { + curlCommands: { + 'node-2': 'curl -X POST https://api.example.com/submit', + }, + }, + }, + }; + uiStore.modalStack = [IMPORT_CURL_MODAL_KEY]; + const ndvStore = mockedStore(useNDVStore); + ndvStore.activeNode = testNode; + + const { getByTestId } = renderModal(); + await nextTick(); + + const input = getByTestId('import-curl-modal-input'); + await userEvent.type(input, 'curl -X GET https://api.example.com/data'); + const button = getByTestId('import-curl-modal-button'); + await userEvent.click(button); + expect(uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands).toEqual({ + 'node-1': 'curl -X GET https://api.example.com/data', + 'node-2': 'curl -X POST https://api.example.com/submit', + }); + }); + + it('should override the input value when the import button is clicked', async () => { + const uiStore = mockedStore(useUIStore); + uiStore.modalsById = { + [IMPORT_CURL_MODAL_KEY]: { + open: true, + data: { + curlCommands: { + 'node-1': 'curl -X GET https://api.example.com/data', + }, + }, + }, + }; + uiStore.modalStack = [IMPORT_CURL_MODAL_KEY]; + const ndvStore = mockedStore(useNDVStore); + ndvStore.activeNode = testNode; + + const { getByTestId } = renderModal(); + await nextTick(); + + const input = getByTestId('import-curl-modal-input'); + await userEvent.clear(input); + await userEvent.type(input, 'curl -X GET https://api.example.com/other'); + const button = getByTestId('import-curl-modal-button'); + await userEvent.click(button); + expect(uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands).toEqual({ + 'node-1': 'curl -X GET https://api.example.com/other', + }); + }); +}); diff --git a/packages/frontend/editor-ui/src/components/ImportCurlModal.vue b/packages/frontend/editor-ui/src/components/ImportCurlModal.vue index 9a8afe60fe..8c1fc1b1f7 100644 --- a/packages/frontend/editor-ui/src/components/ImportCurlModal.vue +++ b/packages/frontend/editor-ui/src/components/ImportCurlModal.vue @@ -6,11 +6,13 @@ import { useUIStore } from '@/stores/ui.store'; import { createEventBus } from '@n8n/utils/event-bus'; import { useTelemetry } from '@/composables/useTelemetry'; import { useI18n } from '@n8n/i18n'; +import { useNDVStore } from '@/stores/ndv.store'; const telemetry = useTelemetry(); const i18n = useI18n(); const uiStore = useUIStore(); +const ndvStore = useNDVStore(); const curlCommand = ref(''); const modalBus = createEventBus(); @@ -18,8 +20,13 @@ const modalBus = createEventBus(); const inputRef = ref(null); onMounted(() => { - curlCommand.value = (uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommand as string) ?? ''; - + const curlCommands = uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands as Record< + string, + string + >; + const nodeId = ndvStore.activeNode?.id ?? ''; + const command = curlCommands?.[nodeId]; + curlCommand.value = command ?? ''; setTimeout(() => { inputRef.value?.focus(); }); @@ -43,9 +50,13 @@ function onImportFailure(data: { invalidProtocol: boolean; protocol?: string }) } function onAfterImport() { + const nodeId = ndvStore.activeNode?.id as string; + const curlCommands = + (uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands as Record) ?? {}; + curlCommands[nodeId] = curlCommand.value; uiStore.setModalData({ name: IMPORT_CURL_MODAL_KEY, - data: { curlCommand: curlCommand.value }, + data: { curlCommands }, }); } @@ -90,6 +101,7 @@ async function onImport() { :model-value="curlCommand" type="textarea" :rows="5" + data-test-id="import-curl-modal-input" :placeholder="i18n.baseText('importCurlModal.input.placeholder')" @update:model-value="onInput" @focus="$event.target.select()" @@ -107,6 +119,7 @@ async function onImport() { diff --git a/packages/frontend/editor-ui/src/stores/ui.store.ts b/packages/frontend/editor-ui/src/stores/ui.store.ts index 374aff83a9..ed13f82efc 100644 --- a/packages/frontend/editor-ui/src/stores/ui.store.ts +++ b/packages/frontend/editor-ui/src/stores/ui.store.ts @@ -140,7 +140,7 @@ export const useUIStore = defineStore(STORES.UI, () => { [IMPORT_CURL_MODAL_KEY]: { open: false, data: { - curlCommand: '', + curlCommands: {}, }, }, [LOG_STREAM_MODAL_KEY]: {