Skip to content

Commit 7b26232

Browse files
committed
Implemented reordering of array properties
1 parent 99300a0 commit 7b26232

File tree

3 files changed

+151
-17
lines changed

3 files changed

+151
-17
lines changed

src/lib/components/common/jschema/ArrayProperty.svelte

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import PropertyDiscriminator from '$lib/components/common/jschema/PropertyDiscriminator.svelte';
44
import PropertyDescription from '$lib/components/common/jschema/PropertyDescription.svelte';
55
6+
/** @type {import('$lib/components/common/jschema/schema_management').SchemaProperty} */
67
export let schemaProperty;
78
let nestedProperties = [];
89
@@ -63,7 +64,10 @@
6364
nestedProperties = schemaProperty.nestedProperties;
6465
}
6566
66-
function removeNestedProperty(/** @type {number} */ index) {
67+
/**
68+
* @param {number} index
69+
*/
70+
function removeNestedProperty(index) {
6771
schemaProperty.removeNestedSchemaProperty(index);
6872
nestedProperties = schemaProperty.nestedProperties;
6973
const minItems = getMinItems(schemaProperty.referenceSchema);
@@ -72,6 +76,20 @@
7276
nestedProperties = schemaProperty.nestedProperties;
7377
}
7478
}
79+
80+
/**
81+
* @param {number} index
82+
*/
83+
function moveUp(index) {
84+
nestedProperties = schemaProperty.moveNestedPropertyUp(index);
85+
}
86+
87+
/**
88+
* @param {number} index
89+
*/
90+
function moveDown(index) {
91+
nestedProperties = schemaProperty.moveNestedPropertyDown(index);
92+
}
7593
</script>
7694

7795
{#if schemaProperty}
@@ -121,6 +139,26 @@
121139
<div class="flex-fill">
122140
<PropertyDiscriminator schemaProperty={nestedProperty} />
123141
</div>
142+
<div class="align-self-right mt-2 me-2">
143+
{#if nestedProperties.length > 1}
144+
<button
145+
class="btn btn-light"
146+
on:click|preventDefault={() => moveUp(index)}
147+
aria-label="Move item up"
148+
disabled={index === 0}
149+
>
150+
<i class="bi-arrow-up" />
151+
</button>
152+
<button
153+
class="btn btn-light"
154+
on:click|preventDefault={() => moveDown(index)}
155+
aria-label="Move item down"
156+
disabled={index === nestedProperties.length - 1}
157+
>
158+
<i class="bi-arrow-down" />
159+
</button>
160+
{/if}
161+
</div>
124162
</div>
125163
{/each}
126164
</div>

src/lib/components/common/jschema/schema_management.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,52 @@ export class SchemaProperty {
249249
return nestedProperty;
250250
}
251251

252+
/**
253+
* @param {number} index
254+
* @returns {any[]}
255+
*/
256+
moveNestedPropertyUp(index) {
257+
if (index > 0) {
258+
let updatedArray = [];
259+
for (let i = 0; i < this.nestedProperties.length; i++) {
260+
if (i === index - 1) {
261+
updatedArray[i] = this.nestedProperties[i + 1];
262+
} else if (i === index) {
263+
updatedArray[i] = this.nestedProperties[i - 1];
264+
} else {
265+
updatedArray[i] = this.nestedProperties[i];
266+
}
267+
updatedArray[i].key = `${this.key}${this.keySeparator}${i}`;
268+
}
269+
this.nestedProperties = updatedArray;
270+
this.manager.updateValue(this.key, this.getUpdatedNestedProperties());
271+
}
272+
return this.nestedProperties;
273+
}
274+
275+
/**
276+
* @param {number} index
277+
* @returns {any[]}
278+
*/
279+
moveNestedPropertyDown(index) {
280+
if (index < this.nestedProperties.length - 1) {
281+
let updatedArray = [];
282+
for (let i = 0; i < this.nestedProperties.length; i++) {
283+
if (i === index) {
284+
updatedArray[i] = this.nestedProperties[i + 1];
285+
} else if (i === index + 1) {
286+
updatedArray[i] = this.nestedProperties[i - 1];
287+
} else {
288+
updatedArray[i] = this.nestedProperties[i];
289+
}
290+
updatedArray[i].key = `${this.key}${this.keySeparator}${i}`;
291+
}
292+
this.nestedProperties = updatedArray;
293+
this.manager.updateValue(this.key, this.getUpdatedNestedProperties());
294+
}
295+
return this.nestedProperties;
296+
}
297+
252298
addProperty(namedKey, propertyValue) {
253299
if (this.type !== 'object') {
254300
throw new Error('Schema property is not of type object');
@@ -292,15 +338,20 @@ export class SchemaProperty {
292338
}
293339
}
294340

341+
/**
342+
* @param {number} index
343+
*/
295344
removeNestedSchemaProperty(index) {
296345
this.nestedProperties.splice(index, 1);
297346
// Should update the keys of nested properties and update the value of the property
298-
const updatedValues = this.nestedProperties.map((nestedProperty, index) => {
347+
this.manager.updateValue(this.key, this.getUpdatedNestedProperties());
348+
}
349+
350+
getUpdatedNestedProperties() {
351+
return this.nestedProperties.map((nestedProperty, index) => {
299352
nestedProperty.key = `${this.key}${this.keySeparator}${index}`;
300353
return nestedProperty.value;
301354
});
302-
this.manager.updateValue(this.key, updatedValues);
303-
// this.manager.updateValue(this.key, this.value)
304355
}
305356

306357
discriminatePropertyType(schema, globalSchema, currentValue) {

tests/jschema.spec.js

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import path from 'path';
66
const __filename = fileURLToPath(import.meta.url);
77
const __dirname = path.dirname(__filename);
88

9-
test('JSON Schema validation', async ({ page, workflow }) => {
9+
test('JSON Schema validation', async ({ page, browserName, workflow }) => {
1010
await page.waitForURL(workflow.url);
1111
await waitPageLoading(page);
1212

@@ -97,8 +97,11 @@ test('JSON Schema validation', async ({ page, workflow }) => {
9797

9898
await test.step('Fill required integer with min and max', async () => {
9999
const input = form.getByLabel('minMaxRequiredInt', { exact: true });
100-
await input.pressSequentially('foo');
101-
expect(form.getByText('Should be a number')).toHaveCount(1);
100+
if (browserName === 'firefox') {
101+
// chrome doesn't allow to insert text inside numeric inputs
102+
await input.pressSequentially('foo');
103+
expect(form.getByText('Should be a number')).toHaveCount(1);
104+
}
102105
await input.fill('1');
103106
expect(form.getByText('Should be greater or equal than 5')).toHaveCount(1);
104107
await input.fill('15');
@@ -111,8 +114,11 @@ test('JSON Schema validation', async ({ page, workflow }) => {
111114

112115
await test.step('Fill optional integer with min and max', async () => {
113116
const input = form.getByLabel('minMaxOptionalInt', { exact: true });
114-
await input.pressSequentially('foo');
115-
expect(form.getByText('Should be a number')).toHaveCount(1);
117+
if (browserName === 'firefox') {
118+
// chrome doesn't allow to insert text inside numeric inputs
119+
await input.pressSequentially('foo');
120+
expect(form.getByText('Should be a number')).toHaveCount(1);
121+
}
116122
await input.fill('-7');
117123
expect(form.getByText('Should be greater or equal than 0')).toHaveCount(1);
118124
await input.fill('33');
@@ -123,8 +129,11 @@ test('JSON Schema validation', async ({ page, workflow }) => {
123129

124130
await test.step('Fill optional integer with exclusive min and max', async () => {
125131
const input = form.getByLabel('exclusiveMinMaxOptionalInt', { exact: true });
126-
await input.pressSequentially('foo');
127-
expect(form.getByText('Should be a number')).toHaveCount(1);
132+
if (browserName === 'firefox') {
133+
// chrome doesn't allow to insert text inside numeric inputs
134+
await input.pressSequentially('foo');
135+
expect(form.getByText('Should be a number')).toHaveCount(1);
136+
}
128137
await input.fill('2');
129138
expect(form.getByText('Should be greater or equal than 4')).toHaveCount(1);
130139
await input.fill('99');
@@ -138,21 +147,39 @@ test('JSON Schema validation', async ({ page, workflow }) => {
138147
await addBtn.click();
139148
await addBtn.click();
140149
await addBtn.click();
150+
// Fill items
141151
await form.locator('id=property-requiredArrayWithMinMaxItems###0').fill('a');
142152
await form.locator('id=property-requiredArrayWithMinMaxItems###1').fill('b');
143153
await form.locator('id=property-requiredArrayWithMinMaxItems###2').fill('c');
144154
await form.locator('id=property-requiredArrayWithMinMaxItems###3').fill('d');
155+
// Move "d" up
156+
await page.getByRole('button', { name: 'Move item up' }).nth(3).click();
157+
await page.getByRole('button', { name: 'Move item up' }).nth(2).click();
158+
await page.getByRole('button', { name: 'Move item up' }).nth(1).click();
159+
await checkFirstArray(['d', 'a', 'b', 'c']);
160+
// Move "d" down
161+
await page.getByRole('button', { name: 'Move item down' }).first().click();
162+
await page.getByRole('button', { name: 'Move item down' }).nth(1).click();
163+
await checkFirstArray(['a', 'b', 'd', 'c']);
164+
// Remove items
145165
await form.getByRole('button', { name: 'Remove' }).nth(3).click();
146166
await form.getByRole('button', { name: 'Remove' }).nth(2).click();
147-
expect(await form.locator('id=property-requiredArrayWithMinMaxItems###1').inputValue()).toEqual(
148-
'b'
149-
);
167+
await checkFirstArray(['a', 'b']);
150168
await form.getByRole('button', { name: 'Remove' }).nth(1).click();
151-
expect(await form.locator('id=property-requiredArrayWithMinMaxItems###1').inputValue()).toEqual(
152-
''
153-
);
169+
await checkFirstArray(['a', '']);
154170
});
155171

172+
/**
173+
* @param {string[]} expectedValues
174+
*/
175+
async function checkFirstArray(expectedValues) {
176+
for (let i = 0; i < expectedValues.length; i++) {
177+
expect(
178+
await form.locator('id=property-requiredArrayWithMinMaxItems###' + i).inputValue()
179+
).toEqual(expectedValues[i]);
180+
}
181+
}
182+
156183
await test.step('Optional array with minItems and maxItems', async () => {
157184
await form.getByText('optionalArrayWithMinMaxItems').first().click();
158185
const addBtn = form.getByRole('button', { name: 'Add argument to list' }).nth(1);
@@ -179,6 +206,24 @@ test('JSON Schema validation', async ({ page, workflow }) => {
179206
expect(form.getByText('Field is required')).toHaveCount(2);
180207
});
181208

209+
await test.step('Save values', async () => {
210+
const stringInput = form.getByLabel('Required string', { exact: true });
211+
await stringInput.fill('foo');
212+
const intInput = form.getByLabel('minMaxRequiredInt', { exact: true });
213+
await intInput.fill('7');
214+
await form.locator('id=property-requiredArrayWithMinMaxItems###1').fill('b');
215+
await page.getByRole('button', { name: 'Move item up' }).nth(1).click();
216+
await page.getByRole('button', { name: 'Save changes' }).click();
217+
await page.getByText('Arguments changes saved successfully').waitFor();
218+
await page.reload();
219+
await waitPageLoading(page);
220+
await page.getByText(`${randomTaskName} #`).first().click();
221+
expect(await page.getByLabel('Required string', { exact: true }).inputValue()).toEqual('foo');
222+
expect(await page.getByLabel('minMaxRequiredInt', { exact: true }).inputValue()).toEqual('7');
223+
expect(await page.locator('id=property-requiredEnum').inputValue()).toEqual('option1');
224+
await checkFirstArray(['b', 'a']);
225+
});
226+
182227
await test.step('Delete workflow task', async () => {
183228
await page.getByLabel('Delete workflow task').click();
184229
const modal = page.locator('.modal.show');

0 commit comments

Comments
 (0)