From 80ec628d361b586eff4494047c9880a99f63a450 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Mon, 4 Sep 2023 13:25:47 +0200 Subject: [PATCH 1/4] fix: Account for nanoid workflow ids for subworkflow execute policy --- packages/editor-ui/src/components/WorkflowSettings.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 8461107ac32f3..5597ecb662c9f 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -566,9 +566,9 @@ export default defineComponent({ }, methods: { onCallerIdsInput(str: string) { - this.workflowSettings.callerIds = /^[0-9,\s]+$/.test(str) + this.workflowSettings.callerIds = /^[a-zA-Z0-9,\s]+$/.test(str) ? str - : str.replace(/[^0-9,\s]/g, ''); + : str.replace(/[^a-zA-Z0-9,\s]/g, ''); }, closeDialog() { this.modalBus.emit('close'); From efea40a2114da050ad2edb7813fa3e90fca9e3aa Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Tue, 5 Sep 2023 10:58:44 +0200 Subject: [PATCH 2/4] wip: add tests to workflow settings --- .../src/components/WorkflowSettings.vue | 12 +- .../__tests__/WorkflowSettings.spec.ts | 130 ++++++++++++++++++ 2 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 5597ecb662c9f..0a58e87524ab6 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -374,13 +374,11 @@ import { import type { WorkflowSettings } from 'n8n-workflow'; import { deepCopy } from 'n8n-workflow'; -import { - useWorkflowsStore, - useSettingsStore, - useRootStore, - useWorkflowsEEStore, - useUsersStore, -} from '@/stores'; +import { useSettingsStore } from '@/stores/settings.store'; +import { useUsersStore } from '@/stores/users.store'; +import { useRootStore } from '@/stores/n8nRoot.store'; +import { useWorkflowsEEStore } from '@/stores/workflows.ee.store'; +import { useWorkflowsStore } from '@/stores/workflows.store'; import { createEventBus } from 'n8n-design-system/utils'; export default defineComponent({ diff --git a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts new file mode 100644 index 0000000000000..863ee9147635d --- /dev/null +++ b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts @@ -0,0 +1,130 @@ +import WorkflowSettingsVue from '../WorkflowSettings.vue'; +import type { EnvironmentVariable, IWorkflowDataUpdate } from '@/Interface'; +import { fireEvent } from '@testing-library/vue'; +import { setupServer } from '@/__tests__/server'; +import { afterAll, beforeAll } from 'vitest'; + +import { useSettingsStore } from '@/stores/settings.store'; +import { useUsersStore } from '@/stores/users.store'; +import { useRootStore } from '@/stores/n8nRoot.store'; +import { useWorkflowsEEStore } from '@/stores/workflows.ee.store'; +import { useWorkflowsStore } from '@/stores/workflows.store'; + +import { createComponentRenderer } from '@/__tests__/render'; +import { createTestingPinia } from '@pinia/testing'; +import { STORES, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; + +let rootStore: ReturnType; +let workflowsEEStore: ReturnType; +let workflowsStore: ReturnType; + +const renderComponent = createComponentRenderer(WorkflowSettingsVue, { + pinia: createTestingPinia({ + initialState: { + [STORES.UI]: { + modals: { + [WORKFLOW_SETTINGS_MODAL_KEY]: { + open: true, + }, + }, + }, + [STORES.SETTINGS]: { + settings: { + enterprise: { + sharing: true, + }, + }, + }, + }, + }), + global: { + stubs: ['n8n-tooltip'], + }, +}); + +const workflowDataUpdate: IWorkflowDataUpdate = { + id: '1', + name: 'Test Workflow', + nodes: [], + connections: {}, + tags: [], + active: true, +}; + +describe('WorkflowSettingsVue', () => { + let server: ReturnType; + + beforeAll(() => { + server = setupServer(); + }); + + beforeEach(async () => { + await useSettingsStore().getSettings(); + await useUsersStore().loginWithCookie(); + rootStore = useRootStore(); + workflowsEEStore = useWorkflowsEEStore(); + workflowsStore = useWorkflowsStore(); + vi.spyOn(workflowsStore, 'workflowName', 'get').mockResolvedValue(workflowDataUpdate.name!); + vi.spyOn(workflowsStore, 'workflowId', 'get').mockResolvedValue(workflowDataUpdate.id!); + }); + + afterAll(() => { + server.shutdown(); + }); + + it.only('should render correctly', () => { + const wrapper = renderComponent({ + props: { + data: workflowDataUpdate, + }, + }); + expect(wrapper.getByTestId('workflow-settings-dialog')).toBeVisible(); + }); + + it('should show edit and delete buttons on hover', async () => { + const wrapper = renderComponent({ + props: { + data: workflowDataUpdate, + }, + }); + + await fireEvent.mouseEnter(wrapper.container); + + expect(wrapper.getByTestId('variable-row-edit-button')).toBeVisible(); + expect(wrapper.getByTestId('variable-row-delete-button')).toBeVisible(); + }); + + it('should show key and value inputs in edit mode', async () => { + const wrapper = renderComponent({ + props: { + data: environmentVariable, + editing: true, + }, + }); + + await fireEvent.mouseEnter(wrapper.container); + + expect(wrapper.getByTestId('variable-row-key-input')).toBeVisible(); + expect(wrapper.getByTestId('variable-row-key-input').querySelector('input')).toHaveValue( + environmentVariable.key, + ); + expect(wrapper.getByTestId('variable-row-value-input')).toBeVisible(); + expect(wrapper.getByTestId('variable-row-value-input').querySelector('input')).toHaveValue( + environmentVariable.value, + ); + }); + + it('should show cancel and save buttons in edit mode', async () => { + const wrapper = renderComponent({ + props: { + data: environmentVariable, + editing: true, + }, + }); + + await fireEvent.mouseEnter(wrapper.container); + + expect(wrapper.getByTestId('variable-row-cancel-button')).toBeVisible(); + expect(wrapper.getByTestId('variable-row-save-button')).toBeVisible(); + }); +}); From ff9b70f511b80b6c940703e730593b7bb5272559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 5 Sep 2023 17:16:41 +0200 Subject: [PATCH 3/4] Add `vue.nextTick()` --- packages/editor-ui/package.json | 2 +- .../src/components/__tests__/WorkflowSettings.spec.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index d6af0fd06d9ea..fb3c201acd43d 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -22,7 +22,7 @@ "lintfix": "eslint src --ext .js,.ts,.vue --fix", "format": "prettier --write . --ignore-path ../../.prettierignore", "serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vite --host 0.0.0.0 --port 8080 dev", - "test": "vitest run --coverage", + "test": "vitest WorkflowSettings", "test:dev": "vitest" }, "dependencies": { diff --git a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts index 863ee9147635d..d5bb4612fcdb0 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts @@ -14,6 +14,8 @@ import { createComponentRenderer } from '@/__tests__/render'; import { createTestingPinia } from '@pinia/testing'; import { STORES, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; +import { nextTick } from 'vue'; + let rootStore: ReturnType; let workflowsEEStore: ReturnType; let workflowsStore: ReturnType; @@ -72,12 +74,13 @@ describe('WorkflowSettingsVue', () => { server.shutdown(); }); - it.only('should render correctly', () => { + it.only('should render correctly', async () => { const wrapper = renderComponent({ props: { data: workflowDataUpdate, }, }); + await nextTick(); expect(wrapper.getByTestId('workflow-settings-dialog')).toBeVisible(); }); From 264db1cd598b2c331afb174c29ca26c23a8e23d1 Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Wed, 6 Sep 2023 13:38:34 +0200 Subject: [PATCH 4/4] test: Add tests for workflow settings modal --- packages/editor-ui/package.json | 2 +- .../src/components/WorkflowSettings.vue | 3 +- .../__tests__/WorkflowSettings.spec.ts | 187 ++++++++++-------- .../src/composables/useExternalHooks.ts | 2 +- 4 files changed, 107 insertions(+), 87 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index fb3c201acd43d..d6af0fd06d9ea 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -22,7 +22,7 @@ "lintfix": "eslint src --ext .js,.ts,.vue --fix", "format": "prettier --write . --ignore-path ../../.prettierignore", "serve": "cross-env VUE_APP_URL_BASE_API=http://localhost:5678/ vite --host 0.0.0.0 --port 8080 dev", - "test": "vitest WorkflowSettings", + "test": "vitest run --coverage", "test:dev": "vitest" }, "dependencies": { diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 0a58e87524ab6..4a1ee8fe46735 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -67,7 +67,7 @@ -
+
{{ $locale.baseText('workflowSettings.callerPolicy') + ':' }} @@ -114,6 +114,7 @@ type="text" v-model="workflowSettings.callerIds" @update:modelValue="onCallerIdsInput" + data-test-id="workflow-caller-policy-workflow-ids" /> diff --git a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts index d5bb4612fcdb0..ca12ecbd7ddd0 100644 --- a/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts +++ b/packages/editor-ui/src/components/__tests__/WorkflowSettings.spec.ts @@ -1,133 +1,152 @@ +import { createPinia, setActivePinia } from 'pinia'; import WorkflowSettingsVue from '../WorkflowSettings.vue'; -import type { EnvironmentVariable, IWorkflowDataUpdate } from '@/Interface'; -import { fireEvent } from '@testing-library/vue'; + import { setupServer } from '@/__tests__/server'; import { afterAll, beforeAll } from 'vitest'; +import { within, fireEvent } from '@testing-library/vue'; -import { useSettingsStore } from '@/stores/settings.store'; -import { useUsersStore } from '@/stores/users.store'; -import { useRootStore } from '@/stores/n8nRoot.store'; -import { useWorkflowsEEStore } from '@/stores/workflows.ee.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; +import { useSettingsStore } from '@/stores/settings.store'; +import { useUIStore } from '@/stores/ui.store'; import { createComponentRenderer } from '@/__tests__/render'; -import { createTestingPinia } from '@pinia/testing'; -import { STORES, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; +import { EnterpriseEditionFeature, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; import { nextTick } from 'vue'; -let rootStore: ReturnType; -let workflowsEEStore: ReturnType; +let pinia: ReturnType; let workflowsStore: ReturnType; +let settingsStore: ReturnType; +let uiStore: ReturnType; -const renderComponent = createComponentRenderer(WorkflowSettingsVue, { - pinia: createTestingPinia({ - initialState: { - [STORES.UI]: { - modals: { - [WORKFLOW_SETTINGS_MODAL_KEY]: { - open: true, - }, - }, - }, - [STORES.SETTINGS]: { - settings: { - enterprise: { - sharing: true, - }, - }, - }, - }, - }), +const createComponent = createComponentRenderer(WorkflowSettingsVue, { global: { stubs: ['n8n-tooltip'], }, }); -const workflowDataUpdate: IWorkflowDataUpdate = { - id: '1', - name: 'Test Workflow', - nodes: [], - connections: {}, - tags: [], - active: true, -}; - describe('WorkflowSettingsVue', () => { let server: ReturnType; - beforeAll(() => { server = setupServer(); }); beforeEach(async () => { - await useSettingsStore().getSettings(); - await useUsersStore().loginWithCookie(); - rootStore = useRootStore(); - workflowsEEStore = useWorkflowsEEStore(); + pinia = createPinia(); + setActivePinia(pinia); + workflowsStore = useWorkflowsStore(); - vi.spyOn(workflowsStore, 'workflowName', 'get').mockResolvedValue(workflowDataUpdate.name!); - vi.spyOn(workflowsStore, 'workflowId', 'get').mockResolvedValue(workflowDataUpdate.id!); + settingsStore = useSettingsStore(); + uiStore = useUIStore(); + + await settingsStore.getSettings(); + + vi.spyOn(workflowsStore, 'workflowName', 'get').mockReturnValue('Test Workflow'); + vi.spyOn(workflowsStore, 'workflowId', 'get').mockReturnValue('1'); + vi.spyOn(workflowsStore, 'workflow', 'get').mockReturnValue({ + id: '1', + name: 'Test Workflow', + active: true, + nodes: [], + connections: {}, + createdAt: 1, + updatedAt: 1, + versionId: '123', + } as IWorkflowDb); + + uiStore.modals[WORKFLOW_SETTINGS_MODAL_KEY] = { + open: true, + }; }); afterAll(() => { server.shutdown(); }); - it.only('should render correctly', async () => { - const wrapper = renderComponent({ - props: { - data: workflowDataUpdate, - }, - }); + it('should render correctly', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = false; + const wrapper = createComponent({ pinia }); await nextTick(); expect(wrapper.getByTestId('workflow-settings-dialog')).toBeVisible(); }); - it('should show edit and delete buttons on hover', async () => { - const wrapper = renderComponent({ - props: { - data: workflowDataUpdate, - }, - }); + it('should not render workflow caller policy when sharing is not enabled', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = false; + const wrapper = createComponent({ pinia }); + + await nextTick(); + + expect( + within(wrapper.getByTestId('workflow-settings-dialog')).queryByTestId( + 'workflow-caller-policy', + ), + ).not.toBeInTheDocument(); + }); + + it('should render workflow caller policy when sharing is enabled', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = true; + const wrapper = createComponent({ pinia }); + + await nextTick(); + + expect(wrapper.getByTestId('workflow-caller-policy')).toBeVisible(); + }); + + it('should render list of workflows field when policy is set to workflowsFromAList', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = true; + const wrapper = createComponent({ pinia }); + + await nextTick(); - await fireEvent.mouseEnter(wrapper.container); + await fireEvent.click(wrapper.getByTestId('workflow-caller-policy')); + console.log(window.document.querySelectorAll('.el-select-dropdown__item')[4].innerHTML); + await fireEvent.click(window.document.querySelectorAll('.el-select-dropdown__item')[4]); - expect(wrapper.getByTestId('variable-row-edit-button')).toBeVisible(); - expect(wrapper.getByTestId('variable-row-delete-button')).toBeVisible(); + expect(wrapper.getByTestId('workflow-caller-policy-workflow-ids')).toBeVisible(); }); - it('should show key and value inputs in edit mode', async () => { - const wrapper = renderComponent({ - props: { - data: environmentVariable, - editing: true, - }, - }); + it('should not remove valid workflow ID characters', async () => { + const validWorkflowList = '1234567890, abcde, efgh, 1234'; + + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = true; + const wrapper = createComponent({ pinia }); + + await nextTick(); - await fireEvent.mouseEnter(wrapper.container); + await fireEvent.click(wrapper.getByTestId('workflow-caller-policy')); + console.log(window.document.querySelectorAll('.el-select-dropdown__item')[4].innerHTML); + await fireEvent.click(window.document.querySelectorAll('.el-select-dropdown__item')[4]); - expect(wrapper.getByTestId('variable-row-key-input')).toBeVisible(); - expect(wrapper.getByTestId('variable-row-key-input').querySelector('input')).toHaveValue( - environmentVariable.key, + await fireEvent.update( + wrapper.getByTestId('workflow-caller-policy-workflow-ids'), + validWorkflowList, ); - expect(wrapper.getByTestId('variable-row-value-input')).toBeVisible(); - expect(wrapper.getByTestId('variable-row-value-input').querySelector('input')).toHaveValue( - environmentVariable.value, + + expect(wrapper.getByTestId('workflow-caller-policy-workflow-ids')).toHaveValue( + validWorkflowList, ); }); - it('should show cancel and save buttons in edit mode', async () => { - const wrapper = renderComponent({ - props: { - data: environmentVariable, - editing: true, - }, - }); + it('should remove invalid workflow ID characters', async () => { + const invalidWorkflowList = '1234567890@, abc/de, ef*gh, 12%34'; + const cleanedUpWorkflowList = '1234567890, abcde, efgh, 1234'; + + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = true; + const wrapper = createComponent({ pinia }); + + await nextTick(); - await fireEvent.mouseEnter(wrapper.container); + await fireEvent.click(wrapper.getByTestId('workflow-caller-policy')); + console.log(window.document.querySelectorAll('.el-select-dropdown__item')[4].innerHTML); + await fireEvent.click(window.document.querySelectorAll('.el-select-dropdown__item')[4]); - expect(wrapper.getByTestId('variable-row-cancel-button')).toBeVisible(); - expect(wrapper.getByTestId('variable-row-save-button')).toBeVisible(); + await fireEvent.update( + wrapper.getByTestId('workflow-caller-policy-workflow-ids'), + invalidWorkflowList, + ); + + expect(wrapper.getByTestId('workflow-caller-policy-workflow-ids')).toHaveValue( + cleanedUpWorkflowList, + ); }); }); diff --git a/packages/editor-ui/src/composables/useExternalHooks.ts b/packages/editor-ui/src/composables/useExternalHooks.ts index e492de00c6950..bc37d97456f62 100644 --- a/packages/editor-ui/src/composables/useExternalHooks.ts +++ b/packages/editor-ui/src/composables/useExternalHooks.ts @@ -1,6 +1,6 @@ import type { IExternalHooks } from '@/Interface'; import type { IDataObject } from 'n8n-workflow'; -import { useWebhooksStore } from '@/stores'; +import { useWebhooksStore } from '@/stores/webhooks.store'; import { runExternalHook } from '@/utils'; export function useExternalHooks(): IExternalHooks {