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