Skip to content

Commit 6992c36

Browse files
authored
fix(editor): Add "time saved per execution" workflow setting (#13369)
1 parent 198f17d commit 6992c36

File tree

3 files changed

+233
-117
lines changed

3 files changed

+233
-117
lines changed

packages/frontend/editor-ui/src/components/WorkflowSettings.test.ts

Lines changed: 126 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,61 @@
1-
import { createPinia, setActivePinia } from 'pinia';
2-
import WorkflowSettingsVue from '@/components/WorkflowSettings.vue';
3-
4-
import { setupServer } from '@/__tests__/server';
1+
import { nextTick, reactive } from 'vue';
2+
import { createTestingPinia } from '@pinia/testing';
53
import type { MockInstance } from 'vitest';
6-
import { afterAll, beforeAll } from 'vitest';
7-
import { within } from '@testing-library/vue';
4+
import { within, waitFor } from '@testing-library/vue';
85
import userEvent from '@testing-library/user-event';
9-
6+
import type { FrontendSettings } from '@n8n/api-types';
7+
import { createComponentRenderer } from '@/__tests__/render';
8+
import { getDropdownItems, mockedStore, type MockedStore } from '@/__tests__/utils';
9+
import { EnterpriseEditionFeature } from '@/constants';
10+
import WorkflowSettingsVue from '@/components/WorkflowSettings.vue';
1011
import { useWorkflowsStore } from '@/stores/workflows.store';
1112
import { useSettingsStore } from '@/stores/settings.store';
12-
import { useUIStore } from '@/stores/ui.store';
13-
14-
import { createComponentRenderer } from '@/__tests__/render';
15-
import { cleanupAppModals, createAppModals, getDropdownItems } from '@/__tests__/utils';
16-
import { EnterpriseEditionFeature, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
17-
18-
import { nextTick } from 'vue';
19-
import type { IWorkflowDb } from '@/Interface';
20-
import * as permissions from '@/permissions';
21-
import type { PermissionsRecord } from '@/permissions';
13+
import { useSourceControlStore } from '@/stores/sourceControl.store';
14+
15+
vi.mock('vue-router', async () => ({
16+
useRouter: vi.fn(),
17+
useRoute: () =>
18+
reactive({
19+
params: {
20+
name: '1',
21+
},
22+
}),
23+
RouterLink: {
24+
template: '<a><slot /></a>',
25+
},
26+
}));
2227

23-
let pinia: ReturnType<typeof createPinia>;
24-
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
25-
let settingsStore: ReturnType<typeof useSettingsStore>;
26-
let uiStore: ReturnType<typeof useUIStore>;
28+
let workflowsStore: MockedStore<typeof useWorkflowsStore>;
29+
let settingsStore: MockedStore<typeof useSettingsStore>;
30+
let sourceControlStore: MockedStore<typeof useSourceControlStore>;
31+
let pinia: ReturnType<typeof createTestingPinia>;
2732

2833
let fetchAllWorkflowsSpy: MockInstance<(typeof workflowsStore)['fetchAllWorkflows']>;
2934

30-
const createComponent = createComponentRenderer(WorkflowSettingsVue);
35+
const createComponent = createComponentRenderer(WorkflowSettingsVue, {
36+
global: {
37+
stubs: {
38+
Modal: {
39+
template:
40+
'<div role="dialog"><slot name="header" /><slot name="content" /><slot name="footer" /></div>',
41+
},
42+
},
43+
},
44+
});
3145

3246
describe('WorkflowSettingsVue', () => {
33-
let server: ReturnType<typeof setupServer>;
34-
beforeAll(() => {
35-
server = setupServer();
36-
});
37-
3847
beforeEach(async () => {
39-
pinia = createPinia();
40-
setActivePinia(pinia);
41-
42-
createAppModals();
43-
44-
workflowsStore = useWorkflowsStore();
45-
settingsStore = useSettingsStore();
46-
uiStore = useUIStore();
47-
48-
await settingsStore.getSettings();
49-
50-
vi.spyOn(workflowsStore, 'workflowName', 'get').mockReturnValue('Test Workflow');
51-
vi.spyOn(workflowsStore, 'workflowId', 'get').mockReturnValue('1');
52-
fetchAllWorkflowsSpy = vi.spyOn(workflowsStore, 'fetchAllWorkflows').mockResolvedValue([
48+
pinia = createTestingPinia();
49+
workflowsStore = mockedStore(useWorkflowsStore);
50+
settingsStore = mockedStore(useSettingsStore);
51+
sourceControlStore = mockedStore(useSourceControlStore);
52+
53+
settingsStore.settings = {
54+
enterprise: {},
55+
} as FrontendSettings;
56+
workflowsStore.workflowName = 'Test Workflow';
57+
workflowsStore.workflowId = '1';
58+
fetchAllWorkflowsSpy = workflowsStore.fetchAllWorkflows.mockResolvedValue([
5359
{
5460
id: '1',
5561
name: 'Test Workflow',
@@ -61,7 +67,7 @@ describe('WorkflowSettingsVue', () => {
6167
versionId: '123',
6268
},
6369
]);
64-
vi.spyOn(workflowsStore, 'getWorkflowById').mockReturnValue({
70+
workflowsStore.getWorkflowById.mockImplementation(() => ({
6571
id: '1',
6672
name: 'Test Workflow',
6773
active: true,
@@ -70,24 +76,12 @@ describe('WorkflowSettingsVue', () => {
7076
createdAt: 1,
7177
updatedAt: 1,
7278
versionId: '123',
73-
} as IWorkflowDb);
74-
vi.spyOn(permissions, 'getResourcePermissions').mockReturnValue({
75-
workflow: {
76-
update: true,
77-
},
78-
} as PermissionsRecord);
79-
80-
uiStore.modalsById[WORKFLOW_SETTINGS_MODAL_KEY] = {
81-
open: true,
82-
};
79+
scopes: ['workflow:update'],
80+
}));
8381
});
8482

8583
afterEach(() => {
86-
cleanupAppModals();
87-
});
88-
89-
afterAll(() => {
90-
server.shutdown();
84+
vi.clearAllMocks();
9185
});
9286

9387
it('should render correctly', async () => {
@@ -220,4 +214,79 @@ describe('WorkflowSettingsVue', () => {
220214
expect(dropdownItems[0]).toHaveTextContent(optionText);
221215
},
222216
);
217+
218+
it('should save time saved per execution correctly', async () => {
219+
const { getByTestId, getByRole } = createComponent({ pinia });
220+
await nextTick();
221+
222+
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
223+
224+
expect(timeSavedPerExecutionInput).toBeVisible();
225+
226+
await userEvent.type(timeSavedPerExecutionInput as Element, '10');
227+
expect(timeSavedPerExecutionInput).toHaveValue(10);
228+
229+
await userEvent.click(getByRole('button', { name: 'Save' }));
230+
expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith(
231+
expect.any(String),
232+
expect.objectContaining({ settings: expect.objectContaining({ timeSavedPerExecution: 10 }) }),
233+
);
234+
});
235+
236+
it('should remove time saved per execution setting', async () => {
237+
workflowsStore.workflowSettings.timeSavedPerExecution = 10;
238+
239+
const { getByTestId, getByRole } = createComponent({ pinia });
240+
await nextTick();
241+
242+
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
243+
244+
expect(timeSavedPerExecutionInput).toBeVisible();
245+
await waitFor(() => expect(timeSavedPerExecutionInput).toHaveValue(10));
246+
247+
await userEvent.clear(timeSavedPerExecutionInput as Element);
248+
expect(timeSavedPerExecutionInput).not.toHaveValue();
249+
250+
await userEvent.click(getByRole('button', { name: 'Save' }));
251+
expect(workflowsStore.updateWorkflow).toHaveBeenCalledWith(
252+
expect.any(String),
253+
expect.objectContaining({
254+
settings: expect.not.objectContaining({ timeSavedPerExecution: 10 }),
255+
}),
256+
);
257+
});
258+
259+
it('should disable save time saved per execution if env is read-only', async () => {
260+
sourceControlStore.preferences.branchReadOnly = true;
261+
262+
const { getByTestId } = createComponent({ pinia });
263+
await nextTick();
264+
265+
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
266+
267+
expect(timeSavedPerExecutionInput).toBeVisible();
268+
expect(timeSavedPerExecutionInput).toBeDisabled();
269+
});
270+
271+
it('should disable save time saved per execution if user has no permission to update workflow', async () => {
272+
workflowsStore.getWorkflowById.mockImplementation(() => ({
273+
id: '1',
274+
name: 'Test Workflow',
275+
active: true,
276+
nodes: [],
277+
connections: {},
278+
createdAt: 1,
279+
updatedAt: 1,
280+
versionId: '123',
281+
scopes: ['workflow:read'],
282+
}));
283+
284+
const { getByTestId } = createComponent({ pinia });
285+
await nextTick();
286+
287+
const timeSavedPerExecutionInput = getByTestId('workflow-settings-time-saved-per-execution');
288+
289+
expect(timeSavedPerExecutionInput).toBeVisible();
290+
expect(timeSavedPerExecutionInput).toBeDisabled();
291+
});
223292
});

0 commit comments

Comments
 (0)