diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js index 7b6b9c53..6a590911 100644 --- a/ext/js/data/json-schema.js +++ b/ext/js/data/json-schema.js @@ -103,8 +103,12 @@ class JsonSchemaProxyHandler { } deleteProperty(target, property) { - const required = this._schema.required; - if (Array.isArray(required) && required.includes(property)) { + const required = ( + (typeof target === 'object' && target !== null) ? + (Array.isArray(target) || this._jsonSchemaValidator.isObjectPropertyRequired(this._schema, property)) : + true + ); + if (required) { throw new Error(`${property} cannot be deleted`); } return Reflect.deleteProperty(target, property); @@ -159,6 +163,11 @@ class JsonSchemaValidator { this._regexCache.clear(); } + isObjectPropertyRequired(schema, property) { + const {required} = schema; + return Array.isArray(required) && required.includes(property); + } + // Private _getPropertySchema(schema, property, value, path) { diff --git a/test/test-schema.js b/test/test-json-schema.js similarity index 78% rename from test/test-schema.js rename to test/test-json-schema.js index 7a1ea9bc..d8e351be 100644 --- a/test/test-schema.js +++ b/test/test-json-schema.js @@ -28,6 +28,19 @@ vm.execute([ const JsonSchemaValidator = vm.get('JsonSchemaValidator'); +function schemaValidate(schema, value) { + return new JsonSchemaValidator().isValid(value, schema); +} + +function getValidValueOrDefault(schema, value) { + return new JsonSchemaValidator().getValidValueOrDefault(schema, value); +} + +function createProxy(schema, value) { + return new JsonSchemaValidator().createProxy(value, schema); +} + + function testValidate1() { const schema = { allOf: [ @@ -54,10 +67,6 @@ function testValidate1() { ] }; - const schemaValidate = (value) => { - return new JsonSchemaValidator().isValid(value, schema); - }; - const jsValidate = (value) => { return ( typeof value === 'number' && @@ -77,7 +86,7 @@ function testValidate1() { }; for (let i = -111; i <= 111; i++) { - const actual = schemaValidate(i, schema); + const actual = schemaValidate(schema, i); const expected = jsValidate(i); assert.strictEqual(actual, expected); } @@ -403,13 +412,9 @@ function testValidate2() { } ]; - const schemaValidate = (value, schema) => { - return new JsonSchemaValidator().isValid(value, schema); - }; - for (const {schema, inputs} of data) { for (const {expected, value} of inputs) { - const actual = schemaValidate(value, schema); + const actual = schemaValidate(schema, value); assert.strictEqual(actual, expected); } } @@ -686,17 +691,132 @@ function testGetValidValueOrDefault1() { for (const {schema, inputs} of data) { for (const [value, expected] of inputs) { - const actual = new JsonSchemaValidator().getValidValueOrDefault(schema, value); + const actual = getValidValueOrDefault(schema, value); vm.assert.deepStrictEqual(actual, expected); } } } +function testProxy1() { + const data = [ + // Object tests + { + schema: { + type: 'object', + required: ['test'], + additionalProperties: false, + properties: { + test: { + type: 'string', + default: 'default' + } + } + }, + tests: [ + {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, + {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, + {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} + ] + }, + { + schema: { + type: 'object', + required: ['test'], + additionalProperties: true, + properties: { + test: { + type: 'string', + default: 'default' + } + } + }, + tests: [ + {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, + {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, + {error: false, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, + {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} + ] + }, + { + schema: { + type: 'object', + required: ['test1'], + additionalProperties: false, + properties: { + test1: { + type: 'object', + required: ['test2'], + additionalProperties: false, + properties: { + test2: { + type: 'object', + required: ['test3'], + additionalProperties: false, + properties: { + test3: { + type: 'string', + default: 'default' + } + } + } + } + } + } + }, + tests: [ + {error: false, action: (value) => { value.test1.test2.test3 = 'string'; }}, + {error: true, action: (value) => { value.test1.test2.test3 = null; }}, + {error: true, action: (value) => { delete value.test1.test2.test3; }}, + {error: true, action: (value) => { value.test1.test2 = null; }}, + {error: true, action: (value) => { value.test1 = null; }}, + {error: true, action: (value) => { value.test4 = 'string'; }}, + {error: false, action: (value) => { delete value.test4; }} + ] + }, + + // Array tests + { + schema: { + type: 'array', + items: { + type: 'string', + default: 'default' + } + }, + tests: [ + {error: false, value: ['default'], action: (value) => { value[0] = 'string'; }}, + {error: true, value: ['default'], action: (value) => { value[0] = null; }}, + {error: true, value: ['default'], action: (value) => { delete value[0]; }}, + {error: false, value: ['default'], action: (value) => { value[1] = 'string'; }} + ] + } + ]; + + for (const {schema, tests} of data) { + for (let {error, value, action} of tests) { + if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } + assert.ok(schemaValidate(schema, value)); + console.log(error, value, schema); + const valueProxy = createProxy(schema, value); + if (error) { + assert.throws(() => action(valueProxy)); + } else { + assert.doesNotThrow(() => action(valueProxy)); + } + } + } +} + + function main() { testValidate1(); testValidate2(); testGetValidValueOrDefault1(); + testProxy1(); }