import * as glassEasel from 'glass-easel'
import { tmpl } from './base/env'
import { MiniProgramEnv } from '../src'
import { StyleIsolation } from '../src/types'
const domHtml = (elem: glassEasel.Element): string => {
const domElem = elem.getBackendElement() as unknown as Element
return domElem.innerHTML
}
describe('selector query', () => {
test('select single component (without custom export)', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('child/comp', {
component: true,
styleIsolation: StyleIsolation.Shared,
})
codeSpace.addCompiledTemplate('child/comp', tmpl('{{a}}'))
// eslint-disable-next-line arrow-body-style
const childDef = codeSpace.componentEnv('child/comp', ({ Component }) => {
return Component()
.lifetime('attached', function () {
// eslint-disable-next-line no-use-before-define
const owner = this.selectOwnerComponent(selfDef)!
owner.update()
expect(this.selectOwnerComponent()).toBe(owner)
// eslint-disable-next-line no-use-before-define
expect(owner.general().asInstanceOf(selfDef)).toBe(owner)
expect(owner.general().asInstanceOf(childDef)).toBe(null)
expect(this.selectOwnerComponent(childDef)).toBe(null)
})
.data(() => ({
a: 123,
}))
.register()
})
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child: '/child/comp',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
// eslint-disable-next-line arrow-body-style
const selfDef = codeSpace.componentEnv('path/to/comp', ({ Component }) => {
return Component()
.methods({
update() {
// eslint-disable-next-line
this.selectComponent('#c1').setData({ a: 456 })
const c2 = this.selectComponent('#c2', childDef)!
expect(c2.is).toBe('child/comp')
expect(c2.id).toBe('c2')
expect(c2.dataset).toStrictEqual({ a: 'A' })
c2.setData({ a: 789 })
expect(this.selectComponent('#c2', selfDef)).toBe(null)
expect(this.selectComponent('#c3')).toBe(null)
expect(this.selectOwnerComponent()).toBe(null)
},
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
expect(domHtml(root.getComponent())).toBe(
'456789
',
)
})
test('select single component (with custom export)', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('child1/comp', {
component: true,
styleIsolation: StyleIsolation.Shared,
})
codeSpace.addComponentStaticConfig('child2/comp', { component: false })
codeSpace.addCompiledTemplate('child1/comp', tmpl('{{a}}'))
codeSpace.addCompiledTemplate('child2/comp', tmpl('{{a}}'))
const child1Def = codeSpace.componentEnv('child1/comp', ({ Component }) =>
Component()
.data(() => ({
a: 123,
}))
.export(function () {
return {
id: 1,
set: (d: { a: number }) => {
this.setData(d)
},
}
})
.register(),
)
const child2Def = codeSpace.componentEnv('child2/comp', ({ Component }) =>
Component()
.definition({
data: () => ({
a: 123,
}),
export() {
return {
id: 2,
set: (d: { a: number }) => {
this.setData(d)
},
}
},
})
.register(),
)
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child1: '/child1/comp',
child2: '/child2/comp',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
const selfDef = Component()
.lifetime('attached', function () {
expect(this.selectComponent('#d')).toBe(null)
expect(this.selectComponent('#c1', child2Def)).toBe(null)
expect(this.selectComponent('#c1', selfDef)).toBe(null)
expect(this.selectComponent('#c2', child1Def)).toBe(null)
expect(this.selectComponent('#c2', selfDef)).toBe(null)
const c1any = this.selectComponent('#c1') as { id: number }
const c1 = this.selectComponent('#c1', child1Def)!
const c2any = this.selectComponent('#c2') as { id: number }
const c2 = this.selectComponent('#c2', child2Def)!
expect(c1any.id).toBe(c1.id)
expect(c2any.id).toBe(c2.id)
c1.set({ a: 456 })
c2.set({ a: 789 })
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
expect(domHtml(root.getComponent())).toBe('456789
')
})
test('select single component (with custom export on behavior)', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('child1/comp', {
component: true,
styleIsolation: StyleIsolation.Shared,
})
codeSpace.addComponentStaticConfig('child2/comp', { component: false })
codeSpace.addCompiledTemplate('child1/comp', tmpl('{{a}}'))
codeSpace.addCompiledTemplate('child2/comp', tmpl('{{a}}'))
codeSpace.addCompiledTemplate('child3/comp', tmpl('{{a}}'))
codeSpace.addCompiledTemplate('child4/comp', tmpl('{{a}}'))
// eslint-disable-next-line arrow-body-style
const child1Def = codeSpace.componentEnv('child1/comp', ({ Behavior, Component }) => {
const beh = Behavior()
.data(() => ({
a: 123,
}))
.export(function () {
return {
id: 1,
set: (d: { a: number }) => {
this.setData(d)
},
}
})
.register()
return Component().behavior(beh).register()
})
const child2Def = codeSpace.componentEnv('child2/comp', ({ Behavior, Component }) => {
const beh = Behavior()
.definition({
data: () => ({
a: 123,
}),
export() {
return {
id: 2,
set: (d: { a: number }) => {
this.setData(d)
},
}
},
})
.register()
return Component().behavior(beh).register()
})
const child3Def = codeSpace.componentEnv('child3/comp', ({ Behavior, Component }) => {
const beh = Behavior()
.definition({
data: () => ({
a: 123,
}),
export() {
return {
id: 3,
set: (d: { a: number }) => {
this.setData(d)
},
}
},
})
.register()
return Component()
.definition<
/* TNewData */ { a: number },
/* TNewProperty */ Record,
/* TNewMethod */ Record,
/* TNewComponentExport */ { id: number; set: (d: { a: number }) => void }
>({
behaviors: [beh],
})
.register()
})
const child4Def = codeSpace.componentEnv('child4/comp', ({ Behavior, Component }) => {
const beh = Behavior({
data: {
a: 123,
},
export() {
return {
id: 4,
set: (d: { a: number }) => {
this.setData(d)
},
}
},
})
return Component().behavior(beh).register()
})
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child1: '/child1/comp',
child2: '/child2/comp',
child3: '/child3/comp',
child4: '/child4/comp',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
const selfDef = Component()
.lifetime('attached', function () {
expect(this.selectComponent('#d')).toBe(null)
expect(this.selectComponent('#c1', child2Def)).toBe(null)
expect(this.selectComponent('#c1', selfDef)).toBe(null)
expect(this.selectComponent('#c2', child1Def)).toBe(null)
expect(this.selectComponent('#c2', selfDef)).toBe(null)
const c1any = this.selectComponent('#c1') as { id: number }
const c1 = this.selectComponent('#c1', child1Def)!
const c2any = this.selectComponent('#c2') as { id: number }
const c2 = this.selectComponent('#c2', child2Def)!
const c3any = this.selectComponent('#c3') as { id: number }
const c3 = this.selectComponent('#c3', child3Def)!
const c4any = this.selectComponent('#c4') as { id: number }
const c4 = this.selectComponent('#c4', child4Def)!
expect(c1any.id).toBe(c1.id)
expect(c2any.id).toBe(c2.id)
expect(c3any.id).toBe(c3.id)
expect(c4any.id).toBe(c4.id)
c1.set({ a: 456 })
c2.set({ a: 789 })
c3.set({ a: 654 })
c4.set({ a: 987 })
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
expect(domHtml(root.getComponent())).toBe(
'456789654987
',
)
})
test('select all components', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('child/comp', {
component: true,
styleIsolation: StyleIsolation.Shared,
})
codeSpace.addCompiledTemplate('child/comp', tmpl('{{a}}'))
// eslint-disable-next-line arrow-body-style
const childDef = codeSpace.componentEnv('child/comp', ({ Component }) => {
return Component()
.data(() => ({
a: 123,
}))
.register()
})
codeSpace.addComponentStaticConfig('child/comp2', {
component: true,
styleIsolation: StyleIsolation.Shared,
})
codeSpace.addCompiledTemplate('child/comp2', tmpl('{{a}}'))
// eslint-disable-next-line arrow-body-style
codeSpace.componentEnv('child/comp2', ({ Component }) => {
Component()
.data(() => ({
a: 456,
}))
.register()
})
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child: '/child/comp',
'child-b': '/child/comp2',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
const selfDef = Component()
.lifetime('attached', function () {
// eslint-disable-next-line
this.selectAllComponents('.c').forEach((item, i) => item.setData({ a: i }))
this.selectAllComponents('.c', childDef).forEach((item, i) => item.setData({ a: i }))
expect(this.selectAllComponents('.c', childDef).length).toBe(2)
expect(this.selectAllComponents('.c', selfDef)).toStrictEqual([])
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
expect(domHtml(root.getComponent())).toBe(
'012
',
)
})
test('query single element info', () =>
new Promise((resolve) => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
const resList = [] as any[]
codeSpace.addComponentStaticConfig('child/comp', {
component: true,
})
codeSpace.addCompiledTemplate('child/comp', tmpl('{{a}}'))
codeSpace.componentEnv('child/comp', ({ Component }) => {
Component().property('p', String).register()
})
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child: '/child/comp',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
Component()
.lifetime('attached', function () {
this.createSelectorQuery()
.select('.invalid')
.fields({})
.select('.s')
.boundingClientRect((res) => {
expect(res.id).toBe('bb')
expect(res.dataset).toStrictEqual({ b: 1 })
expect(typeof res.left).toBe('number')
expect(typeof res.top).toBe('number')
expect(typeof res.right).toBe('number')
expect(typeof res.bottom).toBe('number')
expect(typeof res.width).toBe('number')
expect(typeof res.height).toBe('number')
resList.push(res)
})
.select('#bb')
.scrollOffset((res) => {
expect(res.id).toBe('bb')
expect(res.dataset).toStrictEqual({ b: 1 })
expect(typeof res.scrollLeft).toBe('number')
expect(typeof res.scrollTop).toBe('number')
expect(typeof res.scrollWidth).toBe('number')
expect(typeof res.scrollHeight).toBe('number')
resList.push(res)
})
.select('#cc')
.fields(
{
mark: true,
rect: true,
size: true,
scrollOffset: true,
properties: ['p'],
},
(res) => {
expect(res.id).toBe(undefined)
expect(res.dataset).toBe(undefined)
expect(res.mark).toStrictEqual({ a: 'a2', c: 'c2' })
expect(typeof res.left).toBe('number')
expect(typeof res.top).toBe('number')
expect(typeof res.right).toBe('number')
expect(typeof res.bottom).toBe('number')
expect(typeof res.width).toBe('number')
expect(typeof res.height).toBe('number')
expect(typeof res.scrollLeft).toBe('number')
expect(typeof res.scrollTop).toBe('number')
expect(typeof res.scrollWidth).toBe('number')
expect(typeof res.scrollHeight).toBe('number')
expect(res.p).toBe('123')
resList.push(res)
},
)
.in(this.selectComponent('#cc'))
.select('#cc')
.boundingClientRect((res) => {
expect(res).toBe(null)
})
.exec((r) => {
r.shift()
r.pop()
expect(r).toStrictEqual(resList)
this.createSelectorQuery().exec(() => {
resolve(resList)
})
})
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
}).then((resList: any[]) => {
expect(resList.length).toBe(3)
return undefined
}))
test('query multiple element info', () =>
new Promise((resolve) => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('child/comp', {
component: true,
})
codeSpace.addCompiledTemplate('child/comp', tmpl('{{a}}'))
codeSpace.componentEnv('child/comp', ({ Component }) => {
Component()
.options({
propertyPassingDeepCopy: glassEasel.DeepCopyKind.None,
})
.property('p', {
type: Array,
value: [1, 2, 3],
})
.register()
})
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {
child: '/child/comp',
},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
Component()
.lifetime('attached', function () {
this.createSelectorQuery()
.selectAll('.s')
.fields(
{
mark: true,
properties: ['p'],
},
(res) => {
expect(res).toStrictEqual([
{
mark: {
a: 'a2',
b: 'b2',
},
},
{
mark: {
a: 'a2',
c: 'c2',
},
p: [1, 2, 3],
},
])
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(res[1]!.p).toBe(this.selectComponent('#cc').data.p)
resolve(undefined)
},
)
.exec()
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
}))
test('query viewport info', () =>
new Promise((resolve) => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('path/to/comp', {})
codeSpace.addCompiledTemplate('path/to/comp', tmpl(''))
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
Component()
.lifetime('attached', function () {
this.createSelectorQuery()
.selectViewport()
.fields(
{
id: true,
dataset: true,
mark: true,
rect: true,
size: true,
scrollOffset: true,
properties: ['p'],
},
(res) => {
expect(res.id).toBe('')
expect(res.dataset).toStrictEqual({})
expect(res.mark).toStrictEqual({})
expect(typeof res.left).toBe('number')
expect(typeof res.top).toBe('number')
expect(typeof res.right).toBe('number')
expect(typeof res.bottom).toBe('number')
expect(typeof res.width).toBe('number')
expect(typeof res.height).toBe('number')
expect(typeof res.scrollLeft).toBe('number')
expect(typeof res.scrollTop).toBe('number')
expect(typeof res.scrollWidth).toBe('number')
expect(typeof res.scrollHeight).toBe('number')
expect(res.p).toBe(undefined)
resolve(undefined)
},
)
.exec()
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
}))
test('query context', () =>
new Promise((resolve) => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('path/to/comp', {})
codeSpace.addCompiledTemplate('path/to/comp', tmpl(''))
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
Component()
.lifetime('attached', function () {
this.createSelectorQuery()
.selectViewport()
.context((res) => {
expect(res.context).toBeUndefined()
resolve(undefined)
})
.exec()
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
}))
})
describe('intersection observer', () => {
test('create intersection observer', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
// eslint-disable-next-line arrow-body-style
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
return Component()
.lifetime('attached', function () {
const o1 = this.createIntersectionObserver().relativeToViewport()
o1.observe('#a', () => {
/* empty */
})
const o2 = this.createIntersectionObserver({
thresholds: [1],
initialRatio: 0,
observeAll: true,
}).relativeTo('#a')
o2.observe('#b', () => {
/* empty */
})
o1.disconnect()
o2.disconnect()
})
.register()
})
const backendContext = new glassEasel.EmptyComposedBackendContext()
const ab = env.associateBackend(backendContext)
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
})
})
describe('resize observer', () => {
test('create resize observer', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
// eslint-disable-next-line arrow-body-style
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
return Component()
.lifetime('attached', function () {
const o1 = this.createResizeObserver()
o1.observe('#a', () => {
/* empty */
})
const o2 = this.createResizeObserver({ observeAll: true })
o2.borderBox().observe('#a', () => {
/* empty */
})
o1.disconnect()
o2.disconnect()
})
.register()
})
const backendContext = new glassEasel.EmptyComposedBackendContext()
const ab = env.associateBackend(backendContext)
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
})
})
describe('media query observer', () => {
test('create media query observer', () => {
const env = new MiniProgramEnv()
const codeSpace = env.createCodeSpace('', true)
codeSpace.addComponentStaticConfig('path/to/comp', {
usingComponents: {},
})
codeSpace.addCompiledTemplate(
'path/to/comp',
tmpl(`
`),
)
// eslint-disable-next-line arrow-body-style
codeSpace.componentEnv('path/to/comp', ({ Component }) => {
return Component()
.lifetime('attached', function () {
const o = this.createMediaQueryObserver()
o.observe({ orientation: 'landscape' }, () => {
/* empty */
})
o.disconnect()
})
.register()
})
const ab = env.associateBackend()
const root = ab.createRoot('body', codeSpace, 'path/to/comp')
glassEasel.Element.pretendAttached(root.getComponent())
expect(domHtml(root.getComponent())).toBe('')
})
})