Skip to content

Commit d539961

Browse files
authored
Merge pull request #325 from fractal-analytics-platform/version-update
Task version update
2 parents 927d18b + 6881684 commit d539961

File tree

11 files changed

+2172
-295
lines changed

11 files changed

+2172
-295
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
*Note: Numbers like (\#123) point to closed Pull Requests on the fractal-web repository.*
22

3+
# Unreleased
4+
5+
* Implemented task version update (\#325).
6+
37
# 0.6.2
48

59
* Fixed peformance issue with argument-description popovers (\#324).

__tests__/VersionUpdate.test.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { fireEvent, render, screen } from '@testing-library/svelte';
3+
import { readable } from 'svelte/store';
4+
5+
// Mocking the page store
6+
vi.mock('$app/stores', () => {
7+
return {
8+
page: readable({
9+
params: {
10+
projectId: 1
11+
}
12+
})
13+
};
14+
});
15+
16+
// Mocking fetch
17+
global.fetch = vi.fn();
18+
19+
function createFetchResponse(data) {
20+
return {
21+
ok: true,
22+
json: () => new Promise((resolve) => resolve(data))
23+
};
24+
}
25+
26+
// The component to be tested must be imported after the mock setup
27+
import VersionUpdate from '../src/lib/components/workflow/VersionUpdate.svelte';
28+
29+
const task3ArgsSchema = {
30+
title: 'MyTask',
31+
type: 'object',
32+
properties: {
33+
changed_property: {
34+
type: 'boolean'
35+
},
36+
new_property: {
37+
type: 'string'
38+
}
39+
},
40+
required: ['changed_property', 'new_property'],
41+
additionalProperties: false
42+
};
43+
44+
const tasks = [
45+
{ id: 1, name: 'My Task', owner: null, version: '1.2.3', args_schema: {} },
46+
{ id: 2, name: 'My Task', owner: null, version: '1.2.4', args_schema: {} },
47+
{ id: 3, name: 'My Task', owner: null, version: '2.0.0', args_schema: task3ArgsSchema },
48+
{ id: 4, name: 'My Other Task', owner: null, version: '1.2.3', args_schema: {} },
49+
{ id: 5, name: 'My Other Task', owner: 'admin', version: '1.3.0', args_schema: {} }
50+
];
51+
52+
function mockTaskList() {
53+
fetch.mockResolvedValue(createFetchResponse(tasks));
54+
}
55+
56+
function getTask(name, version) {
57+
return tasks.filter((t) => t.name === name && t.version === version)[0];
58+
}
59+
60+
describe('VersionUpdate', () => {
61+
it('update task without changing the arguments', async () => {
62+
const task = getTask('My Task', '1.2.3');
63+
const versions = await checkVersions(task, 2);
64+
expect(versions[0]).toBe('2.0.0');
65+
expect(versions[1]).toBe('1.2.4');
66+
67+
await fireEvent.change(screen.getByRole('combobox'), { target: { value: '1.2.4' } });
68+
69+
const btn = screen.getByRole('button');
70+
expect(btn.textContent).eq('Update');
71+
expect(btn.disabled).eq(false);
72+
await fireEvent.click(btn);
73+
});
74+
75+
it('update task fixing the arguments', async () => {
76+
const task = getTask('My Task', '1.2.4');
77+
const versions = await checkVersions(task, 1, { extra_property: 1, changed_property: 'x' });
78+
expect(versions[0]).toBe('2.0.0');
79+
80+
await fireEvent.change(screen.getByRole('combobox'), { target: { value: '2.0.0' } });
81+
82+
const [moreLink1, moreLink2, moreLink3, checkBtn, cancelBtn, updateBtnDisabled] =
83+
screen.getAllByRole('button');
84+
expect(moreLink1.textContent).eq('more');
85+
expect(moreLink2.textContent).eq('more');
86+
expect(moreLink3.textContent).eq('more');
87+
expect(checkBtn.textContent).eq('Check');
88+
expect(cancelBtn.textContent).eq('Cancel');
89+
expect(updateBtnDisabled.textContent).eq('Update');
90+
expect(updateBtnDisabled.disabled).eq(true);
91+
92+
expect(
93+
screen.getByText('Following errors must be fixed before performing the update:')
94+
).toBeDefined();
95+
96+
const list = screen.getAllByRole('listitem');
97+
expect(list.length).eq(3);
98+
expect(list[0].textContent).contain("must have required property 'new_property'");
99+
expect(list[1].textContent).contain("must NOT have additional property 'extra_property'");
100+
expect(list[2].textContent).contain('/changed_property: must be boolean');
101+
102+
await fireEvent.input(screen.getByRole('textbox'), {
103+
target: { value: '{"changed_property": true, "new_property": "test"}' }
104+
});
105+
await fireEvent.click(checkBtn);
106+
107+
const updateBtnEnabled = screen.getAllByRole('button')[2];
108+
expect(updateBtnEnabled.textContent).eq('Update');
109+
expect(updateBtnEnabled.disabled).eq(false);
110+
});
111+
112+
it('use the cancel button when fixing the arguments', async () => {
113+
const task = getTask('My Task', '1.2.4');
114+
const versions = await checkVersions(task, 1, { changed_property: 'x' });
115+
expect(versions[0]).toBe('2.0.0');
116+
117+
await fireEvent.change(screen.getByRole('combobox'), { target: { value: '2.0.0' } });
118+
119+
expect(screen.getByRole('textbox').value).eq(
120+
JSON.stringify({ changed_property: 'x' }, null, 2)
121+
);
122+
123+
const cancelBtnDisabled = screen.getByRole('button', { name: 'Cancel' });
124+
expect(cancelBtnDisabled.disabled).eq(true);
125+
126+
await fireEvent.input(screen.getByRole('textbox'), {
127+
target: { value: '{"changed_property": "y"}' }
128+
});
129+
130+
const cancelBtnEnabled = screen.getByRole('button', { name: 'Cancel' });
131+
expect(cancelBtnEnabled.disabled).eq(false);
132+
await fireEvent.click(cancelBtnEnabled);
133+
134+
expect(screen.getByRole('textbox').value).eq(
135+
JSON.stringify({ changed_property: 'x' }, null, 2)
136+
);
137+
});
138+
139+
it('trying to fix the arguments with invalid JSON', async () => {
140+
const task = getTask('My Task', '1.2.4');
141+
const versions = await checkVersions(task, 1);
142+
expect(versions[0]).toBe('2.0.0');
143+
144+
await fireEvent.change(screen.getByRole('combobox'), { target: { value: '2.0.0' } });
145+
146+
const checkBtn = screen.getByRole('button', { name: 'Check' });
147+
const updateBtn = screen.getByRole('button', { name: 'Update' });
148+
expect(updateBtn.disabled).eq(true);
149+
150+
await fireEvent.input(screen.getByRole('textbox'), {
151+
target: { value: '}{' }
152+
});
153+
await fireEvent.click(checkBtn);
154+
155+
expect(screen.getByRole('textbox').classList.contains('is-invalid')).eq(true);
156+
expect(screen.getByText('Invalid JSON')).toBeDefined();
157+
});
158+
159+
it('no new versions available for null owner', async () => {
160+
const task = getTask('My Other Task', '1.2.3');
161+
await checkVersions(task, 0);
162+
});
163+
164+
it('no new versions available for admin owner', async () => {
165+
const task = getTask('My Other Task', '1.3.0');
166+
await checkVersions(task, 0);
167+
});
168+
169+
it('display warning if task has no version', () => {
170+
renderVersionUpdate({ id: 1, name: 'task', owner: null, version: null, args_schema: {} });
171+
expect(
172+
screen.getByText(
173+
'It is not possible to check for new versions because task version is not set.'
174+
)
175+
).toBeDefined();
176+
});
177+
178+
it('display warning if task has no args_schema', () => {
179+
renderVersionUpdate({ id: 1, name: 'task', owner: null, version: '1.2.3', args_schema: null });
180+
expect(
181+
screen.getByText(
182+
'It is not possible to check for new versions because task has no args_schema.'
183+
)
184+
).toBeDefined();
185+
});
186+
});
187+
188+
async function checkVersions(task, expectedCount, args = {}) {
189+
const result = renderVersionUpdate(task, args);
190+
191+
// triggers the updates
192+
await new Promise(setTimeout);
193+
194+
if (expectedCount === 0) {
195+
expect(result.getByText('No new versions available')).toBeDefined();
196+
return;
197+
}
198+
199+
const options = result.getAllByRole('option');
200+
expect(options.length).toBe(expectedCount + 1);
201+
expect(options[0].value).toBe('');
202+
return options.filter((_, i) => i > 0).map((o) => o.value);
203+
}
204+
205+
function renderVersionUpdate(task, args) {
206+
const nop = function () {};
207+
mockTaskList();
208+
209+
const workflowTask = { task, args };
210+
211+
return render(VersionUpdate, {
212+
props: { workflowTask, updateWorkflowCallback: nop, updateNewVersionsCount: nop }
213+
});
214+
}

0 commit comments

Comments
 (0)