@@ -155,6 +155,355 @@ describe('DOMPropertyOperations', () => {
155
155
// Regression test for https://github.com/facebook/react/issues/6119
156
156
expect ( container . firstChild . hasAttribute ( 'value' ) ) . toBe ( false ) ;
157
157
} ) ;
158
+
159
+ // @gate enableCustomElementPropertySupport
160
+ it ( 'custom element custom events lowercase' , ( ) => {
161
+ const oncustomevent = jest . fn ( ) ;
162
+ function Test ( ) {
163
+ return < my-custom-element oncustomevent = { oncustomevent } /> ;
164
+ }
165
+ const container = document . createElement ( 'div' ) ;
166
+ ReactDOM . render ( < Test /> , container ) ;
167
+ container
168
+ . querySelector ( 'my-custom-element' )
169
+ . dispatchEvent ( new Event ( 'customevent' ) ) ;
170
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
171
+ } ) ;
172
+
173
+ // @gate enableCustomElementPropertySupport
174
+ it ( 'custom element custom events uppercase' , ( ) => {
175
+ const oncustomevent = jest . fn ( ) ;
176
+ function Test ( ) {
177
+ return < my-custom-element onCustomevent = { oncustomevent } /> ;
178
+ }
179
+ const container = document . createElement ( 'div' ) ;
180
+ ReactDOM . render ( < Test /> , container ) ;
181
+ container
182
+ . querySelector ( 'my-custom-element' )
183
+ . dispatchEvent ( new Event ( 'Customevent' ) ) ;
184
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
185
+ } ) ;
186
+
187
+ // @gate enableCustomElementPropertySupport
188
+ it ( 'custom element custom event with dash in name' , ( ) => {
189
+ const oncustomevent = jest . fn ( ) ;
190
+ function Test ( ) {
191
+ return < my-custom-element oncustom-event = { oncustomevent } /> ;
192
+ }
193
+ const container = document . createElement ( 'div' ) ;
194
+ ReactDOM . render ( < Test /> , container ) ;
195
+ container
196
+ . querySelector ( 'my-custom-element' )
197
+ . dispatchEvent ( new Event ( 'custom-event' ) ) ;
198
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
199
+ } ) ;
200
+
201
+ // @gate enableCustomElementPropertySupport
202
+ it ( 'custom element remove event handler' , ( ) => {
203
+ const oncustomevent = jest . fn ( ) ;
204
+ function Test ( props ) {
205
+ return < my-custom-element oncustomevent = { props . handler } /> ;
206
+ }
207
+
208
+ const container = document . createElement ( 'div' ) ;
209
+ ReactDOM . render ( < Test handler = { oncustomevent } /> , container ) ;
210
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
211
+ customElement . dispatchEvent ( new Event ( 'customevent' ) ) ;
212
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
213
+
214
+ ReactDOM . render ( < Test handler = { false } /> , container ) ;
215
+ // Make sure that the second render didn't create a new element. We want
216
+ // to make sure removeEventListener actually gets called on the same element.
217
+ expect ( customElement ) . toBe ( customElement ) ;
218
+ customElement . dispatchEvent ( new Event ( 'customevent' ) ) ;
219
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
220
+
221
+ ReactDOM . render ( < Test handler = { oncustomevent } /> , container ) ;
222
+ customElement . dispatchEvent ( new Event ( 'customevent' ) ) ;
223
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 2 ) ;
224
+
225
+ const oncustomevent2 = jest . fn ( ) ;
226
+ ReactDOM . render ( < Test handler = { oncustomevent2 } /> , container ) ;
227
+ customElement . dispatchEvent ( new Event ( 'customevent' ) ) ;
228
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 2 ) ;
229
+ expect ( oncustomevent2 ) . toHaveBeenCalledTimes ( 1 ) ;
230
+ } ) ;
231
+
232
+ it ( 'custom elements shouldnt have non-functions for on* attributes treated as event listeners' , ( ) => {
233
+ const container = document . createElement ( 'div' ) ;
234
+ ReactDOM . render (
235
+ < my-custom-element
236
+ onstring = { 'hello' }
237
+ onobj = { { hello : 'world' } }
238
+ onarray = { [ 'one' , 'two' ] }
239
+ ontrue = { true }
240
+ onfalse = { false }
241
+ /> ,
242
+ container ,
243
+ ) ;
244
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
245
+ expect ( customElement . getAttribute ( 'onstring' ) ) . toBe ( 'hello' ) ;
246
+ expect ( customElement . getAttribute ( 'onobj' ) ) . toBe ( '[object Object]' ) ;
247
+ expect ( customElement . getAttribute ( 'onarray' ) ) . toBe ( 'one,two' ) ;
248
+ expect ( customElement . getAttribute ( 'ontrue' ) ) . toBe ( 'true' ) ;
249
+ expect ( customElement . getAttribute ( 'onfalse' ) ) . toBe ( 'false' ) ;
250
+
251
+ // Dispatch the corresponding event names to make sure that nothing crashes.
252
+ customElement . dispatchEvent ( new Event ( 'string' ) ) ;
253
+ customElement . dispatchEvent ( new Event ( 'obj' ) ) ;
254
+ customElement . dispatchEvent ( new Event ( 'array' ) ) ;
255
+ customElement . dispatchEvent ( new Event ( 'true' ) ) ;
256
+ customElement . dispatchEvent ( new Event ( 'false' ) ) ;
257
+ } ) ;
258
+
259
+ it ( 'custom elements should still have onClick treated like regular elements' , ( ) => {
260
+ let syntheticClickEvent = null ;
261
+ const syntheticEventHandler = jest . fn (
262
+ event => ( syntheticClickEvent = event ) ,
263
+ ) ;
264
+ let nativeClickEvent = null ;
265
+ const nativeEventHandler = jest . fn ( event => ( nativeClickEvent = event ) ) ;
266
+ function Test ( ) {
267
+ return < my-custom-element onClick = { syntheticEventHandler } /> ;
268
+ }
269
+
270
+ const container = document . createElement ( 'div' ) ;
271
+ document . body . appendChild ( container ) ;
272
+ ReactDOM . render ( < Test /> , container ) ;
273
+
274
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
275
+ customElement . onclick = nativeEventHandler ;
276
+ container . querySelector ( 'my-custom-element' ) . click ( ) ;
277
+
278
+ expect ( nativeEventHandler ) . toHaveBeenCalledTimes ( 1 ) ;
279
+ expect ( syntheticEventHandler ) . toHaveBeenCalledTimes ( 1 ) ;
280
+ expect ( syntheticClickEvent . nativeEvent ) . toBe ( nativeClickEvent ) ;
281
+ } ) ;
282
+
283
+ // @gate enableCustomElementPropertySupport
284
+ it ( 'custom elements should allow custom events with capture event listeners' , ( ) => {
285
+ const oncustomeventCapture = jest . fn ( ) ;
286
+ const oncustomevent = jest . fn ( ) ;
287
+ function Test ( ) {
288
+ return (
289
+ < my-custom-element
290
+ oncustomeventCapture = { oncustomeventCapture }
291
+ oncustomevent = { oncustomevent } >
292
+ < div />
293
+ </ my-custom-element >
294
+ ) ;
295
+ }
296
+ const container = document . createElement ( 'div' ) ;
297
+ ReactDOM . render ( < Test /> , container ) ;
298
+ container
299
+ . querySelector ( 'my-custom-element > div' )
300
+ . dispatchEvent ( new Event ( 'customevent' , { bubbles : false } ) ) ;
301
+ expect ( oncustomeventCapture ) . toHaveBeenCalledTimes ( 1 ) ;
302
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 0 ) ;
303
+ } ) ;
304
+
305
+ it ( 'innerHTML should not work on custom elements' , ( ) => {
306
+ const container = document . createElement ( 'div' ) ;
307
+ ReactDOM . render ( < my-custom-element innerHTML = "foo" /> , container ) ;
308
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
309
+ expect ( customElement . getAttribute ( 'innerHTML' ) ) . toBe ( null ) ;
310
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
311
+
312
+ // Render again to verify the update codepath doesn't accidentally let
313
+ // something through.
314
+ ReactDOM . render ( < my-custom-element innerHTML = "bar" /> , container ) ;
315
+ expect ( customElement . getAttribute ( 'innerHTML' ) ) . toBe ( null ) ;
316
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
317
+ } ) ;
318
+
319
+ // @gate enableCustomElementPropertySupport
320
+ it ( 'innerText should not work on custom elements' , ( ) => {
321
+ const container = document . createElement ( 'div' ) ;
322
+ ReactDOM . render ( < my-custom-element innerText = "foo" /> , container ) ;
323
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
324
+ expect ( customElement . getAttribute ( 'innerText' ) ) . toBe ( null ) ;
325
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
326
+
327
+ // Render again to verify the update codepath doesn't accidentally let
328
+ // something through.
329
+ ReactDOM . render ( < my-custom-element innerText = "bar" /> , container ) ;
330
+ expect ( customElement . getAttribute ( 'innerText' ) ) . toBe ( null ) ;
331
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
332
+ } ) ;
333
+
334
+ // @gate enableCustomElementPropertySupport
335
+ it ( 'textContent should not work on custom elements' , ( ) => {
336
+ const container = document . createElement ( 'div' ) ;
337
+ ReactDOM . render ( < my-custom-element textContent = "foo" /> , container ) ;
338
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
339
+ expect ( customElement . getAttribute ( 'textContent' ) ) . toBe ( null ) ;
340
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
341
+
342
+ // Render again to verify the update codepath doesn't accidentally let
343
+ // something through.
344
+ ReactDOM . render ( < my-custom-element textContent = "bar" /> , container ) ;
345
+ expect ( customElement . getAttribute ( 'textContent' ) ) . toBe ( null ) ;
346
+ expect ( customElement . hasChildNodes ( ) ) . toBe ( false ) ;
347
+ } ) ;
348
+
349
+ // @gate enableCustomElementPropertySupport
350
+ it ( 'values should not be converted to booleans when assigning into custom elements' , ( ) => {
351
+ const container = document . createElement ( 'div' ) ;
352
+ document . body . appendChild ( container ) ;
353
+ ReactDOM . render ( < my-custom-element /> , container ) ;
354
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
355
+ customElement . foo = null ;
356
+
357
+ // true => string
358
+ ReactDOM . render ( < my-custom-element foo = { true } /> , container ) ;
359
+ expect ( customElement . foo ) . toBe ( true ) ;
360
+ ReactDOM . render ( < my-custom-element foo = "bar" /> , container ) ;
361
+ expect ( customElement . foo ) . toBe ( 'bar' ) ;
362
+
363
+ // false => string
364
+ ReactDOM . render ( < my-custom-element foo = { false } /> , container ) ;
365
+ expect ( customElement . foo ) . toBe ( false ) ;
366
+ ReactDOM . render ( < my-custom-element foo = "bar" /> , container ) ;
367
+ expect ( customElement . foo ) . toBe ( 'bar' ) ;
368
+
369
+ // true => null
370
+ ReactDOM . render ( < my-custom-element foo = { true } /> , container ) ;
371
+ expect ( customElement . foo ) . toBe ( true ) ;
372
+ ReactDOM . render ( < my-custom-element foo = { null } /> , container ) ;
373
+ expect ( customElement . foo ) . toBe ( null ) ;
374
+
375
+ // false => null
376
+ ReactDOM . render ( < my-custom-element foo = { false } /> , container ) ;
377
+ expect ( customElement . foo ) . toBe ( false ) ;
378
+ ReactDOM . render ( < my-custom-element foo = { null } /> , container ) ;
379
+ expect ( customElement . foo ) . toBe ( null ) ;
380
+ } ) ;
381
+
382
+ // @gate enableCustomElementPropertySupport
383
+ it ( 'custom element custom event handlers assign multiple types' , ( ) => {
384
+ const container = document . createElement ( 'div' ) ;
385
+ document . body . appendChild ( container ) ;
386
+ const oncustomevent = jest . fn ( ) ;
387
+
388
+ // First render with string
389
+ ReactDOM . render ( < my-custom-element oncustomevent = { 'foo' } /> , container ) ;
390
+ const customelement = container . querySelector ( 'my-custom-element' ) ;
391
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
392
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 0 ) ;
393
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
394
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( 'foo' ) ;
395
+
396
+ // string => event listener
397
+ ReactDOM . render (
398
+ < my-custom-element oncustomevent = { oncustomevent } /> ,
399
+ container ,
400
+ ) ;
401
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
402
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
403
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
404
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
405
+
406
+ // event listener => string
407
+ ReactDOM . render ( < my-custom-element oncustomevent = { 'foo' } /> , container ) ;
408
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
409
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
410
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
411
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( 'foo' ) ;
412
+
413
+ // string => nothing
414
+ ReactDOM . render ( < my-custom-element /> , container ) ;
415
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
416
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
417
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
418
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
419
+
420
+ // nothing => event listener
421
+ ReactDOM . render (
422
+ < my-custom-element oncustomevent = { oncustomevent } /> ,
423
+ container ,
424
+ ) ;
425
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
426
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 2 ) ;
427
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
428
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
429
+ } ) ;
430
+
431
+ // @gate enableCustomElementPropertySupport
432
+ it ( 'custom element custom event handlers assign multiple types with setter' , ( ) => {
433
+ const container = document . createElement ( 'div' ) ;
434
+ document . body . appendChild ( container ) ;
435
+ const oncustomevent = jest . fn ( ) ;
436
+
437
+ // First render with nothing
438
+ ReactDOM . render ( < my-custom-element /> , container ) ;
439
+ const customelement = container . querySelector ( 'my-custom-element' ) ;
440
+ // Install a setter to activate the `in` heuristic
441
+ Object . defineProperty ( customelement , 'oncustomevent' , {
442
+ set : function ( x ) {
443
+ this . _oncustomevent = x ;
444
+ } ,
445
+ get : function ( ) {
446
+ return this . _oncustomevent ;
447
+ } ,
448
+ } ) ;
449
+ expect ( customelement . oncustomevent ) . toBe ( undefined ) ;
450
+
451
+ // nothing => event listener
452
+ ReactDOM . render (
453
+ < my-custom-element oncustomevent = { oncustomevent } /> ,
454
+ container ,
455
+ ) ;
456
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
457
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
458
+ expect ( customelement . oncustomevent ) . toBe ( null ) ;
459
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
460
+
461
+ // event listener => string
462
+ ReactDOM . render ( < my-custom-element oncustomevent = { 'foo' } /> , container ) ;
463
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
464
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 1 ) ;
465
+ expect ( customelement . oncustomevent ) . toBe ( 'foo' ) ;
466
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
467
+
468
+ // string => event listener
469
+ ReactDOM . render (
470
+ < my-custom-element oncustomevent = { oncustomevent } /> ,
471
+ container ,
472
+ ) ;
473
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
474
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 2 ) ;
475
+ expect ( customelement . oncustomevent ) . toBe ( null ) ;
476
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
477
+
478
+ // event listener => nothing
479
+ ReactDOM . render ( < my-custom-element /> , container ) ;
480
+ customelement . dispatchEvent ( new Event ( 'customevent' ) ) ;
481
+ expect ( oncustomevent ) . toHaveBeenCalledTimes ( 2 ) ;
482
+ expect ( customelement . oncustomevent ) . toBe ( null ) ;
483
+ expect ( customelement . getAttribute ( 'oncustomevent' ) ) . toBe ( null ) ;
484
+ } ) ;
485
+
486
+ // @gate enableCustomElementPropertySupport
487
+ it ( 'assigning to a custom element property should not remove attributes' , ( ) => {
488
+ const container = document . createElement ( 'div' ) ;
489
+ document . body . appendChild ( container ) ;
490
+ ReactDOM . render ( < my-custom-element foo = "one" /> , container ) ;
491
+ const customElement = container . querySelector ( 'my-custom-element' ) ;
492
+ expect ( customElement . getAttribute ( 'foo' ) ) . toBe ( 'one' ) ;
493
+
494
+ // Install a setter to activate the `in` heuristic
495
+ Object . defineProperty ( customElement , 'foo' , {
496
+ set : function ( x ) {
497
+ this . _foo = x ;
498
+ } ,
499
+ get : function ( ) {
500
+ return this . _foo ;
501
+ } ,
502
+ } ) ;
503
+ ReactDOM . render ( < my-custom-element foo = "two" /> , container ) ;
504
+ expect ( customElement . foo ) . toBe ( 'two' ) ;
505
+ expect ( customElement . getAttribute ( 'foo' ) ) . toBe ( 'one' ) ;
506
+ } ) ;
158
507
} ) ;
159
508
160
509
describe ( 'deleteValueForProperty' , ( ) => {
0 commit comments