Skip to content

Commit ca8f7ea

Browse files
committed
Set bold font on nested required properties; improved type checking; added more tests
1 parent 643eb00 commit ca8f7ea

14 files changed

+578
-161
lines changed

__tests__/JSchema.test.js

Lines changed: 253 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,134 @@
11
import { describe, it, expect } from 'vitest';
2-
import { fireEvent, render } from '@testing-library/svelte';
2+
import { fireEvent, render, screen } from '@testing-library/svelte';
33

44
import JSchema from '../src/lib/components/common/jschema/JSchema.svelte';
55

66
describe('JSchema', () => {
7-
it('NumberProperty with title', async () => {
8-
const result = renderSchemaWithSingleProperty({
9-
title: 'Number title',
10-
type: 'integer'
11-
});
7+
it('Required NumberProperty with title', async () => {
8+
const result = renderSchemaWithSingleProperty(
9+
{
10+
title: 'Number title',
11+
type: 'integer'
12+
},
13+
true
14+
);
15+
checkBold(result.getByText('Number title'), true);
1216
const spinbutton = result.getByRole('spinbutton', { name: 'Number title' });
1317
expect(spinbutton).toBeDefined();
1418
});
1519

16-
it('NumberProperty without title', async () => {
20+
it('Optional NumberProperty without title', async () => {
1721
const result = renderSchemaWithSingleProperty({ type: 'integer' });
22+
checkBold(result.getByText('Number argument'), false);
1823
const spinbutton = result.getByRole('spinbutton', { name: 'Number argument' });
1924
expect(spinbutton).toBeDefined();
2025
});
2126

22-
it('StringProperty with title', async () => {
23-
const result = renderSchemaWithSingleProperty({
24-
title: 'String title',
25-
type: 'string'
26-
});
27+
it('NumberProperty referenced', async () => {
28+
const result = renderSchemaWithReferencedProperty({ type: 'number' });
29+
checkBold(result.getByText('Number argument'), false);
30+
const spinbutton = result.getByRole('spinbutton', { name: 'Number argument' });
31+
expect(spinbutton).toBeDefined();
32+
});
33+
34+
it('Required StringProperty with title', async () => {
35+
const result = renderSchemaWithSingleProperty(
36+
{
37+
title: 'String title',
38+
type: 'string'
39+
},
40+
true
41+
);
42+
checkBold(result.getByText('String title'), true);
2743
const textbox = result.getByRole('textbox', { name: 'String title' });
2844
expect(textbox).toBeDefined();
2945
});
3046

31-
it('StringProperty without title', async () => {
47+
it('Optional StringProperty without title', async () => {
3248
const result = renderSchemaWithSingleProperty({ type: 'string' });
49+
checkBold(result.getByText('String argument'), false);
3350
const textbox = result.getByRole('textbox', { name: 'String argument' });
3451
expect(textbox).toBeDefined();
3552
});
3653

37-
it('EnumProperty with title', async () => {
54+
it('StringProperty referenced', async () => {
55+
const result = renderSchemaWithReferencedProperty({ type: 'string' });
56+
checkBold(result.getByText('String argument'), false);
57+
const textbox = result.getByRole('textbox', { name: 'String argument' });
58+
expect(textbox).toBeDefined();
59+
});
60+
61+
it('Required EnumProperty with title', async () => {
62+
const result = renderSchemaWithSingleProperty(
63+
{
64+
title: 'Enum title',
65+
enum: ['option1', 'option2'],
66+
type: 'string'
67+
},
68+
true
69+
);
70+
checkBold(result.getByText('Enum title'), true);
71+
const combobox = result.getByRole('combobox', { name: 'Enum title' });
72+
const options = combobox.querySelectorAll('option');
73+
expect(options[0].text).eq('Select...');
74+
expect(options[1].value).eq('option1');
75+
expect(options[2].value).eq('option2');
76+
});
77+
78+
it('Optional EnumProperty without title', async () => {
3879
const result = renderSchemaWithSingleProperty({
39-
title: 'Enum title',
4080
enum: ['option1', 'option2'],
4181
type: 'string'
4282
});
43-
const combobox = result.getByRole('combobox', { name: 'Enum title' });
83+
checkBold(result.getByText('Enum argument'), false);
84+
const combobox = result.getByRole('combobox', { name: 'Enum argument' });
4485
const options = combobox.querySelectorAll('option');
4586
expect(options[0].text).eq('Select...');
4687
expect(options[1].value).eq('option1');
4788
expect(options[2].value).eq('option2');
4889
});
4990

50-
it('EnumProperty without title', async () => {
51-
const result = renderSchemaWithSingleProperty({
91+
it('EnumProperty referenced', async () => {
92+
const result = renderSchemaWithReferencedProperty({
5293
enum: ['option1', 'option2'],
5394
type: 'string'
5495
});
96+
checkBold(result.getByText('Enum argument'), false);
5597
const combobox = result.getByRole('combobox', { name: 'Enum argument' });
5698
const options = combobox.querySelectorAll('option');
5799
expect(options[0].text).eq('Select...');
58100
expect(options[1].value).eq('option1');
59101
expect(options[2].value).eq('option2');
60102
});
61103

62-
it('BooleanProperty with title', async () => {
63-
const result = renderSchemaWithSingleProperty({
64-
title: 'Boolean title',
65-
type: 'boolean'
66-
});
104+
it('Required BooleanProperty with title', async () => {
105+
const result = renderSchemaWithSingleProperty(
106+
{
107+
title: 'Boolean title',
108+
type: 'boolean'
109+
},
110+
true
111+
);
67112
const title = result.getByText('Boolean title');
113+
checkBold(title, true);
68114
expect(title).toBeDefined();
69115
const checkbox = result.getByRole('checkbox');
70116
expect(checkbox).toBeDefined();
71117
});
72118

73119
it('BooleanProperty without title', async () => {
74-
const result = renderSchemaWithSingleProperty({ type: 'boolean' });
120+
const result = renderSchemaWithSingleProperty({ type: 'boolean' }, false);
75121
const title = result.getByText('Boolean argument');
122+
checkBold(title, false);
123+
expect(title).toBeDefined();
124+
const checkbox = result.getByRole('checkbox');
125+
expect(checkbox).toBeDefined();
126+
});
127+
128+
it('BooleanProperty referenced', async () => {
129+
const result = renderSchemaWithReferencedProperty({ type: 'boolean' });
130+
const title = result.getByText('Boolean argument');
131+
checkBold(title, false);
76132
expect(title).toBeDefined();
77133
const checkbox = result.getByRole('checkbox');
78134
expect(checkbox).toBeDefined();
@@ -102,25 +158,35 @@ describe('JSchema', () => {
102158
expect(spinbutton.getAttribute('max')).toBe('9');
103159
});
104160

105-
it('ArrayProperty with default values', async () => {
106-
const result = renderSchemaWithSingleProperty({
107-
type: 'array',
108-
items: { type: 'string' },
109-
default: ['foo', 'bar']
110-
});
161+
it('Optional ArrayProperty with default values', async () => {
162+
const result = renderSchemaWithSingleProperty(
163+
{
164+
title: 'ArrayProperty',
165+
type: 'array',
166+
items: { type: 'string' },
167+
default: ['foo', 'bar']
168+
},
169+
false
170+
);
171+
checkBold(result.getByText('ArrayProperty'), false);
111172
const inputs = result.getAllByRole('textbox');
112173
expect(inputs.length).eq(2);
113174
expect(inputs[0].value).eq('foo');
114175
expect(inputs[1].value).eq('bar');
115176
});
116177

117-
it('ArrayProperty with minItems and maxItems', async () => {
118-
const result = renderSchemaWithSingleProperty({
119-
type: 'array',
120-
items: { type: 'string' },
121-
minItems: 3,
122-
maxItems: 5
123-
});
178+
it('Required ArrayProperty with minItems and maxItems', async () => {
179+
const result = renderSchemaWithSingleProperty(
180+
{
181+
title: 'ArrayProperty',
182+
type: 'array',
183+
items: { type: 'string' },
184+
minItems: 3,
185+
maxItems: 5
186+
},
187+
true
188+
);
189+
checkBold(result.getByText('ArrayProperty'), true);
124190
let inputs = result.getAllByRole('textbox');
125191
expect(inputs.length).eq(3);
126192
const addBtn = result.getByRole('button', { name: 'Add argument to list' });
@@ -130,21 +196,156 @@ describe('JSchema', () => {
130196
inputs = result.getAllByRole('textbox');
131197
expect(inputs.length).eq(5);
132198
});
199+
200+
it('Optional ArrayProperty with minItems and maxItems', async () => {
201+
const result = renderSchemaWithSingleProperty(
202+
{
203+
title: 'ArrayProperty',
204+
type: 'array',
205+
items: { type: 'string' },
206+
minItems: 2,
207+
maxItems: 3
208+
},
209+
false
210+
);
211+
checkBold(result.getByText('ArrayProperty'), false);
212+
let inputs = result.queryAllByRole('textbox');
213+
expect(inputs.length).eq(0);
214+
const addBtn = result.getByRole('button', { name: 'Add argument to list' });
215+
await fireEvent.click(addBtn);
216+
await fireEvent.click(addBtn);
217+
await fireEvent.click(addBtn);
218+
await fireEvent.click(addBtn);
219+
inputs = result.queryAllByRole('textbox');
220+
expect(inputs.length).eq(3);
221+
});
222+
223+
it('Required nested objects', async () => {
224+
const result = renderSchema({
225+
title: 'Args',
226+
type: 'object',
227+
properties: {
228+
requiredReferencedProperty: {
229+
$ref: '#/definitions/Ref1'
230+
},
231+
optionalArray: {
232+
title: 'array1 (optional)',
233+
type: 'array',
234+
items: {
235+
title: 'array1 item title',
236+
type: 'object',
237+
properties: {
238+
array1RequiredInteger: {
239+
title: 'array1RequiredInteger',
240+
type: 'integer'
241+
},
242+
array1OptionalInteger: {
243+
title: 'array1OptionalInteger',
244+
type: 'integer'
245+
}
246+
},
247+
required: ['array1RequiredInteger']
248+
}
249+
}
250+
},
251+
required: ['requiredReferencedProperty'],
252+
definitions: {
253+
Ref1: {
254+
title: 'Ref1',
255+
type: 'object',
256+
properties: {
257+
ref1RequiredArray: {
258+
title: 'ref1RequiredArray',
259+
type: 'array',
260+
items: {
261+
$ref: '#/definitions/Ref2'
262+
}
263+
},
264+
ref1RequiredString: {
265+
title: 'ref1RequiredString',
266+
type: 'string'
267+
},
268+
ref1OptionalString: {
269+
title: 'ref1OptionalString',
270+
type: 'string'
271+
}
272+
},
273+
required: ['ref1RequiredArray', 'ref1RequiredString']
274+
},
275+
Ref2: {
276+
title: 'Ref2',
277+
type: 'object',
278+
properties: {
279+
ref2RequiredString: {
280+
title: 'ref2RequiredString',
281+
type: 'string'
282+
},
283+
ref2OptionalString: {
284+
title: 'ref2OptionalString',
285+
type: 'string'
286+
}
287+
},
288+
required: ['ref2RequiredString']
289+
}
290+
}
291+
});
292+
293+
// Insert element into first array
294+
let addBtn = result.getAllByRole('button', { name: 'Add argument to list' })[0];
295+
await fireEvent.click(addBtn);
296+
297+
// Insert element into ref1 array
298+
addBtn = result.getAllByRole('button', { name: 'Add argument to list' })[1];
299+
await fireEvent.click(addBtn);
300+
301+
checkBold(result.getByText('Ref1'), true);
302+
checkBold(result.getByText('array1 (optional)'), false);
303+
checkBold(result.getByText('array1RequiredInteger'), true);
304+
checkBold(result.getByText('array1OptionalInteger'), false);
305+
checkBold(result.getByText('ref1RequiredArray'), true);
306+
checkBold(result.getByText('ref1RequiredString'), true);
307+
checkBold(result.getByText('ref1OptionalString'), false);
308+
checkBold(result.getByText('ref2RequiredString'), true);
309+
checkBold(result.getByText('ref2OptionalString'), false);
310+
});
133311
});
134312

135313
/**
136314
* @param {object} schemaProperty
315+
* @param {boolean} required
137316
*/
138-
function renderSchemaWithSingleProperty(schemaProperty) {
139-
return renderSchema({
317+
function renderSchemaWithSingleProperty(schemaProperty, required = false) {
318+
const schema = {
140319
title: 'Args',
141320
type: 'object',
142321
properties: {
143322
testProp: schemaProperty
323+
}
324+
};
325+
if (required) {
326+
schema.required = ['testProp'];
327+
}
328+
return renderSchema(schema);
329+
}
330+
331+
/**
332+
* @param {object} schemaProperty
333+
*/
334+
function renderSchemaWithReferencedProperty(schemaProperty) {
335+
return renderSchema({
336+
title: 'Args',
337+
type: 'object',
338+
properties: {
339+
testProp: {
340+
$ref: '#/definitions/Ref'
341+
}
144342
},
145-
required: ['testProp']
343+
definitions: {
344+
Ref: schemaProperty
345+
}
146346
});
147347
}
348+
148349
/**
149350
* @param {object} schema
150351
* @param {object} schemaData
@@ -154,3 +355,15 @@ function renderSchema(schema, schemaData = {}) {
154355
props: { schema, schemaData }
155356
});
156357
}
358+
359+
/**
360+
* @param {HTMLElement} element
361+
* @param {boolean} expected
362+
*/
363+
function checkBold(element, expected) {
364+
const isBold = element.classList.contains('fw-bold');
365+
if (isBold !== expected) {
366+
screen.debug(element);
367+
}
368+
expect(isBold).eq(expected);
369+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<div class="d-flex align-items-center p-2">
1616
<div class="property-metadata d-flex flex-row align-self-center w-50">
17-
<span class="">{schemaProperty.title || 'Boolean argument'}</span>
17+
<span class={schemaProperty.isRequired() ? 'fw-bold' : ''}>{schemaProperty.title || 'Boolean argument'}</span>
1818
<PropertyDescription description={schemaProperty.description} />
1919
</div>
2020
<div class="property-input ms-auto w-25">

0 commit comments

Comments
 (0)