Skip to content

Commit e8e7b23

Browse files
fix(editor): Store last entered cURL command for each HTTP node (#17834)
1 parent 4395b8f commit e8e7b23

File tree

3 files changed

+174
-4
lines changed

3 files changed

+174
-4
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { createComponentRenderer } from '@/__tests__/render';
2+
import ImportCurlModal from './ImportCurlModal.vue';
3+
import { createTestingPinia } from '@pinia/testing';
4+
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
5+
import { cleanupAppModals, createAppModals, mockedStore } from '@/__tests__/utils';
6+
import { nextTick } from 'vue';
7+
import { useUIStore } from '@/stores/ui.store';
8+
import { useNDVStore } from '@/stores/ndv.store';
9+
import userEvent from '@testing-library/user-event';
10+
11+
const mockTelemetryTrack = vi.fn();
12+
vi.mock('@/composables/useTelemetry', () => ({
13+
useTelemetry: () => ({
14+
track: mockTelemetryTrack,
15+
}),
16+
}));
17+
18+
vi.mock('@/composables/useImportCurlCommand', () => ({
19+
useImportCurlCommand: (options: {
20+
onImportSuccess: () => void;
21+
onAfterImport: () => void;
22+
}) => ({
23+
importCurlCommand: () => {
24+
options.onImportSuccess();
25+
options.onAfterImport();
26+
},
27+
}),
28+
}));
29+
30+
const renderModal = createComponentRenderer(ImportCurlModal, {
31+
pinia: createTestingPinia(),
32+
});
33+
34+
const testNode = {
35+
id: 'node-1',
36+
name: 'HTTP Request',
37+
type: 'n8n-nodes-base.httpRequest',
38+
position: [0, 0] as [number, number],
39+
typeVersion: 1,
40+
parameters: {},
41+
};
42+
43+
describe('ImportCurlModal', () => {
44+
beforeEach(() => {
45+
vi.clearAllMocks();
46+
createAppModals();
47+
});
48+
49+
afterEach(() => {
50+
cleanupAppModals();
51+
});
52+
53+
it('should show empty input when no curl command exists for active node', async () => {
54+
const uiStore = mockedStore(useUIStore);
55+
uiStore.modalsById = {
56+
[IMPORT_CURL_MODAL_KEY]: {
57+
open: true,
58+
data: {
59+
curlCommands: {
60+
'node-2': 'curl -X GET https://api.example.com/data',
61+
},
62+
},
63+
},
64+
};
65+
uiStore.modalStack = [IMPORT_CURL_MODAL_KEY];
66+
const ndvStore = mockedStore(useNDVStore);
67+
ndvStore.activeNode = testNode;
68+
69+
const { getByTestId } = renderModal();
70+
await nextTick();
71+
72+
const input = getByTestId('import-curl-modal-input');
73+
expect(input).toHaveValue('');
74+
});
75+
76+
it('should show curl command for active node', async () => {
77+
const uiStore = mockedStore(useUIStore);
78+
uiStore.modalsById = {
79+
[IMPORT_CURL_MODAL_KEY]: {
80+
open: true,
81+
data: {
82+
curlCommands: {
83+
'node-1': 'curl -X GET https://api.example.com/data',
84+
'node-2': 'curl -X POST https://api.example.com/submit',
85+
},
86+
},
87+
},
88+
};
89+
uiStore.modalStack = [IMPORT_CURL_MODAL_KEY];
90+
const ndvStore = mockedStore(useNDVStore);
91+
ndvStore.activeNode = testNode;
92+
93+
const { getByTestId } = renderModal();
94+
await nextTick();
95+
96+
const input = getByTestId('import-curl-modal-input');
97+
expect(input).toHaveValue('curl -X GET https://api.example.com/data');
98+
});
99+
100+
it('should set the input value when the import button is clicked', async () => {
101+
const uiStore = mockedStore(useUIStore);
102+
uiStore.modalsById = {
103+
[IMPORT_CURL_MODAL_KEY]: {
104+
open: true,
105+
data: {
106+
curlCommands: {
107+
'node-2': 'curl -X POST https://api.example.com/submit',
108+
},
109+
},
110+
},
111+
};
112+
uiStore.modalStack = [IMPORT_CURL_MODAL_KEY];
113+
const ndvStore = mockedStore(useNDVStore);
114+
ndvStore.activeNode = testNode;
115+
116+
const { getByTestId } = renderModal();
117+
await nextTick();
118+
119+
const input = getByTestId('import-curl-modal-input');
120+
await userEvent.type(input, 'curl -X GET https://api.example.com/data');
121+
const button = getByTestId('import-curl-modal-button');
122+
await userEvent.click(button);
123+
expect(uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands).toEqual({
124+
'node-1': 'curl -X GET https://api.example.com/data',
125+
'node-2': 'curl -X POST https://api.example.com/submit',
126+
});
127+
});
128+
129+
it('should override the input value when the import button is clicked', async () => {
130+
const uiStore = mockedStore(useUIStore);
131+
uiStore.modalsById = {
132+
[IMPORT_CURL_MODAL_KEY]: {
133+
open: true,
134+
data: {
135+
curlCommands: {
136+
'node-1': 'curl -X GET https://api.example.com/data',
137+
},
138+
},
139+
},
140+
};
141+
uiStore.modalStack = [IMPORT_CURL_MODAL_KEY];
142+
const ndvStore = mockedStore(useNDVStore);
143+
ndvStore.activeNode = testNode;
144+
145+
const { getByTestId } = renderModal();
146+
await nextTick();
147+
148+
const input = getByTestId('import-curl-modal-input');
149+
await userEvent.clear(input);
150+
await userEvent.type(input, 'curl -X GET https://api.example.com/other');
151+
const button = getByTestId('import-curl-modal-button');
152+
await userEvent.click(button);
153+
expect(uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands).toEqual({
154+
'node-1': 'curl -X GET https://api.example.com/other',
155+
});
156+
});
157+
});

packages/frontend/editor-ui/src/components/ImportCurlModal.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,27 @@ import { useUIStore } from '@/stores/ui.store';
66
import { createEventBus } from '@n8n/utils/event-bus';
77
import { useTelemetry } from '@/composables/useTelemetry';
88
import { useI18n } from '@n8n/i18n';
9+
import { useNDVStore } from '@/stores/ndv.store';
910
1011
const telemetry = useTelemetry();
1112
const i18n = useI18n();
1213
1314
const uiStore = useUIStore();
15+
const ndvStore = useNDVStore();
1416
1517
const curlCommand = ref('');
1618
const modalBus = createEventBus();
1719
1820
const inputRef = ref<HTMLTextAreaElement | null>(null);
1921
2022
onMounted(() => {
21-
curlCommand.value = (uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommand as string) ?? '';
22-
23+
const curlCommands = uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands as Record<
24+
string,
25+
string
26+
>;
27+
const nodeId = ndvStore.activeNode?.id ?? '';
28+
const command = curlCommands?.[nodeId];
29+
curlCommand.value = command ?? '';
2330
setTimeout(() => {
2431
inputRef.value?.focus();
2532
});
@@ -43,9 +50,13 @@ function onImportFailure(data: { invalidProtocol: boolean; protocol?: string })
4350
}
4451
4552
function onAfterImport() {
53+
const nodeId = ndvStore.activeNode?.id as string;
54+
const curlCommands =
55+
(uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommands as Record<string, string>) ?? {};
56+
curlCommands[nodeId] = curlCommand.value;
4657
uiStore.setModalData({
4758
name: IMPORT_CURL_MODAL_KEY,
48-
data: { curlCommand: curlCommand.value },
59+
data: { curlCommands },
4960
});
5061
}
5162
@@ -90,6 +101,7 @@ async function onImport() {
90101
:model-value="curlCommand"
91102
type="textarea"
92103
:rows="5"
104+
data-test-id="import-curl-modal-input"
93105
:placeholder="i18n.baseText('importCurlModal.input.placeholder')"
94106
@update:model-value="onInput"
95107
@focus="$event.target.select()"
@@ -107,6 +119,7 @@ async function onImport() {
107119
<N8nButton
108120
float="right"
109121
:label="i18n.baseText('importCurlModal.button.label')"
122+
data-test-id="import-curl-modal-button"
110123
@click="onImport"
111124
/>
112125
</div>

packages/frontend/editor-ui/src/stores/ui.store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
140140
[IMPORT_CURL_MODAL_KEY]: {
141141
open: false,
142142
data: {
143-
curlCommand: '',
143+
curlCommands: {},
144144
},
145145
},
146146
[LOG_STREAM_MODAL_KEY]: {

0 commit comments

Comments
 (0)