/* eslint-disable no-restricted-syntax */ import { tmpl, domBackend } from '../base/env' import * as glassEasel from '../../src' const componentSpace = new glassEasel.ComponentSpace() componentSpace.updateComponentOptions({ writeFieldsToNode: true, }) componentSpace.defineComponent({ is: '', }) describe('MutationObserver', () => { const childDef = componentSpace.defineComponent({ properties: { p: String, }, externalClasses: ['e-class'], template: tmpl(''), }) const parentDef = componentSpace.defineComponent({ options: { dynamicSlots: true }, using: { child: childDef.general() }, template: tmpl(`
text `), }) it('observe id changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const child2 = child1.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true }) child1.id = 'a' expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'id', }) child1.class = 'b' expect(callEv.pop()?.target).toBe(child1) child1.class = 'b' expect(callEv.pop()).toBeUndefined() child2.id = 'b' expect(callEv.pop()).toBeUndefined() observer.disconnect() child1.id = 'c' expect(callEv.pop()).toBeUndefined() }) it('observe class changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true }) child1.class = 'a' expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'class', }) child1.class = 'b' expect(callEv.pop()?.target).toBe(child1) child1.class = 'b' expect(callEv.pop()).toBeUndefined() }) it('observe style changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true }) child1.style = 'color: red' expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'style', }) child1.style = 'color: blue' expect(callEv.pop()?.target).toBe(child1) child1.style = 'color: blue' expect(callEv.pop()).toBeUndefined() }) it('observe slot changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true }) child1.slot = 'a' expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'slot', }) child1.slot = 'b' expect(callEv.pop()?.target).toBe(child1) child1.slot = 'b' expect(callEv.pop()).toBeUndefined() }) it('observe slot name changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const child2 = child1.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child2, { properties: true }) glassEasel.Element.setSlotName(child2, 'a') expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'name', }) glassEasel.Element.setSlotName(child2, '') expect(callEv.pop()?.target).toBe(child2) glassEasel.Element.setSlotName(child2, '') expect(callEv.pop()).toBeUndefined() }) it('observe attribute changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const div = child1.childNodes[1]!.asNativeNode()! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(div, { properties: true }) div.setAttribute('hidden', '') expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'attribute', attributeName: 'hidden', }) div.setAttribute('hidden', '') expect(callEv.pop()?.target).toBe(div) div.removeAttribute('hidden') expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'attribute', attributeName: 'hidden', }) div.removeAttribute('hidden') expect(callEv.pop()?.target).toBe(div) }) it('observe component-property changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true }) child1.setData({ p: 'a' }) expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'component-property', propertyName: 'p', }) child1.setData({ p: 'b' }) expect(callEv.pop()?.target).toBe(child1) child1.setData({ p: 'b' }) expect(callEv.pop()).toBeUndefined() }) it('observe slot-value changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const slot = shadowRoot.childNodes[1]!.asVirtualNode()! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(slot, { properties: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(slot, { properties: 'all' }) shadowRoot.setDynamicSlotHandler( () => {}, () => {}, () => {}, () => {}, ) shadowRoot.replaceSlotValue(slot, 'someValue', 'a') expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'slot-value', propertyName: 'someValue', }) shadowRoot.replaceSlotValue(slot, 'someValue', 'b') expect(callEv.pop()?.target).toBe(slot) shadowRoot.replaceSlotValue(slot, 'someValue', 'b') expect(callEv.pop()).toBeUndefined() }) it('observe dataset changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(child1, { properties: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: 'all' }) child1.setDataset('someData', 1) expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'dataset', attributeName: 'data:someData', }) child1.setDataset('someData', 1) expect(callEv.pop()?.target).toBe(child1) }) it('observe mark changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(child1, { properties: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: 'all' }) child1.setMark('someData', 1) expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'mark', attributeName: 'mark:someData', }) child1.setMark('someData', 1) expect(callEv.pop()?.target).toBe(child1) }) it('observe external-classes changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(child1, { properties: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: 'all' }) child1.setExternalClass('p-class', 'a') expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'external-class', attributeName: 'p-class', }) child1.setExternalClass('p-class', 'a') expect(callEv.pop()?.target).toBe(child1) }) it('observe subtree property changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const child1 = elem.getShadowRoot()!.childNodes[0]!.asInstanceOf(childDef)! const child2 = child1.childNodes[0]!.asInstanceOf(childDef)! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { properties: true, subtree: true }) child2.id = 'a' expect(callEv.pop()).toMatchObject({ type: 'properties', nameType: 'basic', attributeName: 'id', }) child2.id = 'b' expect(callEv.pop()?.target).toBe(child2) observer.disconnect() child1.id = 'c' expect(callEv.pop()).toBeUndefined() }) it('observe childList changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const child1 = shadowRoot.childNodes[0]!.asInstanceOf(childDef)! const child2 = child1.childNodes[0]!.asInstanceOf(childDef)! child2.cancelDestroyBackendElementOnDetach() const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(shadowRoot, { childList: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(child1, { childList: true, subtree: true }) child1.removeChild(child2) expect(callEv.pop()).toMatchObject({ type: 'childList', }) child1.appendChild(child2) expect(callEv.pop()?.target).toBe(child1) child1.removeChild(child2) expect( (callEv.pop() as glassEasel.mutationObserver.MutationObserverChildEvent).removedNodes![0], ).toBe(child2) child1.appendChild(child2) expect( (callEv.pop() as glassEasel.mutationObserver.MutationObserverChildEvent).addedNodes![0], ).toBe(child2) child2.destroyBackendElementOnDetach() child1.removeChild(child2) }) it('observe subtree childList changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const child1 = shadowRoot.childNodes[0]!.asInstanceOf(childDef)! const child2 = child1.childNodes[0]!.asInstanceOf(childDef)! child2.cancelDestroyBackendElementOnDetach() const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(shadowRoot, { childList: true, subtree: true }) child1.removeChild(child2) expect(callEv.pop()).toMatchObject({ type: 'childList', }) child1.appendChild(child2) expect(callEv.pop()?.target).toBe(child1) child1.removeChild(child2) expect( (callEv.pop() as glassEasel.mutationObserver.MutationObserverChildEvent).removedNodes![0], ).toBe(child2) child1.appendChild(child2) expect( (callEv.pop() as glassEasel.mutationObserver.MutationObserverChildEvent).addedNodes![0], ).toBe(child2) child2.destroyBackendElementOnDetach() child1.removeChild(child2) }) it('observe text content changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const child1 = shadowRoot.childNodes[0]!.asInstanceOf(childDef)! const textNode = child1.childNodes[2]!.asTextNode()! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer1 = glassEasel.MutationObserver.create(() => { throw new Error() }) observer1.observe(shadowRoot, { characterData: true }) const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(textNode, { characterData: true }) textNode.textContent = 'abc' expect(callEv.pop()).toMatchObject({ type: 'characterData', }) textNode.textContent = 'def' expect(callEv.pop()?.target).toBe(textNode) textNode.textContent = 'def' expect(callEv.pop()).toBeUndefined() }) it('observe subtree text content changes', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const child1 = shadowRoot.childNodes[0]!.asInstanceOf(childDef)! const textNode = child1.childNodes[2]!.asTextNode()! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(shadowRoot, { characterData: true, subtree: true }) textNode.textContent = 'abc' expect(callEv.pop()).toMatchObject({ type: 'characterData', }) textNode.textContent = 'def' expect(callEv.pop()?.target).toBe(textNode) textNode.textContent = 'def' expect(callEv.pop()).toBeUndefined() }) it('observer native node attach status', () => { const elem = glassEasel.Component.createWithContext('root', parentDef, domBackend) const shadowRoot = elem.getShadowRoot()! const child1 = shadowRoot.childNodes[0]!.asInstanceOf(childDef)! const div = child1.childNodes[1]!.asNativeNode()! const callEv = [] as glassEasel.mutationObserver.MutationObserverEvent[] const observer = glassEasel.MutationObserver.create((ev) => { callEv.push(ev) }) observer.observe(div, { attachStatus: true }) glassEasel.Element.pretendAttached(elem) expect(callEv.pop()).toMatchObject({ type: 'attachStatus', status: 'attached', }) glassEasel.Element.pretendDetached(elem) expect(callEv.pop()).toMatchObject({ type: 'attachStatus', status: 'detached', }) }) })