1/* eslint 2 react/no-multi-comp: 0, 3 react/prefer-stateless-function: 0 4*/ 5 6import React from 'react'; 7import { expect } from 'chai'; 8 9import { componentWithName } from '..'; 10 11import callValidator from './_callValidator'; 12 13function SFC() {} 14 15function SFCwithName() {} 16SFCwithName.displayName = 'SFC with a display name!'; 17 18class Component extends React.Component {} 19 20class ComponentWithName extends React.Component {} 21ComponentWithName.displayName = 'Component with a display name!'; 22 23class ComponentWithHOCs extends React.Component {} 24ComponentWithHOCs.displayName = 'withA(withB(withC(Connect(X))))'; 25 26const describeIfForwardRefs = React.forwardRef ? describe : describe.skip; 27 28describe('componentWithName', () => { 29 it('returns a function', () => { 30 expect(typeof componentWithName('name')).to.equal('function'); 31 }); 32 33 it('throws when not given a string or a regex', () => { 34 expect(() => componentWithName()).to.throw(TypeError); 35 expect(() => componentWithName(null)).to.throw(TypeError); 36 expect(() => componentWithName(undefined)).to.throw(TypeError); 37 expect(() => componentWithName(true)).to.throw(TypeError); 38 expect(() => componentWithName(false)).to.throw(TypeError); 39 expect(() => componentWithName(42)).to.throw(TypeError); 40 expect(() => componentWithName(NaN)).to.throw(TypeError); 41 expect(() => componentWithName([])).to.throw(TypeError); 42 expect(() => componentWithName({})).to.throw(TypeError); 43 }); 44 45 it('throws when given unknown options', () => { 46 expect(() => componentWithName('Foo', {})).not.to.throw(); 47 expect(() => componentWithName('Foo', { stripHOCS: 'typo in the last "s"' })).to.throw(TypeError); 48 expect(() => componentWithName('Foo', { stripHOCs: [], extra: true })).to.throw(TypeError); 49 }); 50 51 it('throws when given names of HOCs to strip that are not strings', () => { 52 const notStrings = [null, undefined, true, false, 42, NaN, [], {}, () => {}]; 53 notStrings.forEach((notString) => { 54 expect(() => componentWithName('Foo', { stripHOCs: [notString] })).to.throw(TypeError); 55 }); 56 }); 57 58 it('throws when given names of HOCs to strip that have parens', () => { 59 expect(() => componentWithName('Foo', { stripHOCs: ['with()Foo'] })).to.throw(TypeError); 60 }); 61 62 it('throws when given names of HOCs to strip that are not in camelCase', () => { 63 expect(() => componentWithName('Foo', { stripHOCs: ['with_foo'] })).to.throw(TypeError); 64 }); 65 66 function assertPasses(validator, element, propName) { 67 expect(callValidator(validator, element, propName)).to.equal(null); 68 } 69 70 function assertFails(validator, element, propName) { 71 expect(callValidator(validator, element, propName)).to.be.instanceOf(Error); 72 } 73 74 describe('with a single child of the specified name', () => { 75 it('passes with an SFC', () => assertPasses( 76 componentWithName('SFC'), 77 (<div><SFC default="Foo" /></div>), 78 'children', 79 )); 80 81 it('passes with an SFC + displayName', () => assertPasses( 82 componentWithName(SFCwithName.displayName), 83 (<div><SFCwithName default="Foo" /></div>), 84 'children', 85 )); 86 87 it('passes with a Component', () => assertPasses( 88 componentWithName('Component'), 89 (<div><Component default="Foo" /></div>), 90 'children', 91 )); 92 93 it('passes with a Component + displayName', () => assertPasses( 94 componentWithName(ComponentWithName.displayName), 95 (<div><ComponentWithName default="Foo" /></div>), 96 'children', 97 )); 98 99 it('passes with a component with HOCs', () => { 100 assertPasses( 101 componentWithName('X', { stripHOCs: ['withA', 'withB', 'withC', 'Connect'] }), 102 (<div><ComponentWithHOCs default="Foo" /></div>), 103 'children', 104 ); 105 106 assertPasses( 107 componentWithName('withC(Connect(X))', { stripHOCs: ['withA', 'withB'] }), 108 (<div><ComponentWithHOCs default="Foo" /></div>), 109 'children', 110 ); 111 112 assertPasses( 113 componentWithName('withB(withC(Connect(X)))', { stripHOCs: ['withA', 'withC'] }), 114 (<div><ComponentWithHOCs default="Foo" /></div>), 115 'children', 116 ); 117 }); 118 }); 119 120 describe('with multiple children of the specified name', () => { 121 it('passes with an SFC', () => assertPasses( 122 componentWithName('SFC'), 123 ( 124 <div> 125 <SFC default="Foo" /> 126 <SFC default="Foo" /> 127 <SFC default="Foo" /> 128 <SFC default="Foo" /> 129 </div> 130 ), 131 'children', 132 )); 133 134 it('passes with an SFC + displayName', () => assertPasses( 135 componentWithName(SFCwithName.displayName), 136 ( 137 <div> 138 <SFCwithName default="Foo" /> 139 <SFCwithName default="Foo" /> 140 <SFCwithName default="Foo" /> 141 <SFCwithName default="Foo" /> 142 </div> 143 ), 144 'children', 145 )); 146 147 it('passes with a Component', () => assertPasses( 148 componentWithName('Component'), 149 ( 150 <div> 151 <Component default="Foo" /> 152 <Component default="Foo" /> 153 <Component default="Foo" /> 154 <Component default="Foo" /> 155 </div> 156 ), 157 'children', 158 )); 159 160 it('passes with a Component + displayName', () => assertPasses( 161 componentWithName(ComponentWithName.displayName), 162 ( 163 <div> 164 <ComponentWithName default="Foo" /> 165 <ComponentWithName default="Foo" /> 166 <ComponentWithName default="Foo" /> 167 <ComponentWithName default="Foo" /> 168 </div> 169 ), 170 'children', 171 )); 172 173 it('passes with a component with HOCs', () => { 174 assertPasses( 175 componentWithName('X', { stripHOCs: ['withA', 'withB', 'withC', 'Connect'] }), 176 ( 177 <div> 178 <ComponentWithHOCs default="Foo" /> 179 <ComponentWithHOCs default="Foo" /> 180 <ComponentWithHOCs default="Foo" /> 181 <ComponentWithHOCs default="Foo" /> 182 </div> 183 ), 184 'children', 185 ); 186 187 assertPasses( 188 componentWithName('withC(Connect(X))', { stripHOCs: ['withA', 'withB'] }), 189 ( 190 <div> 191 <ComponentWithHOCs default="Foo" /> 192 <ComponentWithHOCs default="Foo" /> 193 <ComponentWithHOCs default="Foo" /> 194 <ComponentWithHOCs default="Foo" /> 195 </div> 196 ), 197 'children', 198 ); 199 200 assertPasses( 201 componentWithName('withB(withC(Connect(X)))', { stripHOCs: ['withA', 'withC'] }), 202 ( 203 <div> 204 <ComponentWithHOCs default="Foo" /> 205 <ComponentWithHOCs default="Foo" /> 206 <ComponentWithHOCs default="Foo" /> 207 <ComponentWithHOCs default="Foo" /> 208 </div> 209 ), 210 'children', 211 ); 212 }); 213 }); 214 215 describe('with children of the specified names passed as an array', () => { 216 it('passes with an SFC', () => assertPasses( 217 componentWithName('SFC'), 218 ( 219 <div> 220 {[ 221 <SFC key="one" default="Foo" />, 222 <SFC key="two" default="Foo" />, 223 <SFC key="three" default="Foo" />, 224 ]} 225 </div> 226 ), 227 'children', 228 )); 229 230 it('passes with an SFC + displayName', () => assertPasses( 231 componentWithName(SFCwithName.displayName), 232 ( 233 <div> 234 {[ 235 <SFCwithName key="one" default="Foo" />, 236 <SFCwithName key="two" default="Foo" />, 237 <SFCwithName key="three" default="Foo" />, 238 ]} 239 </div> 240 ), 241 'children', 242 )); 243 244 it('passes with a Component', () => assertPasses( 245 componentWithName('Component'), 246 ( 247 <div> 248 {[ 249 <Component key="one" default="Foo" />, 250 <Component key="two" default="Foo" />, 251 <Component key="three" default="Foo" />, 252 ]} 253 </div> 254 ), 255 'children', 256 )); 257 258 it('passes with a Component + displayName', () => assertPasses( 259 componentWithName(ComponentWithName.displayName), 260 ( 261 <div> 262 {[ 263 <ComponentWithName key="one" default="Foo" />, 264 <ComponentWithName key="two" default="Foo" />, 265 <ComponentWithName key="three" default="Foo" />, 266 ]} 267 </div> 268 ), 269 'children', 270 )); 271 272 it('passes with a component with HOCs', () => { 273 assertPasses( 274 componentWithName('X', { stripHOCs: ['withA', 'withB', 'withC', 'Connect'] }), 275 ( 276 <div> 277 {[ 278 <ComponentWithHOCs key="one" default="Foo" />, 279 <ComponentWithHOCs key="two" default="Foo" />, 280 <ComponentWithHOCs key="three" default="Foo" />, 281 <ComponentWithHOCs key="four" default="Foo" />, 282 ]} 283 </div> 284 ), 285 'children', 286 ); 287 288 assertPasses( 289 componentWithName('withC(Connect(X))', { stripHOCs: ['withA', 'withB'] }), 290 ( 291 <div> 292 {[ 293 <ComponentWithHOCs key="one" default="Foo" />, 294 <ComponentWithHOCs key="two" default="Foo" />, 295 <ComponentWithHOCs key="three" default="Foo" />, 296 <ComponentWithHOCs key="four" default="Foo" />, 297 ]} 298 </div> 299 ), 300 'children', 301 ); 302 303 assertPasses( 304 componentWithName('withB(withC(Connect(X)))', { stripHOCs: ['withA', 'withC'] }), 305 ( 306 <div> 307 {[ 308 <ComponentWithHOCs key="one" default="Foo" />, 309 <ComponentWithHOCs key="two" default="Foo" />, 310 <ComponentWithHOCs key="three" default="Foo" />, 311 <ComponentWithHOCs key="four" default="Foo" />, 312 ]} 313 </div> 314 ), 315 'children', 316 ); 317 }); 318 }); 319 320 describe('when an unspecified name is provided as a child', () => { 321 it('fails with an SFC', () => assertFails( 322 componentWithName('SFC'), 323 ( 324 <div> 325 <SFC default="Foo" /> 326 <section>No way.</section> 327 </div> 328 ), 329 'children', 330 )); 331 332 it('fails with an SFC + displayName', () => assertFails( 333 componentWithName(SFCwithName.displayName), 334 ( 335 <div> 336 <SFCwithName default="Foo" /> 337 <section>No way.</section> 338 </div> 339 ), 340 'children', 341 )); 342 343 it('fails with a Component', () => assertFails( 344 componentWithName('Component'), 345 ( 346 <div> 347 <Component default="Foo" /> 348 <section>No way.</section> 349 </div> 350 ), 351 'children', 352 )); 353 354 it('fails with a Component + displayName', () => assertFails( 355 componentWithName(ComponentWithName.displayName), 356 ( 357 <div> 358 <ComponentWithName default="Foo" /> 359 <section>No way.</section> 360 </div> 361 ), 362 'children', 363 )); 364 365 it('fails with a component with HOCs', () => { 366 assertFails( 367 componentWithName('X', { stripHOCs: ['withA', 'withB', 'withC', 'Connect'] }), 368 ( 369 <div> 370 <ComponentWithHOCs default="Foo" /> 371 <section>No way.</section> 372 </div> 373 ), 374 'children', 375 ); 376 377 assertFails( 378 componentWithName('withC(Connect(X))', { stripHOCs: ['withA', 'withB'] }), 379 ( 380 <div> 381 <ComponentWithHOCs default="Foo" /> 382 <section>No way.</section> 383 </div> 384 ), 385 'children', 386 ); 387 388 assertFails( 389 componentWithName('withB(withC(Connect(X)))', { stripHOCs: ['withA', 'withC'] }), 390 ( 391 <div> 392 <ComponentWithHOCs default="Foo" /> 393 <section>No way.</section> 394 </div> 395 ), 396 'children', 397 ); 398 }); 399 }); 400 401 describe('when a regex value is provided instead of a string', () => { 402 it('passes with an SFC', () => assertPasses( 403 componentWithName(/FC$/), 404 ( 405 <div><SFC default="Foo" /></div> 406 ), 407 'children', 408 )); 409 410 it('passes with an SFC + displayName', () => assertPasses( 411 componentWithName(/display name/), 412 ( 413 <div><SFCwithName default="Foo" /></div> 414 ), 415 'children', 416 )); 417 418 it('passes with a Component', () => assertPasses( 419 componentWithName(/^Comp/), 420 (<div><Component default="Foo" /></div>), 421 'children', 422 )); 423 424 it('passes with a Component + displayName', () => assertPasses( 425 componentWithName(/display name/), 426 (<div><ComponentWithName default="Foo" /></div>), 427 'children', 428 )); 429 430 it('passes with a component with HOCs', () => { 431 assertPasses( 432 componentWithName(/^X$/, { stripHOCs: ['withA', 'withB', 'withC', 'Connect'] }), 433 (<div><ComponentWithHOCs default="Foo" /></div>), 434 'children', 435 ); 436 437 assertPasses( 438 componentWithName(/^withC\(Connect\(X\)\)$/, { stripHOCs: ['withA', 'withB'] }), 439 (<div><ComponentWithHOCs default="Foo" /></div>), 440 'children', 441 ); 442 443 assertPasses( 444 componentWithName(/^withB\(withC\(Connect\(X\)\)\)$/, { stripHOCs: ['withA', 'withC'] }), 445 (<div><ComponentWithHOCs default="Foo" /></div>), 446 'children', 447 ); 448 }); 449 450 it('fails when SFC name does not match the regex provided', () => assertFails( 451 componentWithName(/foobar/), 452 (<div><SFC default="Foo" /></div>), 453 'children', 454 )); 455 456 it('fails when Component name does not match the regex provided', () => assertFails( 457 componentWithName(/foobar/), 458 (<div><Component default="Foo" /></div>), 459 'children', 460 )); 461 462 it('fails with a component with HOCs that does not match the regex', () => { 463 assertFails( 464 componentWithName(/^zX$/, { stripHOCs: ['withA', 'withB', 'withC'] }), 465 (<div><ComponentWithHOCs default="Foo" /></div>), 466 'children', 467 ); 468 469 assertFails( 470 componentWithName(/^zwithC\(X\)$/, { stripHOCs: ['withA', 'withB'] }), 471 (<div><ComponentWithHOCs default="Foo" /></div>), 472 'children', 473 ); 474 475 assertFails( 476 componentWithName(/^zwithB\(withC\(X\)\)$/, { stripHOCs: ['withA', 'withC'] }), 477 (<div><ComponentWithHOCs default="Foo" /></div>), 478 'children', 479 ); 480 }); 481 }); 482 483 it('fails when the provided prop is not a component', () => assertFails( 484 componentWithName('SFC'), 485 ( 486 <div> 487 Blah blah blah. 488 </div> 489 ), 490 'children', 491 )); 492 493 it('passes when the prop is null', () => assertPasses( 494 componentWithName('SFC'), 495 ( 496 <div a={null} /> 497 ), 498 'a', 499 )); 500 501 it('passes when the prop is absent', () => assertPasses( 502 componentWithName('SFC'), 503 ( 504 <div /> 505 ), 506 'a', 507 )); 508 509 describe('when the prop is required', () => { 510 it('fails when the prop is null', () => assertFails( 511 componentWithName('SFC').isRequired, 512 (<div a={null} />), 513 'a', 514 )); 515 516 it('passes when the prop is the right component', () => assertPasses( 517 componentWithName('SFC').isRequired, 518 (<div a={<SFC />} />), 519 'a', 520 )); 521 }); 522 523 describeIfForwardRefs('Forward Refs', () => { 524 it('passes on a forward ref', () => { 525 const Reffy = React.forwardRef(() => <main />); 526 Reffy.displayName = 'Reffy Name'; 527 assertPasses( 528 componentWithName('Reffy Name').isRequired, 529 (<div a={<Reffy />} />), 530 'a', 531 ); 532 }); 533 }); 534}); 535