UNPKG

x-widget

Version:

Adds the ability to define reusable Widgets (WebComponents) using Alpinejs.

247 lines (205 loc) 7.09 kB
import { expect } from '@esm-bundle/chai/esm/chai.js' import plugin from '../src/index.mjs' import { xWidgetData } from '../src/x-widget-data.mjs' import Alpine from 'alpinejs' const waitUntil = (predicate, timeout = 10000) => new Promise((resolve, reject) => { setTimeout(() => reject(new Error('timeout')), timeout) const waitId = setInterval(() => { const result = predicate() if (result) { clearInterval(waitId) resolve(result) } }, 1) }) const waitForEl = (selector) => waitUntil(() => document.querySelector(selector)) // so html gets formatted in literals in vscode const html = String.raw describe('x-widget-data', () => { before(() => { document.body.setAttribute('x-data', '') Alpine.plugin(plugin) Alpine.data('xWidget', xWidgetData.bind(Alpine)) Alpine.start() }) beforeEach(() => (document.body.innerHTML = '')) const tplHtml = `<template x-widget="x-c"> <div class="inner" x-data="xWidget({show: true, firstName: 'Default'})($el, $data)"> <template x-if="show"><div>SHOW</div></template> <div class="t" x-text="JSON.stringify(show)"></div> <span x-text="firstName"></span> </div> </template>` it('uses default if not given', async () => { document.body.innerHTML = html` ${tplHtml} <x-c></x-c> ` const c = await waitForEl('.inner') await new Promise((resolve) => setTimeout(resolve, 10)) expect(c.innerText).to.contain('SHOW') }) it('supports passing "false"to bool', async () => { document.body.innerHTML = html` ${tplHtml} <x-c show="false"></x-c> ` const c = await waitForEl('x-c') expect(c.innerText).not.to.contain('SHOW') }) it('supports using attribute as boolean', async () => { document.body.innerHTML = html` ${tplHtml} <x-c show></x-c> ` const c = await waitForEl('x-c') await new Promise((r) => requestAnimationFrame(r)) expect(c.innerText).to.contain('SHOW') }) it('supports binding a prop with a scalar value', async () => { document.body.innerHTML = html` ${tplHtml} <x-c x-prop:show="true"></x-c> ` const c = await waitForEl('x-c') await new Promise((r) => requestAnimationFrame(r)) expect(c.innerText).to.contain('SHOW') }) it.skip('supports refs when lhs is complex', async () => { document.body.innerHTML = html` <div x-data="{y: 'test', names: {test: 'Al'}}" ${tplHtml} <x-c x-prop:show="names[y]"></x-c> </div> ` const c = await waitForEl('x-c') expect(c.innerText).to.contain('SHOW') }) it('supports binding a prop with an expression from parent scope', async () => { document.body.innerHTML = html` ${tplHtml} <div id="root" x-data="{rootShow: true}"> <x-c x-prop:show="rootShow"></x-c> </div> ` const rootEl = await waitForEl('#root') const c = await waitForEl('x-c') await new Promise((r) => requestAnimationFrame(r)) expect(c.innerText).to.contain('SHOW') Alpine.evaluate(rootEl, 'rootShow = false') await new Promise((r) => setTimeout(r, 0)) expect(Alpine.evaluate(c, 'show')).to.equal(false) expect(c.innerText).not.to.contain('SHOW') const cTest = await waitForEl('x-c .t') // test reverse direction. x-props two way bind Alpine.evaluate(cTest, 'show = true') await new Promise((r) => setTimeout(r, 1)) expect(Alpine.evaluate(rootEl, 'rootShow')).to.be.true }) it('supports binding an attribute with a scalar value', async () => { document.body.innerHTML = html` ${tplHtml} <div id="root" x-data="{rootShow: true}"> <x-c id="a" x-bind:show="true"></x-c> <x-c id="b" x-bind:show="false"></x-c> </div> ` const cA = await waitForEl('#a') await new Promise((r) => requestAnimationFrame(r)) expect(cA.innerText).to.contain('SHOW') const cB = await waitForEl('#b') expect(cB.innerText).not.to.contain('SHOW') }) it('supports binding attribute value with an expression from parent scope', async () => { document.body.innerHTML = html` ${tplHtml} <div id="root" x-data="{rootShow: true}"> <x-c x-bind:show="rootShow ? 'true': 'false'"></x-c> </div> ` const rootEl = await waitForEl('#root') const c = await waitForEl('x-c') await new Promise((r) => requestAnimationFrame(r)) expect(c.innerText).to.contain('SHOW') Alpine.evaluate(rootEl, 'rootShow=false') await new Promise((r) => setTimeout(r, 3)) expect(c.innerText).not.to.contain('SHOW') const c7Test = await waitForEl('x-c .t') // test reverse direction attribute bindings don't two way bind Alpine.evaluate(c7Test, 'show = true') await new Promise((r) => setTimeout(r, 1)) expect(Alpine.evaluate(rootEl, 'rootShow')).to.be.false }) it('supports methods on data', async () => { document.body.innerHTML = html` <template x-widget="x-action"> <div x-data="xWidget({ show: false, toggle() { this.show = !this.show } })($el, $data)" > <button @click="toggle">Toggle</button> <div x-text="show ? 'SHOW':''"></div> </div> </template> <x-action></x-action> ` const xAction = await waitForEl('x-action') expect(xAction.innerText).not.to.contain('SHOW') const button = await waitForEl('button') button.click() await new Promise((r) => setTimeout(r, 1)) expect(xAction.innerText).to.contain('SHOW') }) it('supports getters and setters', async () => { document.body.innerHTML = html` <template x-widget="x-getset"> <div id="inner" x-data="xWidget({ tests: [], get tested() { return this.tests.length > 0 }, set tested(newValue) { if (newValue === false) { this.tested = [] } }, test() { this.tests = [...this.tests, Date.now()] } })($el, $data)" > <button @click="tested=false">Untest</button> <button @click="test">Test</button> <div x-text="tested ? 'Tested':'Untested'"></div> </div> </template> <x-getset></x-getset> ` const xGetSet = await waitForEl('x-getset') const xGetSetInner = await waitForEl('x-getset #inner') await new Promise((r) => setTimeout(r, 1)) expect(xGetSet.innerText).to.contain('Untested') Alpine.evaluate(xGetSetInner, 'test()') await new Promise((r) => setTimeout(r, 100)) expect(xGetSet.innerText).to.contain('Tested') }) it('supports binding a camelcase prop', async () => { document.body.innerHTML = html` ${tplHtml} <div id="root" x-data="{ name: 'John'}"> <x-c x-prop:first-name="name"></x-c> </div> ` const c = await waitForEl('x-c') await new Promise((r) => setTimeout(r, 100)) expect(c.innerText).to.contain('John') }) })