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('
') }) })