Skip to content

Commit 7ed9e3e

Browse files
committed
fix required values
fix tests fix required fields validation
1 parent eb5ee82 commit 7ed9e3e

File tree

9 files changed

+64
-65
lines changed

9 files changed

+64
-65
lines changed

.changeset/heavy-geckos-exist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"codemirror-json-schema": patch
3+
---
4+
5+
fix required fields validation

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Based on whether you want to support json4, json5 or both, you will need to inst
2929

3030
### Breaking Changes:
3131

32-
- 0.5.0 - this breaking change _does not_ effect users using `jsonSchema()` or `json5Schema()` modes, but those using the "custom path".
32+
- 0.5.0 - this breaking change only impacts those following the "custom usage" approach, it _does not_ effect users using the high level, "bundled" `jsonSchema()` or `json5Schema()` modes. See the custom usages below to learn how to use the new `stateExtensions` and `handleRefresh` exports.
3333

3434
### json4
3535

src/__tests__/__fixtures__/schemas.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const testSchema2 = {
2525
description: "an elegant string",
2626
},
2727
},
28+
required: ["foo"],
2829
additionalProperties: false,
2930
},
3031
oneOfEg: {
@@ -71,7 +72,7 @@ export const testSchema2 = {
7172
type: "boolean",
7273
},
7374
},
74-
required: ["foo", "object.foo"],
75+
required: ["foo", "object"],
7576
additionalProperties: false,
7677
definitions: {
7778
fancyObject: {

src/__tests__/json-completion.spec.ts

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { describe, it } from "vitest";
22

3-
import { expectCompletion } from "./__helpers__/completion";
4-
import { testSchema3, testSchema4 } from "./__fixtures__/schemas";
3+
import { expectCompletion } from "./__helpers__/completion.js";
54

65
describe("jsonCompletion", () => {
76
it("should return completion data for simple types", async () => {
@@ -292,57 +291,6 @@ describe("jsonCompletion", () => {
292291
},
293292
]);
294293
});
295-
it("should autocomplete for array of objects with filter", async () => {
296-
await expectCompletion('{ "arrayOfObjects": [ { "f|" } ] }', [
297-
{
298-
detail: "string",
299-
info: "",
300-
label: "foo",
301-
template: '"foo": "#{}"',
302-
type: "property",
303-
},
304-
]);
305-
});
306-
it("should autocomplete for a schema with top level $ref", async () => {
307-
await expectCompletion(
308-
'{ "| }',
309-
[
310-
{
311-
type: "property",
312-
detail: "string",
313-
info: "",
314-
label: "foo",
315-
},
316-
{
317-
type: "property",
318-
detail: "number",
319-
info: "",
320-
label: "bar",
321-
},
322-
],
323-
{ schema: testSchema3 }
324-
);
325-
});
326-
it("should autocomplete for a schema with top level complex type", async () => {
327-
await expectCompletion(
328-
'{ "| }',
329-
[
330-
{
331-
type: "property",
332-
detail: "string",
333-
info: "",
334-
label: "foo",
335-
},
336-
{
337-
type: "property",
338-
detail: "number",
339-
info: "",
340-
label: "bar",
341-
},
342-
],
343-
{ schema: testSchema4 }
344-
);
345-
});
346294
});
347295

348296
describe("json5Completion", () => {

src/__tests__/json-validation.spec.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const getErrors = (jsonString: string, schema?: JSONSchema7) => {
1717
};
1818
const expectErrors = (
1919
jsonString: string,
20-
errors: [from: number, to: number, message: string][],
20+
errors: [from: number | undefined, to: number | undefined, message: string][],
2121
schema?: JSONSchema7
2222
) => {
2323
expect(getErrors(jsonString, schema)).toEqual(
@@ -42,7 +42,9 @@ describe("json-validation", () => {
4242
]);
4343
});
4444
it("should not handle invalid json", () => {
45-
expectErrors('{"foo": "example" "bar": 123}', []);
45+
expectErrors('{"foo": "example" "bar": 123}', [
46+
[undefined, undefined, "Expected `object` but received `null`"],
47+
]);
4648
});
4749
it("should provide range for invalid multline json", () => {
4850
expectErrors(
@@ -53,23 +55,35 @@ describe("json-validation", () => {
5355
[[32, 37, "Additional property `bar` in `#` is not allowed"]]
5456
);
5557
});
58+
it("should provide formatted error message when required fields are missing", () => {
59+
expectErrors(
60+
`{
61+
"foo": "example",
62+
"object": {}
63+
}`,
64+
[[46, 48, "The required property `foo` is missing at `object`"]],
65+
testSchema2
66+
);
67+
});
5668
it("should provide formatted error message for oneOf fields with more than 2 items", () => {
5769
expectErrors(
5870
`{
5971
"foo": "example",
72+
"object": { "foo": "true" },
6073
"oneOfEg": 123
6174
}`,
62-
[[43, 46, 'Expected one of `"string"`, `"array"`, or `"boolean"`']],
75+
[[80, 83, 'Expected one of `"string"`, `"array"`, or `"boolean"`']],
6376
testSchema2
6477
);
6578
});
6679
it("should provide formatted error message for oneOf fields with less than 2 items", () => {
6780
expectErrors(
6881
`{
6982
"foo": "example",
83+
"object": { "foo": "true" },
7084
"oneOfEg2": 123
7185
}`,
72-
[[44, 47, 'Expected one of `"string"` or `"array"`']],
86+
[[81, 84, 'Expected one of `"string"` or `"array"`']],
7387
testSchema2
7488
);
7589
});

src/json-validation.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,24 +106,33 @@ export class JSONValidation {
106106
try {
107107
errors = this.schema.validate(json.data);
108108
} catch {}
109-
110109
if (!errors.length) return [];
111110
// reduce() because we want to filter out errors that don't have a pointer
112111
return errors.reduce((acc, error) => {
113112
const errorPath = getErrorPath(error);
114113
const pointer = json.pointers.get(errorPath) as JSONPointerData;
115114
if (pointer) {
116115
// if the error is a property error, use the key position
117-
const isPropertyError = error.name === "NoAdditionalPropertiesError";
116+
const isKeyError =
117+
error.name === "NoAdditionalPropertiesError" ||
118+
error.name === "RequiredPropertyError";
118119
acc.push({
119-
from: isPropertyError ? pointer.keyFrom : pointer.valueFrom,
120-
to: isPropertyError ? pointer.keyTo : pointer.valueTo,
120+
from: isKeyError ? pointer.keyFrom : pointer.valueFrom,
121+
to: isKeyError ? pointer.keyTo : pointer.valueTo,
121122
// TODO: create a domnode and replace `` with <code></code>
122123
// renderMessage: () => error.message,
123124
message: this.rewriteError(error),
124125
severity: "error",
125126
source: this.schemaTitle,
126127
});
128+
} else {
129+
acc.push({
130+
from: 0,
131+
to: 0,
132+
message: this.rewriteError(error),
133+
severity: "error",
134+
source: this.schemaTitle,
135+
});
127136
}
128137
return acc;
129138
}, [] as Diagnostic[]);

src/utils/__tests__/jsonPointers.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe("jsonPointerForPosition for json5", () => {
6363
describe("getJsonPointers", () => {
6464
it("should return a map of all pointers for a document", () => {
6565
const state = EditorState.create({
66-
doc: '{"object": { "foo": true }, "bar": 123}',
66+
doc: '{"object": { "foo": true }, "bar": 123, "baz": [1,2,3], "boop": [{"foo": true}]}',
6767
extensions: [json()],
6868
});
6969
const pointers = getJsonPointers(state);
@@ -79,6 +79,25 @@ describe("getJsonPointers", () => {
7979
valueFrom: 35,
8080
valueTo: 38,
8181
});
82+
expect(pointers.get("/baz")).toEqual({
83+
keyFrom: 40,
84+
keyTo: 45,
85+
valueFrom: 47,
86+
valueTo: 54,
87+
});
88+
expect(pointers.get("/boop/0")).toEqual({
89+
keyFrom: 65,
90+
keyTo: 78,
91+
valueFrom: 78,
92+
valueTo: 79,
93+
});
94+
// TODO: return pointers for all array indexes, not just objects
95+
// expect(pointers.get("/baz/0")).toEqual({
96+
// keyFrom: 40,
97+
// keyTo: 45,
98+
// valueFrom: 47,
99+
// valueTo: 55,
100+
// });
82101
});
83102
});
84103

src/utils/__tests__/parseJSONDocument.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ describe("parseJSONDocument", () => {
88
const doc = parseJSONDocument(`{"object": { "foo": true }, "bar": 123}`);
99
expect(doc.data).toEqual({ object: { foo: true }, bar: 123 });
1010
expect(Array.from(doc.pointers.keys())).toEqual([
11+
"",
1112
"/object",
1213
"/object/foo",
1314
"/bar",
@@ -20,6 +21,7 @@ describe("parseJSON5Document", () => {
2021
const doc = parseJSON5Document(`{'obj"ect': { foo: true }, "bar": 123}`);
2122
expect(doc.data).toEqual({ ['obj"ect']: { foo: true }, bar: 123 });
2223
expect(Array.from(doc.pointers.keys())).toEqual([
24+
"",
2325
'/obj"ect',
2426
'/obj"ect/foo',
2527
"/bar",

src/utils/jsonPointers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function getJsonPointerAt(docText: Text, node: SyntaxNode): string {
3434
}
3535
}
3636
path.unshift("");
37+
3738
return path.join("/");
3839
}
3940

@@ -61,7 +62,7 @@ export const getJsonPointers = (
6162
const pointers: JSONPointersMap = new Map();
6263
json.iterate({
6364
enter: (type: SyntaxNodeRef) => {
64-
if (type.name === "PropertyName") {
65+
if (type.name === "PropertyName" || type.name === "Object") {
6566
const pointer = getJsonPointerAt(state.doc, type.node);
6667

6768
const { from: keyFrom, to: keyTo } = type.node;

0 commit comments

Comments
 (0)