Skip to content

Commit aa98423

Browse files
committed
Improve error annotations display
1 parent 08254aa commit aa98423

6 files changed

Lines changed: 547 additions & 1 deletion

File tree

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
{
2+
"type": "object",
3+
"title": "Person",
4+
"description": "A person schema",
5+
"$schema": "https://json-schema.org/draft/2020-12/schema",
6+
"$id": "https://example.com/person.schema.json",
7+
"required": ["name", "firstName"],
8+
"$defs": {
9+
"name": {
10+
"type": "string",
11+
"description": "Last name"
12+
},
13+
"circular": {
14+
"title": "Circular",
15+
"type": "object",
16+
"properties": {
17+
"name": {
18+
"$ref": "#/$defs/name",
19+
"minLength": 23
20+
},
21+
"circular": {
22+
"$ref": "#/$defs/circular"
23+
}
24+
}
25+
},
26+
"isMarried": {
27+
"type": "boolean",
28+
"const": true
29+
}
30+
},
31+
"patternProperties": {
32+
"^Number.*": {
33+
"type": "number",
34+
"description": "Any number property"
35+
}
36+
},
37+
"if": {
38+
"properties": {
39+
"isMarried": {
40+
"$ref": "#/$defs/isMarried"
41+
}
42+
},
43+
"required": ["isMarried"]
44+
},
45+
"then": {
46+
"properties": {
47+
"spouse": {
48+
"type": "object",
49+
"description": "Spouse",
50+
"properties": {
51+
"name": {
52+
"$ref": "#/$defs/name"
53+
},
54+
"firstName": {
55+
"type": "string",
56+
"description": "First name"
57+
}
58+
}
59+
}
60+
}
61+
},
62+
"dependentSchemas": {
63+
"nickNames": {
64+
"properties": {
65+
"preferredNickName": {
66+
"type": "string",
67+
"description": "Preferred nick name"
68+
}
69+
}
70+
}
71+
},
72+
"properties": {
73+
"circular": {
74+
"$ref": "#/$defs/circular"
75+
},
76+
"name": {
77+
"$ref": "#/$defs/name"
78+
},
79+
"firstName": {
80+
"type": "string",
81+
"description": "First name",
82+
"examples": ["John"],
83+
"deprecated": true
84+
},
85+
"nickNames": {
86+
"type": "array",
87+
"title": "Nick names",
88+
"description": "Nick names",
89+
"items": {
90+
"type": "string"
91+
}
92+
},
93+
"isMarried": {
94+
"type": "boolean",
95+
"description": "Marital Status"
96+
},
97+
"telephoneNumber": {
98+
"type": "integer",
99+
"description": "phone number",
100+
"exclusiveMinimum": 149,
101+
"maximum": 159
102+
},
103+
"heightInMeter": {
104+
"type": "number",
105+
"description": "Height",
106+
"exclusiveMinimum": 1.2,
107+
"maximum": 2.3,
108+
"multipleOf": 0.01
109+
},
110+
"address": {
111+
"type": "object",
112+
"description": "Address of the person",
113+
"allOf": [
114+
{
115+
"properties": {
116+
"street": {
117+
"type": "string",
118+
"description": "Street name",
119+
"examples": ["Main Street"]
120+
}
121+
}
122+
},
123+
{
124+
"properties": {
125+
"number": {
126+
"type": "number"
127+
}
128+
}
129+
},
130+
{
131+
"if": {
132+
"properties": {
133+
"number": {
134+
"multipleOf": 2
135+
}
136+
},
137+
"required": ["number"]
138+
},
139+
"then": {
140+
"properties": {
141+
"number": {
142+
"description": "Even street number"
143+
}
144+
}
145+
},
146+
"else": {
147+
"properties": {
148+
"number": {
149+
"description": "Odd street number"
150+
}
151+
}
152+
}
153+
},
154+
{
155+
"if": {
156+
"properties": {
157+
"street": {
158+
"const": "Main Street"
159+
}
160+
},
161+
"required": ["street"]
162+
},
163+
"then": {
164+
"properties": {
165+
"extraInfo": {
166+
"type": "string",
167+
"description": "Main street extra info"
168+
}
169+
},
170+
"required": ["extraInfo"]
171+
}
172+
}
173+
],
174+
"dependentRequired": {
175+
"city": ["zipCode"]
176+
},
177+
"properties": {
178+
"city": {
179+
"type": "string",
180+
"description": "City name"
181+
},
182+
"zipCode": {
183+
"type": "string",
184+
"description": "Zip code",
185+
"examples": ["12345"]
186+
},
187+
"country": {
188+
"description": "Country name",
189+
"enum": ["Germany", "India", "China", "America", "Japan", "Spain", "France"]
190+
},
191+
"moreInfo": {
192+
"type": "object",
193+
"description": "More info about the address",
194+
"deprecated": true,
195+
"properties": {
196+
"anyThing": true,
197+
"info": {
198+
"type": "string",
199+
"description": "Some info"
200+
},
201+
"neighborhood": {
202+
"type": "string",
203+
"description": "Neighborhood name"
204+
},
205+
"timeZone": {
206+
"description": "Time zone",
207+
"const": "UTC"
208+
},
209+
"booleanArray": {
210+
"type": "array",
211+
"description": "Boolean array",
212+
"items": {
213+
"type": "boolean"
214+
}
215+
},
216+
"numbers": {
217+
"type": "array",
218+
"description": "Numbers",
219+
"items": {
220+
"type": "number"
221+
}
222+
},
223+
"objects": {
224+
"type": "array",
225+
"description": "Objects",
226+
"items": {
227+
"type": "object",
228+
"properties": {
229+
"name": {
230+
"type": "string"
231+
},
232+
"age": {
233+
"type": "number"
234+
}
235+
}
236+
}
237+
}
238+
}
239+
}
240+
}
241+
},
242+
"partner": {
243+
"title": "partner",
244+
"oneOf": [
245+
{
246+
"type": "boolean",
247+
"const": false,
248+
"title": "No Partner"
249+
},
250+
{
251+
"type": "string",
252+
"title": "Partner Name"
253+
}
254+
]
255+
}
256+
}
257+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"heightInMeter": 9.24,
3+
"telephoneNumber": 159,
4+
"isMarried": true,
5+
"address": {
6+
"zipCode": 4
7+
}
8+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {test, expect, type Page} from '@playwright/test';
2+
import {openApp} from '../../tests/shared/utils';
3+
import {SessionMode} from '../src/store/sessionMode';
4+
import {checkCodeEditorForText, getCodeEditor} from '../../tests/shared/utilsCodeEditor';
5+
6+
/**
7+
* Reads the validation annotations Ace has rendered on the editor for the given mode.
8+
* Returns an array of {row (0-indexed), text} for each annotation marker.
9+
*/
10+
async function readAnnotations(
11+
page: Page,
12+
mode: SessionMode
13+
): Promise<Array<{row: number; text: string}>> {
14+
return await page.evaluate(modePrefix => {
15+
const editor = document.querySelector(`[id^="code-editor-${modePrefix}"]`) as HTMLElement;
16+
if (!editor) return [];
17+
const ace = (window as any).ace;
18+
const aceEditor = ace.edit(editor.id);
19+
const annotations = aceEditor.getSession().getAnnotations() as Array<{
20+
row: number;
21+
text: string;
22+
}>;
23+
return annotations.map(a => ({row: a.row, text: a.text}));
24+
}, mode);
25+
}
26+
27+
test('Text editor shows annotations only at the leaf-error rows, including required-at-root', async ({
28+
page,
29+
}) => {
30+
await openApp(
31+
page,
32+
'settings_testpanel.json',
33+
'personInvalid.json',
34+
'featureTesting.schema.json'
35+
);
36+
37+
// wait for data to render
38+
await checkCodeEditorForText(page, '"heightInMeter"', SessionMode.DataEditor);
39+
await getCodeEditor(page, SessionMode.DataEditor).waitFor();
40+
41+
// give validation worker + 500ms annotation debounce time to run
42+
await page.waitForTimeout(1500);
43+
44+
const annotations = await readAnnotations(page, SessionMode.DataEditor);
45+
46+
// Row map for personInvalid.json:
47+
// 0: {
48+
// 1: "heightInMeter": 9.24,
49+
// 2: "telephoneNumber": 159,
50+
// 3: "isMarried": true,
51+
// 4: "address": {
52+
// 5: "zipCode": 4
53+
// 6: }
54+
// 7: }
55+
const rowsWithAnnotations = annotations.map(a => a.row).sort((a, b) => a - b);
56+
57+
// Expectation: required errors → row 0 (root opening brace), heightInMeter error → row 1,
58+
// zipCode error → row 5. No annotation on the address row (4) or anywhere else.
59+
expect(rowsWithAnnotations).toContain(0); // required at root
60+
expect(rowsWithAnnotations).toContain(1); // heightInMeter
61+
expect(rowsWithAnnotations).toContain(5); // address/zipCode
62+
63+
// No annotation on the address line (it would be a parent-level summary that we now filter out)
64+
expect(rowsWithAnnotations).not.toContain(4);
65+
});

0 commit comments

Comments
 (0)