197 lines
6.3 KiB
TypeScript
197 lines
6.3 KiB
TypeScript
import { Declaform } from '../src';
|
|
|
|
function initTestFormWithAttributes(attributes: Record<string, any>) {
|
|
const attributeStr = Object.entries(attributes).map(x => x.join('=')).join(' ');
|
|
return `
|
|
<decla-form id="form" ${attributeStr}>
|
|
<form slot="form">
|
|
<div class="input-group">
|
|
<label for="name">Name</label>
|
|
<input id="name" name="name" />
|
|
</div>
|
|
<select id="genre" name="genre">
|
|
<option value="comedy">Comedy</option>
|
|
<option value="horror">Horror</option>
|
|
</select>
|
|
<fieldset>
|
|
<legend>Rating</legend>
|
|
<div class="input-group">
|
|
<label for="rating-type-user">Type</label>
|
|
<input id="rating-type-user" name="rating.type" type="radio" value="user" />
|
|
</div>
|
|
<div class="input-group">
|
|
<label for="rating-type-critic">Type</label>
|
|
<input id="rating-type-critic" name="rating.type" type="radio" value="critic" />
|
|
</div>
|
|
<div class="input-group">
|
|
<label for="rating-score">Score</label>
|
|
<input id="rating-score" name="rating.score" type="number" />
|
|
</div>
|
|
</fieldset>
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
</decla-form>
|
|
`
|
|
|
|
}
|
|
|
|
const MOVIE_SCHEMA = {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
minLength: 1,
|
|
},
|
|
genre: {
|
|
type: 'string',
|
|
enum: ['comedy', 'horror'],
|
|
},
|
|
rating: {
|
|
type: 'object',
|
|
properties: {
|
|
type: {
|
|
type: 'string',
|
|
enum: ['critic', 'user'],
|
|
},
|
|
score: {
|
|
type: 'number',
|
|
minimum: 0,
|
|
maximum: 5,
|
|
},
|
|
},
|
|
required: ['type', 'score'],
|
|
}
|
|
},
|
|
required: ['name', 'genre', 'rating']
|
|
}
|
|
|
|
describe('Declaform', () => {
|
|
afterEach(() => {
|
|
document.body.innerHTML = '';
|
|
})
|
|
|
|
it('Should mount.', () => {
|
|
document.body.innerHTML = initTestFormWithAttributes({});
|
|
expect(document.getElementById('form')).toBeTruthy();
|
|
})
|
|
|
|
it('.toObject() should serialize form into a JS object', () => {
|
|
document.body.innerHTML = initTestFormWithAttributes({});
|
|
const form = document.getElementById('form') as Declaform;
|
|
setInputValue('name', 'Monty Python');
|
|
setInputValue('genre', 'comedy');
|
|
setRadioValue('rating.type', 'user');
|
|
setInputValue('rating-score', '5');
|
|
const obj = form.toObject();
|
|
expect(obj).toMatchObject({
|
|
name: 'Monty Python',
|
|
genre: 'comedy',
|
|
rating: {
|
|
score: 5,
|
|
type: 'user',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('.hydrate() should populate input fields from a JS object', () => {
|
|
document.body.innerHTML = initTestFormWithAttributes({});
|
|
const form = document.getElementById('form') as Declaform;
|
|
form.hydrate({
|
|
name: 'Jaws',
|
|
genre: 'horror',
|
|
rating: {
|
|
score: 5,
|
|
type: 'critic',
|
|
}
|
|
});
|
|
expect(getInputValue('name')).toBe('Jaws')
|
|
expect(getInputValue('genre')).toBe('horror')
|
|
expect(getInputValue('rating-score')).toBe('5');
|
|
expect(document.getElementById('rating-type-critic')?.hasAttribute('checked')).toBe(true)
|
|
});
|
|
|
|
it('Supplying "src" attribute should hydrate form with the supplied endpoint.', async () => {
|
|
globalThis.fetch = jest.fn().mockResolvedValue({
|
|
status: 200,
|
|
ok: true,
|
|
json: () => Promise.resolve({
|
|
name: 'Jaws',
|
|
genre: 'horror',
|
|
rating: {
|
|
score: 5,
|
|
type: 'critic',
|
|
}
|
|
})
|
|
})
|
|
document.body.innerHTML = initTestFormWithAttributes({
|
|
src: 'movie/123',
|
|
action: 'movie/123',
|
|
method: 'PUT',
|
|
});
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
|
|
expect(getInputValue('name')).toBe('Jaws')
|
|
expect(getInputValue('genre')).toBe('horror')
|
|
expect(getInputValue('rating-score')).toBe('5');
|
|
expect(document.getElementById('rating-type-critic')?.hasAttribute('checked')).toBe(true)
|
|
});
|
|
|
|
it('Should be able to be validated against a JSON Schema', async () => {
|
|
globalThis.fetch = jest.fn().mockResolvedValue({
|
|
status: 200,
|
|
ok: true,
|
|
json: () => Promise.resolve(MOVIE_SCHEMA),
|
|
});
|
|
|
|
document.body.innerHTML = initTestFormWithAttributes({
|
|
schema: 'movie_schema'
|
|
});
|
|
|
|
const form = document.getElementById('form') as Declaform;
|
|
await form.ready;
|
|
const isValid = await form.validate();
|
|
|
|
expect(isValid).not.toBe(true);
|
|
});
|
|
});
|
|
|
|
function setInputValue(id: string, value: string) {
|
|
const input = document.getElementById(id);
|
|
if (!input) {
|
|
throw new Error(`No input with id "${id}"!`);
|
|
}
|
|
if (!('value' in input)) {
|
|
throw new Error(`Tag with id "${id}" does not have "value" attribute!`);
|
|
}
|
|
input.value = value;
|
|
}
|
|
|
|
function getInputValue(id: string) {
|
|
const input = document.getElementById(id);
|
|
if (!input) {
|
|
throw new Error(`No input with id "${id}"!`);
|
|
}
|
|
if (!('value' in input)) {
|
|
throw new Error(`Tag with id "${id}" does not have "value" attribute!`);
|
|
}
|
|
return input.value;
|
|
}
|
|
|
|
function setRadioValue(name: string, value: string) {
|
|
const radios = document.querySelectorAll(`[name="${name}"]`);
|
|
if (!radios) {
|
|
throw new Error(`No radios with name "${name}"!`);
|
|
}
|
|
radios.forEach(radio => {
|
|
if (!('value' in radio)) {
|
|
throw new Error('Radio option does not have "value" attribute!');
|
|
}
|
|
radio.removeAttribute('checked');
|
|
})
|
|
const option = ([...radios] as HTMLInputElement[]).find(x => x.value);
|
|
if (!option) {
|
|
throw new Error(`No option with value "${value}"`);
|
|
}
|
|
option.setAttribute('checked', '');
|
|
} |