@@ -35,14 +35,14 @@ describe('pure', () => {
3535 }
3636
3737 // Tests should run against both the lazy and non-lazy versions of `pure`.
38- // To make the tests work for both versions, we wrap the non-lazy verion in
38+ // To make the tests work for both versions, we wrap the non-lazy version in
3939 // a lazy function component.
4040 sharedTests ( 'normal' , ( ...args ) => {
4141 const Pure = React . pure ( ...args ) ;
42- function Indirection ( props ) {
43- return < Pure { ...props } /> ;
42+ function Indirection ( props , ref ) {
43+ return < Pure { ...props } ref = { ref } /> ;
4444 }
45- return Promise . resolve ( Indirection ) ;
45+ return Promise . resolve ( React . forwardRef ( Indirection ) ) ;
4646 } ) ;
4747 sharedTests ( 'lazy' , ( ...args ) => Promise . resolve ( React . pure ( ...args ) ) ) ;
4848
@@ -84,110 +84,169 @@ describe('pure', () => {
8484 expect ( ReactNoop . flush ( ) ) . toEqual ( [ 1 ] ) ;
8585 expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 1 ) ] ) ;
8686 } ) ;
87- } ) ;
8887
89- it ( "does not bail out if there's a context change" , async ( ) => {
90- const { unstable_Suspense : Suspense } = React ;
91-
92- const CountContext = React . createContext ( 0 ) ;
93-
94- function Counter ( props ) {
95- const count = CountContext . unstable_read ( ) ;
96- return < Text text = { `${ props . label } : ${ count } ` } /> ;
97- }
98- Counter = pure ( Counter ) ;
99-
100- class Parent extends React . Component {
101- state = { count : 0 } ;
102- render ( ) {
103- return (
104- < Suspense >
105- < CountContext . Provider value = { this . state . count } >
106- < Counter label = "Count" />
107- </ CountContext . Provider >
108- </ Suspense >
88+ it ( "does not bail out if there's a context change" , async ( ) => {
89+ const { unstable_Suspense : Suspense } = React ;
90+
91+ const CountContext = React . createContext ( 0 ) ;
92+
93+ function Counter ( props ) {
94+ const count = CountContext . unstable_read ( ) ;
95+ return < Text text = { `${ props . label } : ${ count } ` } /> ;
96+ }
97+ Counter = pure ( Counter ) ;
98+
99+ class Parent extends React . Component {
100+ state = { count : 0 } ;
101+ render ( ) {
102+ return (
103+ < Suspense >
104+ < CountContext . Provider value = { this . state . count } >
105+ < Counter label = "Count" />
106+ </ CountContext . Provider >
107+ </ Suspense >
108+ ) ;
109+ }
110+ }
111+
112+ const parent = React . createRef ( null ) ;
113+ ReactNoop . render ( < Parent ref = { parent } /> ) ;
114+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
115+ await Promise . resolve ( ) ;
116+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Count: 0' ] ) ;
117+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 0' ) ] ) ;
118+
119+ // Should bail out because props have not changed
120+ ReactNoop . render ( < Parent ref = { parent } /> ) ;
121+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
122+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 0' ) ] ) ;
123+
124+ // Should update because there was a context change
125+ parent . current . setState ( { count : 1 } ) ;
126+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Count: 1' ] ) ;
127+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 1' ) ] ) ;
128+ } ) ;
129+
130+ it ( 'accepts custom comparison function' , async ( ) => {
131+ const { unstable_Suspense : Suspense } = React ;
132+
133+ function Counter ( { count} ) {
134+ return < Text text = { count } /> ;
135+ }
136+ Counter = pure ( Counter , ( oldProps , newProps ) => {
137+ ReactNoop . yield (
138+ `Old count: ${ oldProps . count } , New count: ${ newProps . count } ` ,
109139 ) ;
140+ return oldProps . count === newProps . count ;
141+ } ) ;
142+
143+ ReactNoop . render (
144+ < Suspense >
145+ < Counter count = { 0 } />
146+ </ Suspense > ,
147+ ) ;
148+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
149+ await Promise . resolve ( ) ;
150+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 0 ] ) ;
151+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 0 ) ] ) ;
152+
153+ // Should bail out because props have not changed
154+ ReactNoop . render (
155+ < Suspense >
156+ < Counter count = { 0 } />
157+ </ Suspense > ,
158+ ) ;
159+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Old count: 0, New count: 0' ] ) ;
160+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 0 ) ] ) ;
161+
162+ // Should update because count prop changed
163+ ReactNoop . render (
164+ < Suspense >
165+ < Counter count = { 1 } />
166+ </ Suspense > ,
167+ ) ;
168+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Old count: 0, New count: 1' , 1 ] ) ;
169+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 1 ) ] ) ;
170+ } ) ;
171+
172+ it ( 'warns for class components' , ( ) => {
173+ class SomeClass extends React . Component {
174+ render ( ) {
175+ return null ;
176+ }
110177 }
111- }
112-
113- const parent = React . createRef ( null ) ;
114- ReactNoop . render ( < Parent ref = { parent } /> ) ;
115- expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
116- await Promise . resolve ( ) ;
117- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Count: 0' ] ) ;
118- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 0' ) ] ) ;
119-
120- // Should bail out because props have not changed
121- ReactNoop . render ( < Parent ref = { parent } /> ) ;
122- expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
123- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 0' ) ] ) ;
124-
125- // Should update because there was a context change
126- parent . current . setState ( { count : 1 } ) ;
127- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Count: 1' ] ) ;
128- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Count: 1' ) ] ) ;
129- } ) ;
178+ expect ( ( ) => pure ( SomeClass ) ) . toWarnDev (
179+ 'pure: The first argument must be a function component.' ,
180+ { withoutStack : true } ,
181+ ) ;
182+ } ) ;
130183
131- it ( 'accepts custom comparison function' , async ( ) => {
132- const { unstable_Suspense : Suspense } = React ;
184+ it ( 'warns if first argument is not a function' , ( ) => {
185+ expect ( ( ) => pure ( ) ) . toWarnDev (
186+ 'pure: The first argument must be a function component. Instead ' +
187+ 'received: undefined' ,
188+ { withoutStack : true } ,
189+ ) ;
190+ } ) ;
133191
134- function Counter ( { count} ) {
135- return < Text text = { count } /> ;
136- }
137- Counter = pure ( Counter , ( oldProps , newProps ) => {
138- ReactNoop . yield (
139- `Old count: ${ oldProps . count } , New count: ${ newProps . count } ` ,
192+ it ( 'forwards ref' , async ( ) => {
193+ const { unstable_Suspense : Suspense } = React ;
194+ const Transparent = pure ( ( props , ref ) => {
195+ return < div ref = { ref } /> ;
196+ } ) ;
197+ const divRef = React . createRef ( ) ;
198+
199+ ReactNoop . render (
200+ < Suspense >
201+ < Transparent ref = { divRef } />
202+ </ Suspense > ,
140203 ) ;
141- return oldProps . count === newProps . count ;
204+ ReactNoop . flush ( ) ;
205+ await Promise . resolve ( ) ;
206+ ReactNoop . flush ( ) ;
207+ expect ( divRef . current . type ) . toBe ( 'div' ) ;
142208 } ) ;
143209
144- ReactNoop . render (
145- < Suspense >
146- < Counter count = { 0 } />
147- </ Suspense > ,
148- ) ;
149- expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
150- await Promise . resolve ( ) ;
151- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 0 ] ) ;
152- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 0 ) ] ) ;
153-
154- // Should bail out because props have not changed
155- ReactNoop . render (
156- < Suspense >
157- < Counter count = { 0 } />
158- </ Suspense > ,
159- ) ;
160- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Old count: 0, New count: 0' ] ) ;
161- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 0 ) ] ) ;
162-
163- // Should update because count prop changed
164- ReactNoop . render (
165- < Suspense >
166- < Counter count = { 1 } />
167- </ Suspense > ,
168- ) ;
169- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Old count: 0, New count: 1' , 1 ] ) ;
170- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 1 ) ] ) ;
171- } ) ;
210+ it ( 'updates if only ref changes' , async ( ) => {
211+ const { unstable_Suspense : Suspense } = React ;
212+ const Transparent = pure ( ( props , ref ) => {
213+ return [ < Text key = "text" text = "Text" /> , < div key = "div" ref = { ref } /> ] ;
214+ } ) ;
172215
173- it ( 'warns for class components' , ( ) => {
174- class SomeClass extends React . Component {
175- render ( ) {
176- return null ;
177- }
178- }
179- expect ( ( ) => pure ( SomeClass ) ) . toWarnDev (
180- 'pure: The first argument must be a function component.' ,
181- { withoutStack : true } ,
182- ) ;
183- } ) ;
216+ const divRef = React . createRef ( ) ;
217+ const divRef2 = React . createRef ( ) ;
218+
219+ ReactNoop . render (
220+ < Suspense >
221+ < Transparent ref = { divRef } />
222+ </ Suspense > ,
223+ ) ;
224+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
225+ await Promise . resolve ( ) ;
226+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Text' ] ) ;
227+ expect ( divRef . current . type ) . toBe ( 'div' ) ;
228+ expect ( divRef2 . current ) . toBe ( null ) ;
229+
230+ // Should re-render (new ref)
231+ ReactNoop . render (
232+ < Suspense >
233+ < Transparent ref = { divRef2 } />
234+ </ Suspense > ,
235+ ) ;
236+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'Text' ] ) ;
237+ expect ( divRef . current ) . toBe ( null ) ;
238+ expect ( divRef2 . current . type ) . toBe ( 'div' ) ;
184239
185- it ( 'warns if first argument is not a function' , ( ) => {
186- expect ( ( ) => pure ( ) ) . toWarnDev (
187- 'pure: The first argument must be a function component. Instead ' +
188- 'received: undefined' ,
189- { withoutStack : true } ,
190- ) ;
240+ // Should not re-render (same ref)
241+ ReactNoop . render (
242+ < Suspense >
243+ < Transparent ref = { divRef2 } />
244+ </ Suspense > ,
245+ ) ;
246+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ ] ) ;
247+ expect ( divRef . current ) . toBe ( null ) ;
248+ expect ( divRef2 . current . type ) . toBe ( 'div' ) ;
249+ } ) ;
191250 } ) ;
192251 }
193252} ) ;
0 commit comments