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:X 456:Y ')
comp0.setData({ propA: 'X0', propB: 1230 })
expect(domHtml(elem)).toBe('1230:X0 456: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:X0 4560: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('123 456 ')
comp0.setData({ propA: 321 })
expect(domHtml(elem)).toBe('321 456 ')
comp1.setData({ propA: 654 })
expect(domHtml(elem)).toBe('321 654 ')
})
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}}
`),
data: {
s: 'initial',
},
})
const elem = glassEasel.Component.createWithContext('root', def, domBackend)
glassEasel.Element.pretendAttached(elem)
expect(domHtml(elem)).toBe('initial
')
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
')
})
})