import { tmpl, domBackend } from '../base/env' import * as glassEasel from '../../src' const domHtml = (elem: glassEasel.Element): string => { const domElem = elem.getBackendElement() as unknown as Element return domElem.innerHTML } describe('model value binding', () => { test('model value binding on single data path', () => { let updateCount = 0 const subComp = glassEasel.registerElement({ template: tmpl(`
`), properties: { propA: Number, }, observers: { propA() { updateCount += 1 }, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` {{a.b[c]}} `), data: { a: { b: [10, 100], }, c: 0, }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp = elem.getShadowRoot()!.getElementById('comp')! as glassEasel.GeneralComponent expect(domHtml(elem)).toBe('
10
') expect(updateCount).toBe(1) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(10) comp.setData({ propA: 20 }) expect(domHtml(elem)).toBe('
20
') expect(updateCount).toBe(3) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(20) expect(elem.data.a).toEqual({ b: [20, 100] }) elem.setData({ 'a.b[0]': 30 }) expect(domHtml(elem)).toBe('
30
') expect(updateCount).toBe(4) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(30) elem.setData({ 'a.b[1]': 200 }) expect(updateCount).toBe(4) elem.setData({ c: 1 }) expect(domHtml(elem)).toBe('
200
') expect(updateCount).toBe(5) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(200) comp.setData({ propA: 300 }) expect(domHtml(elem)).toBe('
300
') expect(updateCount).toBe(7) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(300) expect(elem.data.a).toEqual({ b: [30, 300] }) }) test('model value binding on conditional expression', () => { const subComp = glassEasel.registerElement({ template: tmpl(`
`), properties: { propA: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` `), data: { cond: false, cond2: false, a: 12, b: 34, subA: { f: 21 }, subB: { f: 43 }, }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp = elem.getShadowRoot()!.getElementById('comp')! as glassEasel.GeneralComponent const comp2 = elem.getShadowRoot()!.getElementById('comp2')! as glassEasel.GeneralComponent expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(12) expect(comp2.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(21) comp.setData({ propA: 56 }) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(56) expect(elem.data.a).toBe(56) expect(elem.data.b).toBe(34) comp2.setData({ propA: 65 }) expect(comp2.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(65) expect(elem.data.subA.f).toBe(65) expect(elem.data.subB.f).toBe(43) elem.setData({ cond: true }) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(34) expect(elem.data.a).toBe(56) expect(elem.data.b).toBe(34) expect(comp2.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(43) expect(elem.data.subA.f).toBe(65) expect(elem.data.subB.f).toBe(43) comp.setData({ propA: 78 }) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(78) expect(elem.data.a).toBe(56) expect(elem.data.b).toBe(78) comp2.setData({ propA: 87 }) expect(comp2.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(87) expect(elem.data.subA.f).toBe(65) expect(elem.data.subB.f).toBe(87) elem.setData({ cond2: true }) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(9) expect(elem.data.a).toBe(56) expect(elem.data.b).toBe(78) comp.setData({ propA: 999 }) expect(comp.getShadowRoot()!.getElementById('a')!.dataset.a).toBe(999) expect(elem.data.a).toBe(56) expect(elem.data.b).toBe(78) }) test('model value binding for items', () => { const subComp = glassEasel.registerElement({ template: tmpl('{{propB}}:{{propA}}'), properties: { propA: String, propB: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` `), data: { list: [ { a: 'X', b: 123, }, { a: 'Y', b: 456, }, ], }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp0 = elem.getShadowRoot()!.getElementById('comp0')! as glassEasel.GeneralComponent const comp1 = elem.getShadowRoot()!.getElementById('comp1')! as glassEasel.GeneralComponent expect(domHtml(elem)).toBe('123:X456:Y') comp0.setData({ propA: 'X0', propB: 1230 }) expect(domHtml(elem)).toBe('1230:X0456:Y') expect(elem.data.list).toEqual([ { a: 'X0', b: 1230 }, { a: 'Y', b: 456 }, ]) comp1.setData({ propA: 'Y0', propB: 4560 }) expect(domHtml(elem)).toBe('1230:X04560:Y0') expect(elem.data.list).toEqual([ { a: 'X0', b: 1230 }, { a: 'Y0', b: 4560 }, ]) elem.setData({ list: [{ a: 'Z', b: 789 }] }) expect(domHtml(elem)).toBe('789:Z') }) test('model value binding for let variables', () => { const subComp = glassEasel.registerElement({ template: tmpl('{{propB}}:{{propA}}'), properties: { propA: String, propB: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` `), data: { list: [ { a: 'X', b: 123, }, ], }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp = elem.getShadowRoot()!.getElementById('comp')! as glassEasel.GeneralComponent expect(domHtml(elem)).toBe('123:X') comp.setData({ propA: 'X0', propB: 1230 }) expect(domHtml(elem)).toBe('1230:X0') expect(elem.data.list).toEqual([{ a: 'X0', b: 1230 }]) expect(elem.data.list).toEqual([{ a: 'X0', b: 1230 }]) elem.setData({ list: [{ a: 'Z', b: 789 }] }) expect(domHtml(elem)).toBe('789:Z') }) test('invalid model value binding for items', () => { const subComp = glassEasel.registerElement({ template: tmpl('{{propA}}'), properties: { propA: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` `), data: { list: [{ a: [123] }], }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp = elem.getShadowRoot()!.getElementById('comp')! as glassEasel.GeneralComponent expect(domHtml(elem)).toBe('123') comp.setData({ propA: 456 }) expect(domHtml(elem)).toBe('456') expect(elem.data.list).toEqual([{ a: [123] }]) }) test('invalidate model value binding for non-lvalue', () => { const subComp = glassEasel.registerElement({ template: tmpl('{{propA}}'), properties: { propA: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subComp.general(), }, template: tmpl(` `), data: {}, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const comp0 = elem.getShadowRoot()!.getElementById('comp0')! as glassEasel.GeneralComponent const comp1 = elem.getShadowRoot()!.getElementById('comp1')! as glassEasel.GeneralComponent expect(domHtml(elem)).toBe('123456') comp0.setData({ propA: 321 }) expect(domHtml(elem)).toBe('321456') comp1.setData({ propA: 654 }) expect(domHtml(elem)).toBe('321654') }) test('nested model value bindings', () => { const subCompA = glassEasel.registerElement({ template: tmpl(`
{{propA}}
`), properties: { propA: Number, }, }) const subCompB = glassEasel.registerElement({ using: { comp: subCompA.general(), }, template: tmpl(` `), properties: { propB: Number, }, }) const def = glassEasel.registerElement({ using: { comp: subCompB.general(), }, template: tmpl(` `), data: { b: 123, }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) const compB = elem.getShadowRoot()!.getElementById('comp')!.asInstanceOf(subCompB)! const compA = compB.getShadowRoot()!.getElementById('comp')!.asInstanceOf(subCompA)! expect(domHtml(elem)).toBe('
123
') compA.setData({ propA: 45 }) expect(domHtml(elem)).toBe('
45
') expect(elem.data.b).toBe(45) expect(compB.data.propB).toBe(45) expect(compA.data.propA).toBe(45) compB.setData({ propB: 67 }) expect(domHtml(elem)).toBe('
67
') expect(elem.data.b).toBe(67) expect(compB.data.propB).toBe(67) expect(compA.data.propA).toBe(67) elem.setData({ b: 89 }) expect(domHtml(elem)).toBe('
89
') expect(elem.data.b).toBe(89) expect(compB.data.propB).toBe(89) expect(compA.data.propA).toBe(89) }) test('model value binding on native node (DOM input)', () => { const def = glassEasel.registerElement({ template: tmpl(`
{{s}}
') const input = elem.getShadowRoot()!.getElementById('a')!.$$ as unknown as HTMLInputElement input.value = 'changed' const ev = new Event('input') as InputEvent input.dispatchEvent(ev) expect(domHtml(elem)).toBe('
changed
') }) test('model value binding on native node (DOM checkbox)', () => { const def = glassEasel.registerElement({ template: tmpl(`
{{v}}
`), data: { v: false, }, }) const elem = glassEasel.Component.createWithContext('root', def, domBackend) glassEasel.Element.pretendAttached(elem) expect(domHtml(elem)).toBe('
false
') const input = elem.getShadowRoot()!.getElementById('a')!.$$ as unknown as HTMLInputElement input.checked = true const ev = new Event('change') as InputEvent input.dispatchEvent(ev) expect(domHtml(elem)).toBe('
true
') }) })