@@ -155,6 +155,355 @@ describe('DOMPropertyOperations', () => {
155155 // Regression test for https://github.com/facebook/react/issues/6119
156156 expect ( container . firstChild . hasAttribute ( 'value' ) ) . toBe ( false ) ;
157157 } ) ;
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+ } ) ;
158507 } ) ;
159508
160509 describe ( 'deleteValueForProperty' , ( ) => {
0 commit comments