Skip to content

Commit 44a50eb

Browse files
committed
Pull in attr test from use-delegates
1 parent ae48275 commit 44a50eb

1 file changed

Lines changed: 166 additions & 64 deletions

File tree

test/attr.ts

Lines changed: 166 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import {expect, fixture, html} from '@open-wc/testing'
2-
import {controller} from '../src/controller.js'
3-
import {attr} from '../src/attr.js'
2+
import {attr, Attrable} from '../src/attr.js'
3+
import {use} from '../src/use.js'
44

5-
describe('Attr', () => {
6-
@controller
5+
describe('Attrable', () => {
6+
@use(Attrable)
77
class InitializeAttrTest extends HTMLElement {
88
@attr foo = 'hello'
99
bar = 1
10+
11+
getCount = 0
12+
setCount = 0
13+
#baz = 'world'
14+
get baz() {
15+
this.getCount += 1
16+
return this.#baz
17+
}
18+
@attr set baz(value: string) {
19+
this.setCount += 1
20+
this.#baz = value
21+
}
1022
}
23+
window.customElements.define('initialize-attr-test', InitializeAttrTest)
1124

1225
let instance
1326
beforeEach(async () => {
@@ -18,134 +31,223 @@ describe('Attr', () => {
1831
document.createElement('initialize-attr-test')
1932
})
2033

21-
it('marks attrs as observedAttributes', () => {
22-
expect(InitializeAttrTest.observedAttributes).to.eql(['data-foo'])
34+
it('does not alter field values from their initial value', () => {
35+
expect(instance).to.have.property('foo', 'hello')
36+
expect(instance).to.have.property('bar', 1)
37+
expect(instance).to.have.property('baz', 'world')
2338
})
2439

25-
it('creates a getter/setter pair for each given attr name', () => {
26-
expect(instance.foo).to.equal('hello')
27-
expect(instance).to.have.ownPropertyDescriptor('foo')
40+
it('reflects the initial value as an attribute, if not present', () => {
41+
expect(instance).to.have.attribute('data-foo', 'hello')
42+
expect(instance).to.not.have.attribute('data-bar')
43+
expect(instance).to.have.attribute('data-baz', 'world')
2844
})
2945

30-
it('sets the attribute to a previously defined value on the key', () => {
31-
expect(instance.foo).to.equal('hello')
32-
expect(instance.getAttributeNames()).to.include('data-foo')
33-
expect(instance.getAttribute('data-foo')).to.equal('hello')
46+
it('prioritises the value in the attribute over the property', async () => {
47+
instance = await fixture(html`<initialize-attr-test data-foo="goodbye" data-baz="universe" />`)
48+
expect(instance).to.have.property('foo', 'goodbye')
49+
expect(instance).to.have.attribute('data-foo', 'goodbye')
50+
expect(instance).to.have.property('baz', 'universe')
51+
expect(instance).to.have.attribute('data-baz', 'universe')
3452
})
3553

36-
it('reflects the `data-*` attribute name of the given key', () => {
37-
expect(instance.foo).to.equal('hello')
38-
instance.foo = 'bar'
39-
expect(instance.getAttributeNames()).to.include('data-foo')
40-
expect(instance.getAttribute('data-foo')).to.equal('bar')
41-
instance.setAttribute('data-foo', 'baz')
42-
expect(instance.foo).to.equal('baz')
54+
it('changes the property when the attribute changes', async () => {
55+
instance.setAttribute('data-foo', 'goodbye')
56+
await Promise.resolve()
57+
expect(instance).to.have.property('foo', 'goodbye')
58+
instance.setAttribute('data-baz', 'universe')
59+
await Promise.resolve()
60+
expect(instance).to.have.property('baz', 'universe')
4361
})
4462

45-
it('sets the attribute to a previously defined value on the key', () => {
46-
instance.foo = 'hello'
47-
expect(instance.foo).to.equal('hello')
48-
expect(instance.getAttributeNames()).to.include('data-foo')
49-
expect(instance.getAttribute('data-foo')).to.equal('hello')
63+
it('resets to the default value when the attribute is removed', async () => {
64+
instance.setAttribute('data-foo', 'goodbye')
65+
expect(instance).to.have.property('foo', 'goodbye')
66+
instance.removeAttribute('data-foo')
67+
await Promise.resolve()
68+
expect(instance).to.have.property('foo', 'hello')
5069
})
5170

52-
it('prioritises the value in the attribute over the property', async () => {
53-
instance = await fixture(html`<initialize-attr-test data-foo="goodbye" />`)
54-
expect(instance.foo).to.equal('goodbye')
55-
expect(instance.getAttributeNames()).to.include('data-foo')
56-
expect(instance.getAttribute('data-foo')).to.equal('goodbye')
71+
it('changes the attribute when the property changes', () => {
72+
instance.foo = 'goodbye'
73+
expect(instance).to.have.attribute('data-foo', 'goodbye')
74+
instance.baz = 'universe'
75+
expect(instance).to.have.attribute('data-baz', 'universe')
76+
})
77+
78+
it('calls underlying get/set', async () => {
79+
instance.getCount = 0
80+
instance.setCount = 0
81+
instance.baz
82+
expect(instance).to.have.property('getCount', 1)
83+
expect(instance).to.have.property('setCount', 0)
84+
instance.baz = 2
85+
expect(instance).to.have.property('getCount', 1)
86+
expect(instance).to.have.property('setCount', 1)
87+
})
88+
89+
it('does not overly eagerly call get/set on attribute change', async () => {
90+
instance.getCount = 0
91+
instance.setCount = 0
92+
instance.setAttribute('data-baz', 'one')
93+
instance.setAttribute('data-baz', 'one')
94+
instance.setAttribute('data-baz', 'one')
95+
instance.setAttribute('data-baz', 'one')
96+
await Promise.resolve()
97+
expect(instance).to.have.property('getCount', 0)
98+
expect(instance).to.have.property('setCount', 4)
5799
})
58100

59101
describe('types', () => {
60102
it('infers number types from property and casts as number always', async () => {
61-
@controller
103+
@use(Attrable)
62104
class NumberAttrTest extends HTMLElement {
63105
@attr foo = 1
64106
}
65-
expect(NumberAttrTest).to.have.property('observedAttributes').include('data-foo')
107+
window.customElements.define('number-attr-test', NumberAttrTest)
66108
instance = await fixture(html`<number-attr-test />`)
67-
expect(instance.foo).to.equal(1)
68-
expect(instance.getAttributeNames()).to.include('data-foo')
69-
expect(instance.getAttribute('data-foo')).to.equal('1')
109+
110+
expect(instance).to.have.property('foo', 1)
111+
expect(instance).to.have.attribute('data-foo', '1')
70112
instance.setAttribute('data-foo', '7')
71-
expect(instance.foo).to.equal(7)
113+
await Promise.resolve()
114+
expect(instance).to.have.property('foo', 7)
72115
instance.setAttribute('data-foo', '-3.14')
73-
expect(instance.foo).to.equal(-3.14)
116+
await Promise.resolve()
117+
expect(instance).to.have.property('foo', -3.14)
74118
instance.setAttribute('data-foo', 'Not a Number')
75-
expect(Number.isNaN(instance.foo)).to.equal(true)
76-
instance.removeAttribute('data-foo')
77-
expect(instance.foo).to.equal(0)
119+
await Promise.resolve()
120+
expect(instance).to.have.property('foo').satisfy(Number.isNaN)
78121
instance.foo = 3.14
79122
expect(instance.getAttribute('data-foo')).to.equal('3.14')
123+
instance.removeAttribute('data-foo')
124+
await Promise.resolve()
125+
expect(instance).to.have.property('foo', 1)
80126
})
81127

82128
it('infers boolean types from property and uses has/toggleAttribute', async () => {
83-
@controller
129+
@use(Attrable)
84130
class BooleanAttrTest extends HTMLElement {
85131
@attr foo = false
86132
}
87-
expect(BooleanAttrTest).to.have.property('observedAttributes').include('data-foo')
133+
window.customElements.define('boolean-attr-test', BooleanAttrTest)
134+
88135
instance = await fixture(html`<boolean-attr-test />`)
89-
expect(instance.foo).to.equal(false)
90-
expect(instance.getAttributeNames()).to.not.include('data-foo')
91-
expect(instance.getAttribute('data-foo')).to.equal(null)
136+
137+
expect(instance).to.have.property('foo', false)
138+
expect(instance).to.not.have.attribute('data-foo')
92139
instance.setAttribute('data-foo', '7')
93-
expect(instance.foo).to.equal(true)
140+
await Promise.resolve()
141+
expect(instance).to.have.property('foo', true)
94142
instance.setAttribute('data-foo', 'hello')
95-
expect(instance.foo).to.equal(true)
143+
await Promise.resolve()
144+
expect(instance).to.have.property('foo', true)
96145
instance.setAttribute('data-foo', 'false')
97-
expect(instance.foo).to.equal(true)
146+
await Promise.resolve()
147+
expect(instance).to.have.property('foo', true)
98148
instance.removeAttribute('data-foo')
99-
expect(instance.foo).to.equal(false)
100-
instance.foo = '1'
101-
expect(instance.foo).to.equal(true)
102-
expect(instance.getAttributeNames()).to.include('data-foo')
103-
expect(instance.getAttribute('data-foo')).to.equal('')
149+
await Promise.resolve()
150+
expect(instance).to.have.property('foo', false)
151+
instance.foo = true
152+
expect(instance).to.have.attribute('data-foo', '')
104153
instance.foo = false
105-
expect(instance.getAttributeNames()).to.not.include('data-foo')
154+
expect(instance).to.not.have.attribute('data-foo')
155+
instance.removeAttribute('data-foo')
156+
await Promise.resolve()
157+
expect(instance).to.have.property('foo', false)
106158
})
107159

108160
it('defaults to inferring string type for non-boolean non-number types', async () => {
109-
@controller
161+
const regexp = /^a regexp$/
162+
@use(Attrable)
110163
class RegExpAttrTest extends HTMLElement {
111-
@attr foo = /^a regexp$/
164+
@attr foo = regexp
112165
}
113-
expect(RegExpAttrTest).to.have.property('observedAttributes').include('data-foo')
166+
window.customElements.define('reg-exp-attr-test', RegExpAttrTest)
114167
instance = await fixture(html`<reg-exp-attr-test />`)
115-
expect(instance.foo).to.equal('/^a regexp$/')
116-
expect(instance.getAttributeNames()).to.include('data-foo')
117-
expect(instance.getAttribute('data-foo')).to.equal('/^a regexp$/')
168+
169+
expect(instance).to.have.property('foo', '/^a regexp$/')
170+
expect(instance).to.have.attribute('data-foo', '/^a regexp$/')
171+
instance.setAttribute('data-foo', '/^another$/')
172+
await Promise.resolve()
173+
expect(instance).to.have.property('foo', '/^another$/')
174+
instance.removeAttribute('data-foo')
175+
await Promise.resolve()
176+
expect(instance).to.have.property('foo', regexp)
177+
})
178+
179+
it('defers to custom set logic if present', async () => {
180+
const regexp = /^a regexp$/
181+
@use(Attrable)
182+
class RegExpCastAttrTest extends HTMLElement {
183+
#reg = regexp
184+
@attr
185+
get foo() {
186+
return this.#reg
187+
}
188+
set foo(value) {
189+
this.#reg = value instanceof RegExp ? value : new RegExp(String(value).replace(/^\/|\/$/g, ''))
190+
}
191+
}
192+
window.customElements.define('reg-exp-cast-attr-test', RegExpCastAttrTest)
193+
instance = await fixture(html`<reg-exp-cast-attr-test />`)
194+
195+
expect(instance).to.have.property('foo', regexp)
196+
expect(instance).to.have.attribute('data-foo', '/^a regexp$/')
197+
instance.setAttribute('data-foo', '/^another$/')
198+
await Promise.resolve()
199+
expect(instance).to.have.property('foo').a('regexp').property('source', '^another$')
200+
})
201+
202+
it('avoids infinite loops', async () => {
203+
@use(Attrable)
204+
class LoopAttrTest extends HTMLElement {
205+
count = 0
206+
@attr
207+
get foo() {
208+
return ++this.count
209+
}
210+
set foo(value) {
211+
this.count += 1
212+
}
213+
}
214+
window.customElements.define('loop-attr-test', LoopAttrTest)
215+
instance = await fixture(html`<loop-attr-test />`)
216+
217+
expect(instance).to.have.property('foo')
218+
instance.foo = 1
219+
instance.setAttribute('data-foo', '2')
220+
instance.foo = 3
221+
instance.setAttribute('data-foo', '4')
118222
})
119223
})
120224

121225
describe('naming', () => {
122-
@controller
226+
@use(Attrable)
123227
class NamingAttrTest extends HTMLElement {
124228
@attr fooBarBazBing = 'a'
125229
@attr URLBar = 'b'
126230
@attr ClipX = 'c'
127231
}
232+
window.customElements.define('naming-attr-test', NamingAttrTest)
128233

129234
beforeEach(async () => {
130235
instance = await fixture(html`<naming-attr-test />`)
131236
})
132237

133238
it('converts camel cased property names to their HTML dasherized equivalents', async () => {
134-
expect(NamingAttrTest).to.have.property('observedAttributes').include('data-foo-bar-baz-bing')
135239
expect(instance.fooBarBazBing).to.equal('a')
136240
instance.fooBarBazBing = 'bar'
137241
expect(instance.getAttributeNames()).to.include('data-foo-bar-baz-bing')
138242
})
139243

140244
it('will intuitively dasherize acryonyms', async () => {
141-
expect(NamingAttrTest).to.have.property('observedAttributes').include('data-url-bar')
142245
expect(instance.URLBar).to.equal('b')
143246
instance.URLBar = 'bar'
144247
expect(instance.getAttributeNames()).to.include('data-url-bar')
145248
})
146249

147250
it('dasherizes cap suffixed names correctly', async () => {
148-
expect(NamingAttrTest).to.have.property('observedAttributes').include('data-clip-x')
149251
expect(instance.ClipX).to.equal('c')
150252
instance.ClipX = 'bar'
151253
expect(instance.getAttributeNames()).to.include('data-clip-x')

0 commit comments

Comments
 (0)