UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

796 lines (588 loc) 27 kB
'use strict'; import * as chai from 'chai'; import {ID} from "../../../source/types/id.mjs"; import {Observer} from "../../../source/types/observer.mjs"; import {ProxyObserver} from "../../../source/types/proxyobserver.mjs"; import {chaiDom} from "../../util/chai-dom.mjs"; import {initJSDOM} from "../../util/jsdom.mjs"; let expect = chai.expect; chai.use(chaiDom); let html1 = ` <template id="current"> <li data-monster-replace="path:current | tojson"></li> </template> <div id="test1"> <ul data-monster-insert="current path:a.b"> </ul> </div> <div id="test2"> <ul data-monster-insert="current path:a.b | doit"> </ul> </div> <div id="test3"> <div data-monster-attributes="class path:a.b"> <input data-monster-attributes="value path:a.c" id="input1"> <input data-monster-attributes="checked path:a.checkbox" type="checkbox" name="checkbox" id="checkbox"> <input data-monster-attributes="value path:a.text" type="text" name="text" id="text"> <input data-monster-attributes="checked path:a.radio" type="radio" name="radio" value="r1" id="radio"> <input type="radio" name="radio" value="r2" id="r2"> <input type="radio" name="radio" value="rx" id="rx"> <select data-monster-attributes="value path:a.select" name="select" id="select"> <option value="other-value">value1</option> <option>value2</option> </select> <select data-monster-attributes="value path:a.multiselect" name="multiselect" multiple id="multiselect"> <option>value1</option> <option>value2</option> <option>value3</option> <option>value4</option> <option value="other-value5">value5</option> </select> <textarea name="textarea" id="textarea" data-monster-attributes="value path:a.textarea"></textarea> </div> </div> `; let html2 = ` <div id="test1"> <div data-monster-replace="path:text | tolower"></div> <div data-monster-replace="path:text | call:myformatter"></div> <div data-monster-replace="static:hello\\ "></div> </div> `; let html3 = ` <template id="myinnerid"> <span data-monster-replace="path:myinnerid | tojson"></span> </template> <template id="myid"> <p data-monster-insert="myinnerid path:a.b"></p> </template> <div id="test1"> <div data-monster-insert="myid path:a.b"></div> </div> `; let html4 = ` <div> <form id="form1"> <input type="checkbox" value="checked" name="checkbox" data-monster-bind="path:state"> <input type="text" name="text"> <input type="radio" name="radio" value="r1" id="r1" data-monster-bind="path:radio"> <input type="radio" name="radio" value="r2" id="r2" data-monster-bind="path:radio"> <input type="radio" name="radio" value="rx" id="rx" data-comment="not called because no bind attribute"> <input type="button" name="button"> <select name="select1" id="select1" data-monster-bind="path:select"> <option>value1</option> <option>value2</option> </select> <select name="select2" multiple id="select2" data-monster-bind="path:multiselect"> <option>value1</option> <option>value2</option> <option>value3</option> <option>value4</option> <option>value5</option> </select> <textarea name="textarea" id="textarea" data-monster-bind="path:textarea"> </textarea> </form> </div> `; describe('DOM', function () { let Updater = null; before(function (done) { const options = { } initJSDOM(options).then(() => { import("../../../source/dom/updater.mjs").then((m) => { Updater = m.Updater; done(); }).catch((e) => { done(e) }); }); }); beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; }) afterEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = ""; }) describe('Updater()', function () { describe('test Getter && Setter', function () { it('setEventTypes()', function () { let element = document.getElementById('test1') expect(new Updater(element).setEventTypes(['touch'])).to.be.instanceof(Updater); }) it('getSubject()', function () { let element = document.getElementById('test1') let subject = {a: 1}; expect(new Updater(element, subject).getSubject().a).to.be.equal(1); }) }); describe('test control methods', function () { it('enableEventProcessing()', function () { let element = document.getElementById('test1') expect(new Updater(element).enableEventProcessing()).to.be.instanceof(Updater); }) it('disableEventProcessing()', function () { let element = document.getElementById('test1') expect(new Updater(element).disableEventProcessing()).to.be.instanceof(Updater); }) }); describe('test Errors', function () { it('should throw value is not an instance of HTMLElement Error', function () { expect(() => new Updater()).to.throw(TypeError) }) it('should throw value is wrong', function () { let element = document.getElementById('test1') expect(() => new Updater(element, null)).to.throw(TypeError) }) it('should throw Error: the value is not iterable', function (done) { let element = document.getElementById('test1') let u = new Updater( element, { a: { x: [] } } ); let promise = u.run(); setTimeout(() => { promise.then(() => { setTimeout(() => { done(new Error("should never called!")); }, 100); }).catch((e) => { expect(e).is.instanceOf(Error); expect(e + "").to.be.equal('Error: the value is not iterable'); done(); }) }, 100); }); }); }); describe('Updater()', function () { describe('new Updater', function () { it('should return document object', function () { let element = document.getElementById('test1') let d = new Updater( element, {} ); expect(typeof d).is.equal('object'); }); }); }); describe('Updater()', function () { describe('Repeat', function () { it('should build 6 li elements', function (done) { let element = document.getElementById('test1') let d = new Updater( element, { a: { b: [ {i: '0'}, {i: '1'}, {i: '2'}, {i: '3'}, {i: '4'}, {i: '5'}, ] } } ); d.run().then(() => { setTimeout(() => { expect(typeof d).is.equal('object'); for (let i = 0; i < 6; i++) { expect(element).contain.html('<li data-monster-replace="path:a.b.' + i + ' | tojson" data-monster-insert-reference="current-' + i + '">{"i":"' + i + '"}</li>'); } done(); }, 100); }).catch( e => { done(new Error(e)) }) }); }); }); describe('Updater()', function () { beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html4; }) describe('Eventhandling', function () { let updater, form1, proxyobserver; beforeEach(() => { proxyobserver = new ProxyObserver({}) updater = new Updater(document.getElementById('form1'), proxyobserver); form1 = document.getElementById('form1'); }) // here click events are thrown on the checkbox and the setting of the value is observed via the proxyobserver. it('should handle checkbox click events', function (done) { updater.enableEventProcessing(); let subject = updater.getSubject(); expect(subject).is.equal(proxyobserver.getSubject()); let expected = ['checked', undefined, 'checked']; // here the notation with function is important, because the pointer is set. proxyobserver.attachObserver(new Observer(function () { let e = expected.shift(); if (e === undefined && expected.length === 0) done(new Error('to many calls')); if (this.getSubject()['state'] !== e) done(new Error(this.getSubject()['state'] + ' should ' + e)); if (expected.length === 0) { done(); } else { setTimeout(() => { form1.querySelector('[name=checkbox]').click(); }, 10) } })); setTimeout(() => { form1.querySelector('[name=checkbox]').click(); }, 10) }) it('should handle radio click events 1', function (done) { updater.enableEventProcessing(); let subject = updater.getSubject(); expect(subject).is.equal(proxyobserver.getSubject()); let expected = ['r1', 'r2', 'r1']; let clickTargets = ['r2', 'r1'] // here the notation with function is important, because the this pointer is set. proxyobserver.attachObserver(new Observer(function () { let e = expected.shift(); if (e === undefined && expected.length === 0) done(new Error('to many calls')); let v = this.getSubject()['radio']; if (v !== e) done(new Error(v + ' should ' + e)); if (expected.length === 0) { done(); } else { setTimeout(() => { document.getElementById(clickTargets.shift()).click(); }, 10) } })); setTimeout(() => { document.getElementById('r1').click(); }, 10) // no handler // bind setTimeout(() => { document.getElementById('rx').click(); }, 20) }) it('should handle select click events 2', function (done) { let selectElement = document.getElementById('select1'); updater.enableEventProcessing(); let subject = updater.getSubject(); expect(subject).is.equal(proxyobserver.getSubject()); let expected = ['value2', 'value1', 'value2']; // here the notation with function is important, because the this pointer is set. proxyobserver.attachObserver(new Observer(function () { let e = expected.shift(); if (e === undefined && expected.length === 0) done(new Error('to many calls')); let v = this.getSubject()['select']; if (v !== e) done(new Error(v + ' should ' + e)); if (expected.length === 0) { done(); } else { setTimeout(() => { selectElement.selectedIndex = selectElement.selectedIndex === 1 ? 0 : 1; selectElement.click(); }, 10) } })); setTimeout(() => { // set value and simulate click event for bubble selectElement.selectedIndex = 1; selectElement.click(); }, 20) }); it('should handle textarea events', function (done) { let textareaElement = document.getElementById('textarea'); updater.enableEventProcessing(); let subject = updater.getSubject(); expect(subject).is.equal(proxyobserver.getSubject()); let expected = ['testX', 'lorem ipsum', '']; let testValues = ["lorem ipsum", ""]; // here the notation with function is important, because the this pointer is set. proxyobserver.attachObserver(new Observer(function () { let e = expected.shift(); if (e === undefined && expected.length === 0) done(new Error('to many calls')); let v = this.getSubject()['textarea']; if (JSON.stringify(v) !== JSON.stringify(e)) done(new Error(JSON.stringify(v) + ' should ' + JSON.stringify(e))); if (expected.length === 0) { done(); } else { setTimeout(() => { textareaElement.value = testValues.shift(); textareaElement.click(); }, 10) } })); setTimeout(() => { // set value and simulate click event for bubble textareaElement.value = "testX"; textareaElement.click(); }, 20) }); it('should handle multiple select events', function (done) { let selectElement = document.getElementById('select2'); updater.enableEventProcessing(); let subject = updater.getSubject(); expect(subject).is.equal(proxyobserver.getSubject()); let expected = [ ['value1'], ['value2', 'value3', 'value4'], ['value1', 'value4'], ]; let testSelections = [ [false, true, true, true], [true, false, false, true], ] // here the notation with function is important, because the this pointer is set. proxyobserver.attachObserver(new Observer(function () { let e = expected.shift(); if (e === undefined && expected.length === 0) done(new Error('to many calls')); let v = this.getSubject()['multiselect']; if (JSON.stringify(v) !== JSON.stringify(e)) done(new Error(JSON.stringify(v) + ' should ' + JSON.stringify(e))); if (expected.length === 0) { done(); } else { setTimeout(() => { let v = testSelections.shift(); selectElement.options[0].selected = v[0]; selectElement.options[1].selected = v[1]; selectElement.options[2].selected = v[2]; selectElement.options[3].selected = v[3]; selectElement.click(); }, 10) } })); setTimeout(() => { selectElement.options[0].selected = true; selectElement.options[1].selected = false; selectElement.options[2].selected = false; selectElement.options[3].selected = false; selectElement.click(); }, 20) }); }); }) describe('Updater()', function () { beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html2; }) describe('Replace', function () { it('should add lower hello and HELLOyes!', function (done) { let element = document.getElementById('test1') let d = new Updater( element, { text: "HALLO" } ); d.setCallback('myformatter', function (a) { return a + 'yes!' }) d.run().then(() => { setTimeout(() => { expect(typeof d).is.equal('object'); expect(element).contain.html('<div data-monster-replace="path:text | tolower">hallo</div>'); expect(element).contain.html('<div data-monster-replace="path:text | call:myformatter">HALLOyes!</div>'); expect(element).contain.html('<div data-monster-replace="static:hello\\ ">hello </div>'); return done(); }, 100); }).catch( e => { done(new Error(e)) }) }); }); }); describe('Updater()', function () { beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html3; }) describe('Replace', function () { it('should ', function (done) { let element = document.getElementById('test1') let d = new Updater( element, { a: { b: [ {i: '0'}, ] } } ); d.run().then(() => { setTimeout(() => { expect(typeof d).is.equal('object'); expect(element).contain.html('<div data-monster-insert="myid path:a.b">'); expect(element).contain.html('<p data-monster-insert="myinnerid path:a.b" data-monster-insert-reference="myid-0">'); expect(element).contain.html('<span data-monster-replace="path:a.b.0 | tojson" data-monster-insert-reference="myinnerid-0">{"i":"0"}</span>'); done(); }, 100); }).catch( e => { done(new Error(e)) }) }); }); }); describe('Updater()', function () { describe('Attributes', function () { it('should change attributes', function (done) { let element = document.getElementById('test3') let text = document.getElementById('text') expect(text.value).to.be.equal(""); let radio = document.getElementById('radio') expect(radio.checked).to.be.false; let checkbox = document.getElementById('checkbox') expect(checkbox.checked).to.be.false; let select = document.getElementById('select') expect(select.selectedIndex).to.be.equal(0); let multiselect = document.getElementById('multiselect') expect(multiselect.selectedIndex).to.be.equal(-1); let textarea = document.getElementById('textarea') expect(textarea.value).to.be.equal(""); let d = new Updater( element, { a: { b: "div-class", c: "hello", text: "hello", radio: "true", textarea: "test", multiselect: ['value3', 'value4', 'other-value5'], select: "value2", checkbox: "true" } } ); d.run().then(() => { setTimeout(() => { expect(element).contain.html('<div data-monster-attributes="class path:a.b" class="div-class">'); expect(element).contain.html('<input data-monster-attributes="value path:a.c" id="input1" value="hello">'); expect(element).contain.html('<textarea name="textarea" id="textarea" data-monster-attributes="value path:a.textarea" value="test">'); expect(element).contain.html('<input data-monster-attributes="checked path:a.radio" type="radio" name="radio" value="r1" id="radio" checked="true">'); expect(text.value, 'text control').to.be.equal(d.getSubject()['a']['c']); expect(radio.checked, 'radio control').to.be.equal(true); expect(textarea.value, 'textarea control').to.be.equal(d.getSubject()['a']['textarea']); expect(select.selectedIndex, 'select control').to.be.equal(1); // [0=>other-value, 1=>value2] let multiselectSelectedOptions = []; for (const [index, obj] of Object.entries(multiselect.selectedOptions)) { multiselectSelectedOptions.push(obj.value); } expect(JSON.stringify(multiselectSelectedOptions), 'multiselect control').to.be.equal(JSON.stringify(d.getSubject()['a']['multiselect'])); expect(checkbox.checked, 'checkbox control').to.be.true; done(); }, 100); }).catch( e => { done(new Error(e)) }) }); }); }); describe('Get Attribute Pipe', function () { let id, mocks; beforeEach(() => { mocks = document.getElementById('mocks'); id = new ID('monster'); mocks.innerHTML = ` <div id="` + id + `" data-monster-replace="path:a | if:value:\\ "></div>` }) afterEach(() => { mocks.innerHTML = ""; }) it('should include space', function () { const div = document.getElementById(id.toString()) const pipe = div.getAttribute('data-monster-replace'); expect(pipe.length).to.be.equal(20); }); }); describe('manuel update', function () { let id, mocks; beforeEach(() => { mocks = document.getElementById('mocks'); id = new ID('monster').toString(); mocks.innerHTML = `<input id="` + id + `"data-monster-bind="path:myvalue">` }) afterEach(() => { mocks.innerHTML = ""; }) it('should get value', function () { document.getElementById(id).value = "hello"; const updater = new Updater(mocks); const subject = updater.getSubject(); expect(subject).to.not.have.property('myvalue'); updater.retrieve(); expect(subject).to.have.property('myvalue'); }); }); /** * https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/112 */ describe('Updater() 20220107', function () { beforeEach(() => { let mocks = document.getElementById('mocks'); // language=HTML mocks.innerHTML = ` <div id="container"> <div data-monster-replace="path:content"></div> </div> `; }) describe('Bugfix #112', function () { it('should add ', function (done) { let containerElement = document.getElementById('container'); let newElement = document.createElement('div'); newElement.innerHTML = 'yeah! <b>Test</b>!'; const containerHTML = containerElement.innerHTML; const newHTML = newElement.innerHTML; let d = new Updater( containerElement, { content: newElement } ); setTimeout(() => { d.run().then(() => { setTimeout(() => { try { expect(containerElement).contain.html('<div>yeah! <b>Test</b>!</div>'); } catch (e) { return done(e); } done() }, 100) }) }, 100) // d.setCallback('myformatter', function (a) { // return a + 'yes!' // }) // // setTimeout(() => { // d.run().then(() => { // // expect(typeof d).is.equal('object'); // expect(element).contain.html('<div data-monster-replace="path:text | tolower">hallo</div>'); // expect(element).contain.html('<div data-monster-replace="path:text | call:myformatter">HALLOyes!</div>'); // expect(element).contain.html('<div data-monster-replace="static:hello\\ ">hello </div>'); // // return done(); // }).catch( // e => { // done(new Error(e)) // }) // }, 100) }); }); }); });