'use strict'; import * as chai from 'chai'; import {internalSymbol} from "../../../source/constants.mjs"; import {getDocument} from "../../../source/dom/util.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 = ` <div id="test1"> </div> `; let html2 = ` <input data-monster-bind="path:a" id="test2" data-monster-attributes="value path:a"> `; // defined in constants.mjs const updaterSymbolKey = "@schukai/monster/dom/custom-element@@options-updater-link" const updaterSymbolSymbol = Symbol.for(updaterSymbolKey); describe('DOM', function () { let CustomElement, registerCustomElement, TestComponent, document, TestComponent2, assignUpdaterToElement, addObjectWithUpdaterToElement; describe("assignUpdaterToElement", function () { before(function (done) { const options = {}; initJSDOM(options).then(() => { import("../../../source/dom/updater.mjs").then((yy) => { addObjectWithUpdaterToElement = yy['addObjectWithUpdaterToElement']; import("../../../source/dom/customelement.mjs").then((m) => { try { CustomElement = m['CustomElement']; assignUpdaterToElement = function (elements, object) { return addObjectWithUpdaterToElement.call(this, elements, updaterSymbolSymbol, object); } document = getDocument(); done() } catch (e) { done(e); } }).catch((e) => { done(e); }); }).catch((e) => { done(e); }); }); }) beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html2; }) afterEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = ""; }) /** * this test try to simulate the bug that was found in the assignUpdaterToElement function. * The bug was that the updater was not assigned to the element when the element was created. * * unfortunately, this test does not reproduce the bug. */ it("should assign an updater to an element", function (done) { let element = document.getElementById('test2'); expect(document.getElementById("mocks").innerHTML).to.equal(html2); const a = {a: 1}; const b = {b: 2}; const ap = new ProxyObserver(a); const bp = new ProxyObserver(b); const x = ap.getSubject() const y = bp.getSubject() const set = new Set(); set.add(element); assignUpdaterToElement.call(element, set, ap); assignUpdaterToElement.call(element, set, bp); expect(JSON.stringify(x)).to.equal('{"a":1}'); expect(JSON.stringify(y)).to.equal('{"b":2}'); const sy = updaterSymbolSymbol; let v = element.getAttribute("data-monster-objectlink"); expect(v).to.equal('Symbol(' + updaterSymbolKey + ')'); const updater = element[sy]; for (const v of updater) { for (const u of v) { u.run().then(() => { u.enableEventProcessing(); }); } } expect(updater).to.be.an.instanceof(Set); expect(updater).to.be.a("Set"); x.a = 3; bp.getSubject().b = 4; setTimeout(() => { let mockHTML = document.getElementById("mocks"); // html expexted: // <input data-monster-bind="path:a" id="test2" data-monster-attributes="value path:a" data-monster-objectlink="Symbol(@schukai/monster/dom/@@object-updater-link)" value="3"> expect(mockHTML.querySelector("#test2")).to.have.value('3') expect(mockHTML.querySelector("#test2")).to.have.attribute('data-monster-objectlink', 'Symbol(' + updaterSymbolKey + ')') //expect(mockHTML).to.have.html(resultHTML); expect(element.value).to.equal("3"); expect(JSON.stringify(ap.getRealSubject())).to.equal('{"a":3}'); expect(JSON.stringify(bp.getRealSubject())).to.equal('{"b":4}'); done() }, 50) }) }) describe('CustomElement()', function () { before(function (done) { initJSDOM({}).then(() => { import("../../../source/dom/customelement.mjs").then((m) => { try { CustomElement = m['CustomElement']; registerCustomElement = m['registerCustomElement']; TestComponent = class extends CustomElement { static getTag() { return "monster-testclass" } } registerCustomElement(TestComponent) TestComponent2 = class extends CustomElement { static getTag() { return "monster-testclass2" } /** * * @return {Object} */ get defaults() { return Object.assign({}, super.defaults, { demotest: undefined, templates: { main: '<h1></h1><article><p>test</p><div id="container"></div></article>' }, }) } } registerCustomElement(TestComponent2) document = getDocument(); done() } catch (e) { done(e); } }); }); }) beforeEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = html1; }) afterEach(() => { let mocks = document.getElementById('mocks'); mocks.innerHTML = ""; }) describe('CustomElement() with Config', function () { it('should read config from tag', function () { let mocks = document.getElementById('mocks'); mocks.innerHTML = ` <script id="config1" type="application/json"> { "demotest":1425 } </script> <monster-testclass2 id="thisisatest" data-monster-options-selector="#config1"> </monster-testclass2> `; let monster = document.getElementById('thisisatest'); expect(monster.getOption('demotest')).is.eql(1425); }); }); describe('create', function () { it('should return custom-element object', function () { let d = new TestComponent(); expect(typeof d).is.equal('object'); }); }); describe('connect empty element', function () { it('document should contain monster-testclass', function () { let d = document.createElement('monster-testclass'); document.getElementById('test1').appendChild(d); expect(document.getElementsByTagName('monster-testclass').length).is.equal(1); // no data-monster-objectlink="Symbol(monsterUpdater)" because it has nothing to update // but data-monster-error="Error: html is not set." expect(document.getElementById('test1')).contain.html('<monster-testclass data-monster-error="html is not set."></monster-testclass>'); }); }); describe('connect element with html', function () { it('document should contain monster-testclass2', function (done) { let d = document.createElement('monster-testclass2'); document.getElementById('test1').appendChild(d); // insert DOM run in extra process via setTimeout! setTimeout(function () { try { expect(document.getElementsByTagName('monster-testclass2').length).is.equal(1); expect(document.getElementsByTagName('monster-testclass2').item(0).shadowRoot.innerHTML).is.equal('<h1></h1><article><p>test</p><div id="container"></div></article>'); expect(document.getElementById('test1')).contain.html('<monster-testclass2 data-monster-objectlink="Symbol(' + updaterSymbolKey + ')"></monster-testclass2>'); return done(); } catch (e) { done(e); } }, 10); }); }); describe('Options change', function () { it('delegatesFocus should change from true to false', function () { let element = document.createElement('monster-testclass') const o = element[internalSymbol].realSubject; expect(Object.is(element[internalSymbol].realSubject, o)).to.be.true; expect(element[internalSymbol].realSubject.options.delegatesFocus).to.be.true; expect(element[internalSymbol].subject.options.delegatesFocus).to.be.true; expect(element.getOption('delegatesFocus')).to.be.true; expect(Object.is(element[internalSymbol].realSubject, o)).to.be.true; // element.setAttribute(ATTRIBUTE_OPTIONS, JSON.stringify({delegatesFocus: false})); // expect(Object.is(element[internalSymbol].realSubject, o)).to.be.true; // // expect(element.getOption('delegatesFocus')).to.be.false; // expect(element[internalSymbol].realSubject.options.delegatesFocus).to.be.false; // expect(Object.is(element[internalSymbol].realSubject, o)).to.be.true; }) }) describe('setOptions()', function () { [ ['shadowMode', 'x1'], ['templates.main', 'x2'], // is explicitly set to undefined ['delegatesFocus', 'x4'], ].forEach(function (data) { let key = data.shift() let newValue = data.shift() let text = key + ' should return ' + newValue; if (newValue !== undefined) { text = key + ' was not set, therefore default ' + newValue; } it(text, function () { let d = document.createElement('monster-testclass'); expect(d.getOption(key)).to.be.not.equal(newValue); let x = d.setOption(key, newValue); expect(d.getOption(key)).to.be.equal(newValue); }) }) }); describe('getOptions()', function () { [ ['shadowMode', 'open'], ['templates.main', undefined], // is explicitly set to undefined ['delegatesFocus', true], ['x.y.z', true, true], // x.y.z isnt set, defaultValue is used ['x', true, true] // x isnt set, defaultValue is used ].forEach(function (data) { let key = data.shift() let value = data.shift() let defaultValue = data.shift() let text = key + ' should return ' + value; if (defaultValue !== undefined) { text = key + ' was not set, therefore default ' + defaultValue; } it(text, function () { let d = document.createElement('monster-testclass'); let x = d.getOption(key, defaultValue); expect(x).to.be.equal(value); }) }) }) /** * @link https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/113 */ describe('Assign CSSStyle as Array with wrong type', function () { const htmlTAG = 'monster-testclass-x1'; let mocks, TestComponentX1; beforeEach(() => { mocks = document.getElementById('mocks'); mocks.innerHTML = html1; TestComponentX1 = class extends CustomElement { static getTag() { return htmlTAG } static getCSSStyleSheet() { return [true]; } /** * @return {Object} */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: '<h1>test</h1>' }, }) } } registerCustomElement(TestComponentX1) }) it(htmlTAG + " should throw Exception", function (done) { let d = document.createElement(htmlTAG); let div = document.getElementById('test1'); div.append(d); expect(div).contain.html('data-monster-error="value is not an instance of CSSStyleSheet"'); done(); }) }) /** * @link https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/113 */ describe('Assign CSSStyle as Array and CSSStylesheet', function () { const htmlTAG = 'monster-testclass-x113-2'; let mocks, TestComponentX113X2; beforeEach(() => { mocks = document.getElementById('mocks'); mocks.innerHTML = html1; TestComponentX113X2 = class extends CustomElement { static getTag() { return htmlTAG } /** * @return {Object} */ get defaults() { return Object.assign({}, super.defaults, { templates: {main: '<h1>test</h1>'}, }) } static getCSSStyleSheet() { const s = (new CSSStyleSheet()) s.insertRule('a { color : red}'); return [s]; } } registerCustomElement(TestComponentX113X2) }) it(htmlTAG + " should throw Exception 2", function (done) { let d = document.createElement(htmlTAG); let div = document.getElementById('test1'); div.append(d); expect(d.shadowRoot.innerHTML).is.eq('<h1>test</h1>'); done(); }) }) /** * @link https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/113 */ describe('Assign CSSStyle as Array and CSS as string', function () { const htmlTAG = 'monster-testclass-x113-21'; let mocks, TestComponentX113X22; beforeEach(() => { mocks = document.getElementById('mocks'); mocks.innerHTML = html1; TestComponentX113X22 = class extends CustomElement { static getTag() { return htmlTAG } /** * @return {Object} */ get defaults() { return Object.assign({}, super.defaults, { templates: {main: '<h1>test</h1>'}, }) } static getCSSStyleSheet() { return 'a { color:red }'; } } registerCustomElement(TestComponentX113X22) }) it(htmlTAG + " should eq <style>a { color:red }</style><h1>test</h1>", function (done) { let d = document.createElement(htmlTAG); let div = document.getElementById('test1'); div.append(d); expect(d.shadowRoot.innerHTML).is.eq('<style>a { color:red }</style><h1>test</h1>'); done(); }) }) /** * @link https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/113 */ describe('Assign CSSStyle as Array and CSS as string', function () { const htmlTAG = 'monster-testclass-x113-22'; let mocks, TestComponentX113X223; beforeEach(() => { mocks = document.getElementById('mocks'); mocks.innerHTML = html1; TestComponentX113X223 = class extends CustomElement { static getTag() { return htmlTAG } /** * @return {Object} */ get defaults() { return Object.assign({}, super.defaults, { templates: {main: '<h1>test</h1>'}, }) } static getCSSStyleSheet() { return ['a { color:red }']; } } registerCustomElement(TestComponentX113X223) }) it(htmlTAG + " should eq <style>a { color:red }</style><h1>test</h1>", function (done) { let d = document.createElement(htmlTAG); let div = document.getElementById('test1'); div.append(d); expect(d.shadowRoot.innerHTML).is.eq('<style>a { color:red }</style><h1>test</h1>'); done(); }) }) describe('hasNode()', function () { let mocks; beforeEach(() => { mocks = document.getElementById('mocks'); mocks.innerHTML = html1; }) it("hasNode monster-testclass should return ...", function () { let d = document.createElement('monster-testclass'); let p1 = document.createElement('p'); let t1 = document.createTextNode('test1'); p1.appendChild(t1); let p = document.createElement('div'); let t = document.createTextNode('test'); p.appendChild(p1); p.appendChild(t); d.appendChild(p); let div = document.getElementById('test1'); div.append(d); let n1 = document.createElement('p'); expect(d.hasNode(n1)).to.be.false; expect(d.hasNode(t)).to.be.true; expect(d.hasNode(p)).to.be.true; expect(d.hasNode(p1)).to.be.true; expect(d.hasNode(t1)).to.be.true; }) it("hasNode monster-testclass2 should return ...", function () { let d = document.createElement('monster-testclass2'); let p1 = document.createElement('p'); let t1 = document.createTextNode('test1'); p1.appendChild(t1); let p = document.createElement('div'); let t = document.createTextNode('test'); p.appendChild(p1); p.appendChild(t); let div = document.getElementById('test1'); div.append(d); let a = d.shadowRoot.getElementById('container'); d.shadowRoot.getElementById('container').appendChild(p); let n1 = document.createElement('p'); expect(d.hasNode(n1)).to.be.false; expect(d.hasNode(t)).to.be.true; expect(d.hasNode(p)).to.be.true; expect(d.hasNode(p1)).to.be.true; expect(d.hasNode(t1)).to.be.true; }) }) }); })