@lion/ui
Version:
A package of extendable web components
218 lines (193 loc) • 6.62 kB
JavaScript
/* eslint-disable dot-notation */
import { LitElement } from 'lit';
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
import sinon from 'sinon';
import { ValidateMixin, Validator } from '@lion/ui/form-core.js';
/**
* @param {function} method
* @param {string} errorMessage
*/
async function expectThrowsAsync(method, errorMessage) {
let error = null;
try {
await method();
} catch (err) {
error = err;
}
expect(error).to.be.an('Error', 'No error was thrown');
if (errorMessage) {
expect(/** @type {Error} */ (error).message).to.equal(errorMessage);
}
}
/**
* @param {Validator} validatorEl
*/
function getProtectedMembers(validatorEl) {
return {
// @ts-ignore
getMessage: (...args) => validatorEl._getMessage(...args),
};
}
describe('Validator', () => {
it('has an "execute" function returning "shown" state', async () => {
class MyValidator extends Validator {
/**
* @param {string} [modelValue]
* @param {string} [param]
*/
execute(modelValue, param) {
const hasError = modelValue === 'test' && param === 'me';
return hasError;
}
}
expect(new MyValidator().execute('test', 'me')).to.be.true;
});
it('throws when executing a Validator without a name', async () => {
class MyValidator extends Validator {}
expect(() => {
new MyValidator().execute(undefined);
}).to.throw(
'A validator needs to have a name! Please set it via "static get validatorName() { return \'IsCat\'; }"',
);
});
it('throws when executing a Validator that has a getMessage config property with a value not of type function', async () => {
class MyValidator extends Validator {
static get validatorName() {
return 'MyValidator';
}
}
// @ts-ignore needed for test
const vali = new MyValidator({}, { getMessage: 'This is the custom error message' });
const { getMessage } = getProtectedMembers(vali);
await expectThrowsAsync(
() => getMessage(),
"You must provide a value for getMessage of type 'function', you provided a value of type: string",
);
});
it('receives a "param" as a first argument on instantiation', async () => {
const vali = new Validator('myParam');
expect(vali.param).to.equal('myParam');
});
it('receives a config object (optionally) as a second argument on instantiation', async () => {
const vali = new Validator('myParam', { fieldName: 'X' });
expect(vali.config).to.eql({ fieldName: 'X' });
});
it('has access to name, type, params, config in getMessage provided by config', () => {
const configSpy = sinon.spy();
class MyValidator extends Validator {
static get validatorName() {
return 'MyValidator';
}
}
const vali = new MyValidator('myParam', { fieldName: 'X', getMessage: configSpy });
const { getMessage } = getProtectedMembers(vali);
getMessage();
expect(configSpy.args[0][0]).to.deep.equal({
name: 'MyValidator',
type: 'error',
params: 'myParam',
config: { fieldName: 'X', getMessage: configSpy },
});
});
it('has access to name, type, params, config in static get getMessage', () => {
let data;
class MyValidator extends Validator {
static get validatorName() {
return 'MyValidator';
}
/**
* @param {Object.<string,?>} _data
*/
static async getMessage(_data) {
data = _data;
return '';
}
}
const vali = new MyValidator('myParam', { fieldName: 'X' });
const { getMessage } = getProtectedMembers(vali);
getMessage();
expect(data).to.deep.equal({
name: 'MyValidator',
type: 'error',
params: 'myParam',
config: { fieldName: 'X' },
});
});
it('fires "param-changed" event on param change', async () => {
const vali = new Validator('foo');
const cb = sinon.spy(() => {});
if (vali.addEventListener) {
vali.addEventListener('param-changed', cb);
}
vali.param = 'bar';
expect(cb.callCount).to.equal(1);
});
it('fires "config-changed" event on config change', async () => {
const vali = new Validator('foo', { fieldName: 'X' });
const cb = sinon.spy(() => {});
if (vali.addEventListener) {
vali.addEventListener('config-changed', cb);
}
vali.config = { fieldName: 'Y' };
expect(cb.callCount).to.equal(1);
});
it('has access to FormControl', async () => {
const lightDom = '';
class ValidateElement extends ValidateMixin(LitElement) {
/** @type {any} */
static get properties() {
return { modelValue: String };
}
}
const tagString = defineCE(ValidateElement);
const tag = unsafeStatic(tagString);
class MyValidator extends Validator {
/**
* @param {string} modelValue
* @param {string} param
*/
execute(modelValue, param) {
const hasError = modelValue === 'forbidden' && param === 'values';
return hasError;
}
}
const myVal = new MyValidator();
const connectSpy = sinon.spy(myVal, 'onFormControlConnect');
const disconnectSpy = sinon.spy(myVal, 'onFormControlDisconnect');
const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} .validators=${[myVal]}>${lightDom}</${tag}>
`)
);
expect(connectSpy.callCount).to.equal(1);
expect(connectSpy.calledWith(el)).to.equal(true);
expect(disconnectSpy.callCount).to.equal(0);
el.validators = [];
expect(connectSpy.callCount).to.equal(1);
expect(disconnectSpy.callCount).to.equal(1);
expect(disconnectSpy.calledWith(el)).to.equal(true);
});
it('adds static ["_$isValidator$"] property as a marker to identify the Validator class across different lion versions (as instanceof cannot be used)', async () => {
const myValidator = new Validator();
expect(myValidator.constructor['_$isValidator$']).to.exist;
expect(myValidator.constructor['_$isValidator$']).to.be.true;
});
describe('Types', () => {
it('has type "error" by default', async () => {
expect(new Validator().type).to.equal('error');
});
it('supports customized types', async () => {
// This test shows the best practice of adding custom types
class MyValidator extends Validator {
/**
* @param {...any} args
*/
constructor(...args) {
super(...args);
this.type = 'my-type';
}
}
expect(new MyValidator().type).to.equal('my-type');
});
});
});