diff --git a/src/core/meta/InputValidity.ts b/src/core/meta/InputValidity.ts index 689890fee..dd183cf89 100644 --- a/src/core/meta/InputValidity.ts +++ b/src/core/meta/InputValidity.ts @@ -1,14 +1,15 @@ import Base from './Base'; export class InputValidity extends Base { - get(key: string | number, value: string) { + get(key: string | number, value: string, required: boolean) { const node = this.getNode(key) as HTMLFormElement | undefined; if (!node) { return { valid: undefined, message: '' }; } - if (value !== node.value) { + const nodeRequired = node.attributes.getNamedItem('required'); + if (value !== node.value && required !== Boolean(nodeRequired && nodeRequired.value)) { // if the vdom is out of sync with the real dom our // validation check will be one render behind. // Call invalidate on the next loop. diff --git a/src/core/middleware/validity.ts b/src/core/middleware/validity.ts index 73caa01ff..5b47010a5 100644 --- a/src/core/middleware/validity.ts +++ b/src/core/middleware/validity.ts @@ -4,14 +4,15 @@ const factory = create({ node, invalidator }); const validity = factory(function({ middleware: { node, invalidator } }) { return { - get(key: string | number, value: string) { + get(key: string | number, value: string, required: boolean) { const domNode = node.get(key) as HTMLFormElement | undefined; if (!domNode) { return { valid: undefined, message: '' }; } - if (value !== domNode.value) { + const nodeRequired = domNode.attributes.getNamedItem('required'); + if (value !== domNode.value && required !== Boolean(nodeRequired && nodeRequired.value)) { setTimeout(() => invalidator()); } diff --git a/tests/core/unit/meta/InputValidity.ts b/tests/core/unit/meta/InputValidity.ts index 372ac000b..329a36538 100644 --- a/tests/core/unit/meta/InputValidity.ts +++ b/tests/core/unit/meta/InputValidity.ts @@ -34,7 +34,7 @@ registerSuite('meta - InputValidity', { bind: bindInstance }); - const { message, valid } = validity.get('test', 'testValue'); + const { message, valid } = validity.get('test', 'testValue', true); assert.strictEqual(message, ''); assert.isUndefined(valid); }, @@ -42,6 +42,9 @@ registerSuite('meta - InputValidity', { 'returns the validity and message for the element'() { sandbox.stub(nodeHandler, 'get').returns({ validity: { valid: false }, + attributes: { + getNamedItem: () => true + }, value: 'testValue', validationMessage: 'test validation message' }); @@ -51,7 +54,7 @@ registerSuite('meta - InputValidity', { bind: bindInstance }); - const { message, valid } = validity.get('test', 'testValue'); + const { message, valid } = validity.get('test', 'testValue', true); assert.strictEqual(message, 'test validation message'); assert.isFalse(valid); }, @@ -63,6 +66,9 @@ registerSuite('meta - InputValidity', { .withArgs('input') .returns({ validity: { valid: true }, + attributes: { + getNamedItem: () => true + }, value: 'test1', validationMessage: '' }); @@ -73,7 +79,37 @@ registerSuite('meta - InputValidity', { bind: bindInstance }); - validity.get('input', 'test2'); + validity.get('input', 'test2', true); + return new Promise((resolve) => { + setTimeout(() => { + assert.isTrue(invalidateStub.calledOnce); + nodeStub.reset(); + resolve(); + }, 10); + }); + }, + + async 'calls invalidate if the required value and node required attribute do not match'() { + const nodeHandler = new NodeHandler(); + const nodeStub = sandbox + .stub(nodeHandler, 'get') + .withArgs('input') + .returns({ + validity: { valid: true }, + attributes: { + getNamedItem: () => false + }, + value: 'test1', + validationMessage: '' + }); + + const validity = new InputValidity({ + invalidate: invalidateStub, + nodeHandler, + bind: bindInstance + }); + + validity.get('input', 'test2', true); return new Promise((resolve) => { setTimeout(() => { assert.isTrue(invalidateStub.calledOnce); diff --git a/tests/core/unit/middleware/validity.ts b/tests/core/unit/middleware/validity.ts index 5a3b8acb7..f57842ec5 100644 --- a/tests/core/unit/middleware/validity.ts +++ b/tests/core/unit/middleware/validity.ts @@ -26,7 +26,7 @@ describe('validity middleware', () => { properties: () => ({}), children: () => [] }); - const result = validity.get('root', 'testValue'); + const result = validity.get('root', 'testValue', true); assert.deepEqual(result, { valid: undefined, message: '' }); }); @@ -43,11 +43,14 @@ describe('validity middleware', () => { }); const domNode = { validity: { valid: false }, + attributes: { + getNamedItem: () => true + }, value: 'testValue', validationMessage: 'test validation message' }; nodeStub.get.withArgs('root').returns(domNode); - const result = validity.get('root', 'testValue'); + const result = validity.get('root', 'testValue', true); assert.deepEqual(result, { valid: false, message: 'test validation message' }); assert.isTrue(invalidatorStub.notCalled); }); @@ -65,11 +68,44 @@ describe('validity middleware', () => { }); const domNode = { validity: { valid: false }, + attributes: { + getNamedItem: () => true + }, + value: 'otherValue', + validationMessage: 'test validation message' + }; + nodeStub.get.withArgs('root').returns(domNode); + const result = validity.get('root', 'testValue', true); + assert.deepEqual(result, { valid: false, message: 'test validation message' }); + return new Promise((resolve) => { + setTimeout(() => { + assert.isTrue(invalidatorStub.calledOnce); + resolve(); + }, 10); + }); + }); + + it('should invalidate if the required value and domNode required attribute do not match', () => { + const { callback } = validityMiddleware(); + const validity = callback({ + id: 'test', + middleware: { + node: nodeStub, + invalidator: invalidatorStub + }, + properties: () => ({}), + children: () => [] + }); + const domNode = { + validity: { valid: false }, + attributes: { + getNamedItem: () => false + }, value: 'otherValue', validationMessage: 'test validation message' }; nodeStub.get.withArgs('root').returns(domNode); - const result = validity.get('root', 'testValue'); + const result = validity.get('root', 'testValue', true); assert.deepEqual(result, { valid: false, message: 'test validation message' }); return new Promise((resolve) => { setTimeout(() => {