11import { 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 r e g e x p $ /
162+ @use ( Attrable )
110163 class RegExpAttrTest extends HTMLElement {
111- @attr foo = / ^ a r e g e x p $ /
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 r e g e x p $ /
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