// eslint-disable-next-line import/no-extraneous-dependencies
import { tmpl, domBackend, composedBackend, shadowBackend } from '../base/env'
import * as glassEasel from '../../src'
const testCases = (testBackend: glassEasel.GeneralBackendContext) => {
beforeAll(() => {
domBackend.onEvent(glassEasel.Event.triggerBackendEvent)
})
test('event listener filter', () => {
const customHandler = (a: number, b: number) => a + b
const template = tmpl(
`
module.exports = { customHandler: ${customHandler.toString()} }
`,
{},
{
C: (elem, evName, listener, final, mutated, capture, generalLvaluePath) => {
const hostMethodCaller = elem.ownerShadowRoot?.getHostNode().getMethodCaller()
expect(final).toBe(false)
expect(mutated).toBe(false)
expect(capture).toBe(false)
switch (evName) {
case 'customHandler': {
expect(generalLvaluePath).toStrictEqual([
glassEasel.template.GeneralLvaluePathPrefix.InlineScript,
'',
'w',
'customHandler',
])
break
}
default:
expect(generalLvaluePath).toBe(undefined)
}
return (event) => {
expect(event.getEventName()).toBe(evName)
switch (evName) {
case 'customEv': {
event.target = Object.assign(event.target, { id: 'b' })
return listener.apply(hostMethodCaller, [event])
}
case 'dropEvent': {
return undefined
}
case 'customHandler': {
const ret = (listener as any as typeof customHandler)(event.detail as number, 2)
expect(ret).toBe(3)
return undefined
}
default: {
return listener.apply(hostMethodCaller, [event])
}
}
}
},
},
)
let droppedEventCalled = false
const eventOrder: number[] = []
const def = glassEasel.registerElement({
template,
methods: {
f(ev: glassEasel.ShadowedEvent) {
expect(this).toBe(elem.getMethodCaller())
expect(ev.target.id).toBe('b')
eventOrder.push(1)
},
shouldBeDropped() {
droppedEventCalled = true
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const div = elem.getShadowRoot()!.getElementById('a')!
div.triggerEvent('customEv')
expect(eventOrder).toStrictEqual([1])
div.triggerEvent('dropEvent')
expect(droppedEventCalled).toBe(false)
div.triggerEvent('customHandler', 1)
})
test('catch bindings', () => {
const eventOrder: number[] = []
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evA() {
eventOrder.push(1)
},
evB() {
eventOrder.push(2)
},
evC() {
eventOrder.push(3)
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
c.triggerEvent('customEv', null, { bubbles: true })
expect(eventOrder).toStrictEqual([3, 2])
})
test('mut-bind bindings', () => {
const eventOrder: number[] = []
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evA() {
eventOrder.push(1)
},
evB() {
eventOrder.push(2)
},
evC() {
eventOrder.push(3)
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
c.triggerEvent('customEv', null, { bubbles: true })
expect(eventOrder).toStrictEqual([3, 1])
})
test('dynamic catch bindings', () => {
const eventOrder: number[] = []
const def = glassEasel.registerElement({
template: tmpl(`
`),
data: {
evBHandler: 'evB',
},
methods: {
evA() {
eventOrder.push(1)
},
evB() {
eventOrder.push(2)
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
c.triggerEvent('customEv', null, { bubbles: true })
expect(eventOrder).toStrictEqual([2])
eventOrder.length = 0
elem.setData({
evBHandler: '',
})
c.triggerEvent('customEv', null, { bubbles: true })
expect(eventOrder).toStrictEqual([1])
})
test('bubble cross shadow', () => {
const ops: string[] = []
const single = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
on0: () => ops.push('s0'),
on1: () => ops.push('s1'),
on2: () => ops.push('s2'),
on3: () => ops.push('s3'),
on0c: () => ops.push('s0c'),
on1c: () => ops.push('s1c'),
on2c: () => ops.push('s2c'),
on3c: () => ops.push('s3c'),
},
})
const multi = glassEasel.registerElement({
options: { multipleSlots: true },
template: tmpl(`
`),
methods: {
on0: () => ops.push('m0'),
on1: () => ops.push('m1'),
on2: () => ops.push('m2'),
on3: () => ops.push('m3'),
on0c: () => ops.push('m0c'),
on1c: () => ops.push('m1c'),
on2c: () => ops.push('m2c'),
on3c: () => ops.push('m3c'),
},
})
const dynamic = glassEasel.registerElement({
options: { dynamicSlots: true },
template: tmpl(`
`),
methods: {
on0: () => ops.push('d0'),
on1: () => ops.push('d1'),
on2: () => ops.push('d2'),
on3: () => ops.push('d3'),
on0c: () => ops.push('d0c'),
on1c: () => ops.push('d1c'),
on2c: () => ops.push('d2c'),
on3c: () => ops.push('d3c'),
},
})
const parent = glassEasel.registerElement({
using: {
single: single.general(),
multi: multi.general(),
dynamic: dynamic.general(),
},
template: tmpl(`
S
M
D
`),
methods: {
on0: () => ops.push('p0'),
on1: () => ops.push('p1'),
on2: () => ops.push('p2'),
on3: () => ops.push('p3'),
on4: () => ops.push('p4'),
on5: () => ops.push('p5'),
on0c: () => ops.push('p0c'),
on1c: () => ops.push('p1c'),
on2c: () => ops.push('p2c'),
on3c: () => ops.push('p3c'),
on4c: () => ops.push('p4c'),
on5c: () => ops.push('p5c'),
},
})
const parentElem = glassEasel.Component.createWithContext('root', parent, testBackend)
const singleElem = parentElem.$.s as glassEasel.Element
const multiElem = parentElem.$.m as glassEasel.Element
const dynamicElem = parentElem.$.d as glassEasel.Element
glassEasel.Event.triggerEvent(singleElem, 'customEv', null, {
bubbles: true,
composed: true,
capturePhase: true,
})
glassEasel.Event.triggerEvent(multiElem, 'customEv', null, {
bubbles: true,
composed: true,
capturePhase: true,
})
glassEasel.Event.triggerEvent(dynamicElem, 'customEv', null, {
bubbles: true,
composed: true,
capturePhase: true,
})
expect(ops).toEqual([
'p0c',
's0c',
's1c',
'p3c',
'p3',
's1',
's0',
'p0',
'p1c',
'm2c',
'm3c',
'p4c',
'p4',
'm3',
'm2',
'p1',
'p2c',
'd2c',
'd3c',
'p5c',
'p5',
'd3',
'd2',
'p2',
])
})
test('change property bindings', () => {
const abc = glassEasel.registerElement({
properties: { abc: String },
})
const def = glassEasel.registerElement({
using: { abc: abc.general() },
template: tmpl(`
exports.fA = function (newVal, oldVal, self, target) {
self._test = 789
target._test = newVal + ':' + (oldVal || '-')
}
`),
data: {
abc: 123,
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const a = elem.getShadowRoot()!.getElementById('a')!
expect((elem as unknown as { _test: number })._test).toBe(789)
expect((a as unknown as { _test: string })._test).toBe('123:-')
elem.setData({ abc: 456 })
expect((a as unknown as { _test: string })._test).toBe('456:123')
})
test('event function bindings', () => {
const abc = glassEasel.registerElement({
lifetimes: {
attached() {
this.triggerEvent('abc')
},
},
})
const def = glassEasel.registerElement({
using: { abc: abc.general() },
template: tmpl(`
exports.fA = function (ev) {
ev.target._test = 123
}
`),
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
glassEasel.Element.pretendAttached(elem)
const a = elem.getShadowRoot()!.getElementById('a')!
expect((a as unknown as { _test: string })._test).toBe(123)
})
test('event function bindings (with conditional expression)', () => {
const abc = glassEasel.registerElement({
lifetimes: {
attached() {
this.triggerEvent('abc')
},
},
})
const def = glassEasel.registerElement({
using: { abc: abc.general() },
template: tmpl(`
exports.fA = function (ev) {
ev.target._test = 123
}
exports.fB = function (ev) {
ev.target._test = 456
}
`),
data: {
cond: false,
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
glassEasel.Element.pretendAttached(elem)
const a = elem.getShadowRoot()!.getElementById('a')!
expect((a as unknown as { _test: string })._test).toBe(123)
elem.setData({ cond: true })
a.triggerEvent('abc')
expect((a as unknown as { _test: string })._test).toBe(456)
})
test('worklet directives', () => {
const triggered: string[] = []
const abc = glassEasel.registerElement({
lifetimes: {
workletChange(name: string, value: number) {
if (name === 'abc') {
expect(value).toBe('abc')
} else if (name === 'def') {
expect(value).toBe('456')
}
triggered.push(name)
},
},
})
const def = glassEasel.registerElement({
using: { abc: abc.general() },
template: tmpl(`
`),
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
glassEasel.Element.pretendAttached(elem)
expect(triggered).toStrictEqual(['abc', 'def'])
})
test('handle listener return', () => {
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evA() {
return 1
},
evB() {
return 2
},
evC() {
return 3
},
evCC() {
return 4
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
let eventOrder: number[] = []
c.triggerEvent('customEv', null, {
bubbles: true,
handleListenerReturn(ret) {
expect(typeof ret).toBe('number')
eventOrder.push(Number(ret))
},
})
expect(eventOrder).toStrictEqual([3, 4, 2])
eventOrder = []
c.triggerEvent('customEv', null, {
bubbles: true,
handleListenerReturn(ret) {
expect(typeof ret).toBe('number')
eventOrder.push(Number(ret))
return ret !== 4
},
})
expect(eventOrder).toStrictEqual([3, 4])
})
test('event phase', () => {
const eventPhase: [string, glassEasel.EventPhase][] = []
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evA(e: glassEasel.ShadowedEvent) {
eventPhase.push(['a', e.eventPhase])
},
evB(e: glassEasel.ShadowedEvent) {
eventPhase.push(['b', e.eventPhase])
},
evC(e: glassEasel.ShadowedEvent) {
eventPhase.push(['c', e.eventPhase])
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
const event = new glassEasel.Event(
'customEv',
{},
{ composed: true, capturePhase: true, bubbles: true },
)
expect(event.eventPhase).toBe(glassEasel.EventPhase.None)
c.dispatchEvent(event)
expect(event.eventPhase).toBe(glassEasel.EventPhase.None)
expect(eventPhase).toStrictEqual([
['b', glassEasel.EventPhase.CapturingPhase],
['c', glassEasel.EventPhase.BubblingPhase],
['a', glassEasel.EventPhase.BubblingPhase],
])
})
test('has listeners', () => {
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evB(e: glassEasel.ShadowedEvent) {
expect(e.hasListener()).toBe(true)
},
evC(e: glassEasel.ShadowedEvent) {
expect(e.hasListener()).toBe(true)
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
const a = elem.getShadowRoot()!.getElementById('a')!
const event = new glassEasel.Event('customEv', {}, { composed: true, capturePhase: true })
expect(event.hasListener()).toBe(false)
c.dispatchEvent(event)
expect(event.hasListener()).toBe(true)
const event1 = new glassEasel.Event('customEv', {}, { composed: true, capturePhase: true })
expect(event1.hasListener()).toBe(false)
a.dispatchEvent(event1)
expect(event1.hasListener()).toBe(false)
})
if (testBackend === domBackend) {
test('prevent default', () => {
const events: string[] = []
const def = glassEasel.registerElement({
template: tmpl(`
`),
methods: {
evA() {
events.push('a')
},
evB() {
events.push('b')
},
evC() {
events.push('c')
return false
},
},
})
const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend)
const c = elem.getShadowRoot()!.getElementById('c')!
const domElemC = c.getBackendElement() as unknown as HTMLDivElement
const event = new Event('custom', { bubbles: true, composed: true, cancelable: true })
domElemC.dispatchEvent(event)
expect(events).toStrictEqual(['b', 'c'])
expect(event.defaultPrevented).toBe(true)
})
test('stop propagation in native-rendering elements', () => {
let op: string[] = []
let breakpoints: string[] = []
const comp = glassEasel.registerElement({
options: {
externalComponent: true,
},
template: tmpl(`
`),
listeners: {
'this.customEv': 'evThis',
customEv: 'evShadowRoot',
},
methods: {
evThis() {
op.push('this')
return !breakpoints.includes('this')
},
evShadowRoot() {
op.push('shadowRoot')
return !breakpoints.includes('shadowRoot')
},
evA() {
op.push('a')
return !breakpoints.includes('a')
},
evB() {
op.push('b')
return !breakpoints.includes('b')
},
},
})
const root = glassEasel.registerElement({
using: {
comp,
},
template: tmpl(`
`),
methods: {
evComp() {
op.push('comp')
return !breakpoints.includes('comp')
},
evC() {
op.push('c')
return !breakpoints.includes('c')
},
},
})
const elem = glassEasel.Component.createWithContext('root', root.general(), testBackend)
const c = elem.$.c as glassEasel.Element
op = []
breakpoints = []
glassEasel.triggerEvent(
c,
'customEv',
{},
{ bubbles: true, composed: true, capturePhase: true },
)
expect(op).toStrictEqual(['c', 'b', 'a', 'shadowRoot', 'this', 'comp'])
op = []
breakpoints = ['shadowRoot']
glassEasel.triggerEvent(
c,
'customEv',
{},
{ bubbles: true, composed: true, capturePhase: true },
)
expect(op).toStrictEqual(['c', 'b', 'a', 'shadowRoot'])
op = []
breakpoints = ['b']
glassEasel.triggerEvent(
c,
'customEv',
{},
{ bubbles: true, composed: true, capturePhase: true },
)
expect(op).toStrictEqual(['c', 'b'])
op = []
breakpoints = ['c']
glassEasel.triggerEvent(
c,
'customEv',
{},
{ bubbles: true, composed: true, capturePhase: true },
)
expect(op).toStrictEqual(['c'])
})
}
}
describe('event bindings (DOM backend)', () => testCases(domBackend))
describe('event bindings (shadow backend)', () => testCases(shadowBackend))
describe('event bindings (composed backend)', () => testCases(composedBackend))