@khanacademy/wonder-blocks-testing 8.0.21 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/es/index.js +450 -33
  3. package/dist/harness/adapt.d.ts +17 -0
  4. package/dist/harness/adapter.d.ts +16 -0
  5. package/dist/harness/adapters/adapters.d.ts +1 -0
  6. package/dist/harness/adapters/ssr.d.ts +12 -0
  7. package/dist/harness/test-harness.d.ts +1 -0
  8. package/dist/harness/types.d.ts +1 -1
  9. package/dist/index.js +449 -33
  10. package/dist/mock-requester.d.ts +2 -2
  11. package/package.json +2 -2
  12. package/src/fetch/fetch-request-matches-mock.ts +2 -4
  13. package/src/fetch/mock-fetch.ts +1 -1
  14. package/src/fixtures/__tests__/fixtures.test.tsx +9 -14
  15. package/src/fixtures/fixtures.basic.stories.tsx +9 -3
  16. package/src/fixtures/fixtures.tsx +1 -2
  17. package/src/gql/mock-gql-fetch.ts +1 -1
  18. package/src/harness/__tests__/adapt.test.tsx +200 -0
  19. package/src/harness/__tests__/adapter.test.tsx +67 -0
  20. package/src/harness/__tests__/hook-harness.test.ts +4 -2
  21. package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
  22. package/src/harness/__tests__/test-harness.test.ts +4 -2
  23. package/src/harness/__tests__/types.typestest.tsx +6 -13
  24. package/src/harness/adapt.tsx +55 -0
  25. package/src/harness/adapter.tsx +27 -0
  26. package/src/harness/adapters/__tests__/data.test.tsx +12 -4
  27. package/src/harness/adapters/__tests__/ssr.test.tsx +82 -0
  28. package/src/harness/adapters/adapters.ts +3 -0
  29. package/src/harness/adapters/css.tsx +1 -3
  30. package/src/harness/adapters/router.tsx +5 -17
  31. package/src/harness/adapters/ssr.tsx +37 -0
  32. package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
  33. package/src/harness/make-test-harness.tsx +6 -8
  34. package/src/harness/types.ts +1 -1
  35. package/src/mock-requester.ts +4 -11
  36. package/src/respond-with.ts +2 -1
  37. package/src/settle-controller.ts +2 -3
  38. package/tsconfig-build.tsbuildinfo +1 -1
  39. package/dist/harness/render-adapters.d.ts +0 -6
  40. package/src/harness/__tests__/render-adapters.test.tsx +0 -87
  41. package/src/harness/render-adapters.ts +0 -27
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @khanacademy/wonder-blocks-testing
2
2
 
3
+ ## 9.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6ed7e928: Test harness adapters are now rendered as React components which should ensure that contexts are properly available when children are rendered inside the adapters that require those contexts
8
+
9
+ ## 9.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - 1920feb8: Added new SSR adapter for test harnesses to support `RenderStateRoot` in tests and stories
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [65c02cff]
18
+ - @khanacademy/wonder-blocks-data@13.0.0
19
+
3
20
  ## 8.0.21
4
21
 
5
22
  ### Patch Changes
package/dist/es/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import * as React from 'react';
2
+ import { useContext } from 'react';
2
3
  import { action } from '@storybook/addon-actions';
3
4
  import { InterceptRequests } from '@khanacademy/wonder-blocks-data';
4
5
  import { StaticRouter, MemoryRouter, Switch, Route } from 'react-router-dom';
6
+ import { KindError, Errors } from '@khanacademy/wonder-stuff-core';
7
+ import { StyleSheet, css } from 'aphrodite';
5
8
 
6
9
  const fixtures = Component => {
7
10
  const templateMap = new WeakMap();
@@ -247,11 +250,12 @@ class SettleController {
247
250
  return this._signal;
248
251
  }
249
252
  settle() {
250
- this._settleFn();
253
+ var _this$_settleFn;
254
+ (_this$_settleFn = this._settleFn) == null ? void 0 : _this$_settleFn.call(this);
251
255
  }
252
256
  }
253
257
 
254
- const defaultConfig$3 = null;
258
+ const defaultConfig$4 = null;
255
259
  const normalizeConfig = config => {
256
260
  if (typeof config === "string") {
257
261
  return {
@@ -276,7 +280,7 @@ const normalizeConfig = config => {
276
280
  }
277
281
  throw new Error(`Invalid config: ${config}`);
278
282
  };
279
- const adapter$3 = (children, config) => {
283
+ const adapter$4 = (children, config) => {
280
284
  const {
281
285
  classes,
282
286
  style
@@ -288,8 +292,8 @@ const adapter$3 = (children, config) => {
288
292
  }, children);
289
293
  };
290
294
 
291
- const defaultConfig$2 = [];
292
- const adapter$2 = (children, config) => {
295
+ const defaultConfig$3 = [];
296
+ const adapter$3 = (children, config) => {
293
297
  let currentChildren = children;
294
298
  const interceptors = Array.isArray(config) ? config : [config];
295
299
  for (const interceptor of interceptors) {
@@ -300,18 +304,18 @@ const adapter$2 = (children, config) => {
300
304
  return React.createElement(React.Fragment, null, currentChildren);
301
305
  };
302
306
 
303
- const defaultConfig$1 = null;
304
- const adapter$1 = (children, config) => React.createElement(React.Fragment, null, React.createElement("div", {
307
+ const defaultConfig$2 = null;
308
+ const adapter$2 = (children, config) => React.createElement(React.Fragment, null, React.createElement("div", {
305
309
  id: config,
306
310
  "data-test-id": config
307
311
  }), children);
308
312
 
309
- const defaultConfig = {
313
+ const defaultConfig$1 = {
310
314
  location: "/"
311
315
  };
312
316
  const maybeWithRoute = (children, path) => {
313
317
  if (path == null) {
314
- return children;
318
+ return React.createElement(React.Fragment, null, children);
315
319
  }
316
320
  return React.createElement(Switch, null, React.createElement(Route, {
317
321
  exact: true,
@@ -323,28 +327,28 @@ const maybeWithRoute = (children, path) => {
323
327
  }
324
328
  }));
325
329
  };
326
- const adapter = (children, config) => {
330
+ const adapter$1 = (children, config) => {
327
331
  if (typeof config === "string") {
328
332
  config = {
329
333
  location: config
330
334
  };
331
335
  }
332
336
  const wrappedWithRoute = maybeWithRoute(children, config.path);
333
- if (config.forceStatic) {
337
+ if ("forceStatic" in config && config.forceStatic) {
334
338
  return React.createElement(StaticRouter, {
335
339
  location: config.location,
336
340
  context: {}
337
341
  }, wrappedWithRoute);
338
342
  }
339
- if (typeof config.location !== "undefined") {
343
+ if ("location" in config && config.location !== undefined) {
340
344
  return React.createElement(MemoryRouter, {
341
345
  initialEntries: [config.location]
342
346
  }, wrappedWithRoute);
343
347
  }
344
- if (typeof config.initialEntries === "undefined") {
348
+ if (!("initialEntries" in config) || config.initialEntries === undefined) {
345
349
  throw new Error("A location or initial history entries must be provided.");
346
350
  }
347
- const entries = config.initialEntries.length === 0 ? [defaultConfig.location] : config.initialEntries;
351
+ const entries = config.initialEntries.length === 0 ? [defaultConfig$1.location] : config.initialEntries;
348
352
  const routerProps = {
349
353
  initialEntries: entries
350
354
  };
@@ -357,17 +361,406 @@ const adapter = (children, config) => {
357
361
  return React.createElement(MemoryRouter, routerProps, wrappedWithRoute);
358
362
  };
359
363
 
364
+ function _extends$1() {
365
+ _extends$1 = Object.assign ? Object.assign.bind() : function (target) {
366
+ for (var i = 1; i < arguments.length; i++) {
367
+ var source = arguments[i];
368
+ for (var key in source) {
369
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
370
+ target[key] = source[key];
371
+ }
372
+ }
373
+ }
374
+ return target;
375
+ };
376
+ return _extends$1.apply(this, arguments);
377
+ }
378
+ function _objectWithoutPropertiesLoose(source, excluded) {
379
+ if (source == null) return {};
380
+ var target = {};
381
+ var sourceKeys = Object.keys(source);
382
+ var key, i;
383
+ for (i = 0; i < sourceKeys.length; i++) {
384
+ key = sourceKeys[i];
385
+ if (excluded.indexOf(key) >= 0) continue;
386
+ target[key] = source[key];
387
+ }
388
+ return target;
389
+ }
390
+ function flatten(list) {
391
+ const result = [];
392
+ if (!list) {
393
+ return result;
394
+ } else if (Array.isArray(list)) {
395
+ for (const item of list) {
396
+ result.push(...flatten(item));
397
+ }
398
+ } else {
399
+ result.push(list);
400
+ }
401
+ return result;
402
+ }
403
+ function processStyleList(style) {
404
+ const stylesheetStyles = [];
405
+ const inlineStyles = [];
406
+ if (!style) {
407
+ return {
408
+ style: {},
409
+ className: ""
410
+ };
411
+ }
412
+ const shouldInlineStyles = typeof global !== "undefined" && global.SNAPSHOT_INLINE_APHRODITE;
413
+ flatten(style).forEach(child => {
414
+ const _definition = child._definition;
415
+ if (_definition != null) {
416
+ if (shouldInlineStyles) {
417
+ const def = {};
418
+ for (const [key, value] of Object.entries(_definition)) {
419
+ def[key.replace(/-[a-z]/g, match => match[1].toUpperCase())] = value;
420
+ }
421
+ inlineStyles.push(def);
422
+ } else {
423
+ stylesheetStyles.push(child);
424
+ }
425
+ } else {
426
+ inlineStyles.push(child);
427
+ }
428
+ });
429
+ const inlineStylesObject = Object.assign({}, ...inlineStyles);
430
+ if (inlineStyles.length > 0 && !shouldInlineStyles) {
431
+ const inlineStylesStyleSheet = StyleSheet.create({
432
+ inlineStyles: inlineStylesObject
433
+ });
434
+ stylesheetStyles.push(inlineStylesStyleSheet.inlineStyles);
435
+ }
436
+ return {
437
+ style: shouldInlineStyles ? inlineStylesObject : {},
438
+ className: css(...stylesheetStyles)
439
+ };
440
+ }
441
+ const _excluded$2 = ["children", "style", "tag", "testId"];
442
+ const isHeaderRegex = /^h[1-6]$/;
443
+ const styles$1 = StyleSheet.create({
444
+ text: {
445
+ WebkitFontSmoothing: "antialiased",
446
+ MozOsxFontSmoothing: "grayscale"
447
+ },
448
+ header: {
449
+ marginTop: 0,
450
+ marginBottom: 0
451
+ }
452
+ });
453
+ React.forwardRef(function Text(_ref, ref) {
454
+ let {
455
+ children,
456
+ style,
457
+ tag: Tag = "span",
458
+ testId
459
+ } = _ref,
460
+ otherProps = _objectWithoutPropertiesLoose(_ref, _excluded$2);
461
+ const isHeader = isHeaderRegex.test(Tag);
462
+ const styleAttributes = processStyleList([styles$1.text, isHeader && styles$1.header, style]);
463
+ return React.createElement(Tag, _extends$1({}, otherProps, {
464
+ style: styleAttributes.style,
465
+ className: styleAttributes.className,
466
+ "data-test-id": testId,
467
+ ref: ref
468
+ }), children);
469
+ });
470
+ const _excluded$1 = ["className", "style"];
471
+ function addStyle(Component, defaultStyle) {
472
+ return React.forwardRef((props, ref) => {
473
+ const {
474
+ className,
475
+ style
476
+ } = props,
477
+ otherProps = _objectWithoutPropertiesLoose(props, _excluded$1);
478
+ const reset = typeof Component === "string" ? overrides[Component] : null;
479
+ const {
480
+ className: aphroditeClassName,
481
+ style: inlineStyles
482
+ } = processStyleList([reset, defaultStyle, style]);
483
+ return React.createElement(Component, _extends$1({}, otherProps, {
484
+ ref: ref,
485
+ className: [aphroditeClassName, className].filter(Boolean).join(" "),
486
+ style: inlineStyles
487
+ }));
488
+ });
489
+ }
490
+ const overrides = StyleSheet.create({
491
+ button: {
492
+ margin: 0,
493
+ "::-moz-focus-inner": {
494
+ border: 0
495
+ }
496
+ }
497
+ });
498
+ const _excluded = ["testId", "tag"];
499
+ const styles = StyleSheet.create({
500
+ default: {
501
+ alignItems: "stretch",
502
+ borderWidth: 0,
503
+ borderStyle: "solid",
504
+ boxSizing: "border-box",
505
+ display: "flex",
506
+ flexDirection: "column",
507
+ margin: 0,
508
+ padding: 0,
509
+ position: "relative",
510
+ zIndex: 0,
511
+ minHeight: 0,
512
+ minWidth: 0
513
+ }
514
+ });
515
+ const StyledDiv = addStyle("div", styles.default);
516
+ const StyledArticle = addStyle("article", styles.default);
517
+ const StyledAside = addStyle("aside", styles.default);
518
+ const StyledNav = addStyle("nav", styles.default);
519
+ const StyledSection = addStyle("section", styles.default);
520
+ React.forwardRef(function View(props, ref) {
521
+ const {
522
+ testId,
523
+ tag = "div"
524
+ } = props,
525
+ restProps = _objectWithoutPropertiesLoose(props, _excluded);
526
+ const commonProps = _extends$1({}, restProps, {
527
+ "data-test-id": testId
528
+ });
529
+ switch (tag) {
530
+ case "article":
531
+ return React.createElement(StyledArticle, _extends$1({}, commonProps, {
532
+ ref: ref
533
+ }));
534
+ case "aside":
535
+ return React.createElement(StyledAside, _extends$1({}, commonProps, {
536
+ ref: ref
537
+ }));
538
+ case "nav":
539
+ return React.createElement(StyledNav, _extends$1({}, commonProps, {
540
+ ref: ref
541
+ }));
542
+ case "section":
543
+ return React.createElement(StyledSection, _extends$1({}, commonProps, {
544
+ ref: ref
545
+ }));
546
+ case "div":
547
+ return React.createElement(StyledDiv, _extends$1({}, commonProps, {
548
+ ref: ref
549
+ }));
550
+ default:
551
+ throw Error(`${tag} is not an allowed value for the 'tag' prop`);
552
+ }
553
+ });
554
+ let RenderState = function (RenderState) {
555
+ RenderState["Root"] = "root";
556
+ RenderState["Initial"] = "initial";
557
+ RenderState["Standard"] = "standard";
558
+ return RenderState;
559
+ }({});
560
+ const RenderStateContext = React.createContext(RenderState.Root);
561
+ RenderStateContext.displayName = "RenderStateContext";
562
+ class WithSSRPlaceholder extends React.Component {
563
+ constructor(...args) {
564
+ super(...args);
565
+ this.state = {
566
+ mounted: false
567
+ };
568
+ this._isTheRootComponent = false;
569
+ }
570
+ componentDidMount() {
571
+ if (this._isTheRootComponent) {
572
+ this.setState({
573
+ mounted: true
574
+ });
575
+ }
576
+ }
577
+ _renderAsRootComponent() {
578
+ const {
579
+ mounted
580
+ } = this.state;
581
+ const {
582
+ children,
583
+ placeholder
584
+ } = this.props;
585
+ this._isTheRootComponent = true;
586
+ if (mounted) {
587
+ return React.createElement(RenderStateContext.Provider, {
588
+ value: RenderState.Standard
589
+ }, children());
590
+ }
591
+ if (placeholder) {
592
+ return React.createElement(RenderStateContext.Provider, {
593
+ value: RenderState.Initial
594
+ }, placeholder());
595
+ }
596
+ return null;
597
+ }
598
+ _maybeRender(renderState) {
599
+ const {
600
+ children,
601
+ placeholder
602
+ } = this.props;
603
+ switch (renderState) {
604
+ case RenderState.Root:
605
+ return this._renderAsRootComponent();
606
+ case RenderState.Initial:
607
+ if (placeholder) {
608
+ return placeholder();
609
+ }
610
+ return null;
611
+ case RenderState.Standard:
612
+ return children();
613
+ }
614
+ {
615
+ var _JSON$stringify;
616
+ console.log(`We got a render state we don't understand: "${(_JSON$stringify = JSON.stringify(renderState)) != null ? _JSON$stringify : ""}"`);
617
+ return this._maybeRender(RenderState.Root);
618
+ }
619
+ }
620
+ render() {
621
+ return React.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
622
+ }
623
+ }
624
+ class UniqueIDFactory {
625
+ constructor(scope) {
626
+ this._uniqueFactoryName = void 0;
627
+ this.get = key => {
628
+ const normalizedKey = key.toLowerCase();
629
+ if (!this._hasValidIdChars(key)) {
630
+ throw new Error(`Invalid identifier key: ${key}`);
631
+ }
632
+ return `${this._uniqueFactoryName}-${normalizedKey}`;
633
+ };
634
+ scope = typeof scope === "string" ? scope : "";
635
+ const normalizedScope = scope.toLowerCase();
636
+ if (!this._hasValidIdChars(normalizedScope)) {
637
+ throw new Error(`Invalid factory scope: ${scope}`);
638
+ }
639
+ this._uniqueFactoryName = `uid-${normalizedScope}-${UniqueIDFactory._factoryUniquenessCounter++}`;
640
+ }
641
+ _hasValidIdChars(value) {
642
+ if (typeof value !== "string") {
643
+ return false;
644
+ }
645
+ const invalidCharsReplaced = value.replace(/[^\d\w-]/g, "-");
646
+ return value === invalidCharsReplaced;
647
+ }
648
+ }
649
+ UniqueIDFactory._factoryUniquenessCounter = 0;
650
+ class SsrIDFactory {
651
+ get(id) {
652
+ return id;
653
+ }
654
+ }
655
+ SsrIDFactory.Default = new SsrIDFactory();
656
+ var SsrIDFactory$1 = SsrIDFactory.Default;
657
+ class UniqueIDProvider extends React.Component {
658
+ constructor(...args) {
659
+ super(...args);
660
+ this._idFactory = void 0;
661
+ }
662
+ _performRender(firstRender) {
663
+ const {
664
+ children,
665
+ mockOnFirstRender,
666
+ scope
667
+ } = this.props;
668
+ if (firstRender) {
669
+ if (mockOnFirstRender) {
670
+ return children(SsrIDFactory$1);
671
+ }
672
+ return null;
673
+ }
674
+ if (!this._idFactory) {
675
+ this._idFactory = new UniqueIDFactory(scope);
676
+ }
677
+ return children(this._idFactory);
678
+ }
679
+ render() {
680
+ return React.createElement(WithSSRPlaceholder, {
681
+ placeholder: () => this._performRender(true)
682
+ }, () => this._performRender(false));
683
+ }
684
+ }
685
+ class IDProvider extends React.Component {
686
+ renderChildren(ids) {
687
+ const {
688
+ id,
689
+ children
690
+ } = this.props;
691
+ const uniqueId = ids ? ids.get(IDProvider.defaultId) : id;
692
+ if (!uniqueId) {
693
+ throw new Error("Did not get an identifier factory nor a id prop");
694
+ }
695
+ return children(uniqueId);
696
+ }
697
+ render() {
698
+ const {
699
+ id,
700
+ scope
701
+ } = this.props;
702
+ if (id) {
703
+ return this.renderChildren();
704
+ } else {
705
+ return React.createElement(UniqueIDProvider, {
706
+ scope: scope,
707
+ mockOnFirstRender: true
708
+ }, ids => this.renderChildren(ids));
709
+ }
710
+ }
711
+ }
712
+ IDProvider.defaultId = "wb-id";
713
+ const useRenderState = () => useContext(RenderStateContext);
714
+ const {
715
+ useEffect,
716
+ useState
717
+ } = React;
718
+ const RenderStateRoot = ({
719
+ children,
720
+ throwIfNested: _throwIfNested = true
721
+ }) => {
722
+ const [firstRender, setFirstRender] = useState(true);
723
+ const renderState = useRenderState();
724
+ useEffect(() => {
725
+ setFirstRender(false);
726
+ }, []);
727
+ if (renderState !== RenderState.Root) {
728
+ if (_throwIfNested) {
729
+ throw new Error("There's already a <RenderStateRoot> above this instance in " + "the render tree. This instance should be removed.");
730
+ }
731
+ return React.createElement(React.Fragment, null, children);
732
+ }
733
+ const value = firstRender ? RenderState.Initial : RenderState.Standard;
734
+ return React.createElement(RenderStateContext.Provider, {
735
+ value: value
736
+ }, children);
737
+ };
738
+
739
+ const defaultConfig = null;
740
+ const adapter = (children, config) => {
741
+ if (config !== true) {
742
+ throw new KindError("Unexpected configuration: set config to null to turn this adapter off", Errors.InvalidInput, {
743
+ metadata: {
744
+ config
745
+ }
746
+ });
747
+ }
748
+ return React.createElement(RenderStateRoot, null, children);
749
+ };
750
+
360
751
  const DefaultAdapters = {
361
- css: adapter$3,
362
- data: adapter$2,
363
- portal: adapter$1,
364
- router: adapter
752
+ css: adapter$4,
753
+ data: adapter$3,
754
+ portal: adapter$2,
755
+ router: adapter$1,
756
+ ssr: adapter
365
757
  };
366
758
  const DefaultConfigs = {
367
- css: defaultConfig$3,
368
- data: defaultConfig$2,
369
- portal: defaultConfig$1,
370
- router: defaultConfig
759
+ css: defaultConfig$4,
760
+ data: defaultConfig$3,
761
+ portal: defaultConfig$2,
762
+ router: defaultConfig$1,
763
+ ssr: defaultConfig
371
764
  };
372
765
 
373
766
  var adapters = /*#__PURE__*/Object.freeze({
@@ -391,22 +784,46 @@ function _extends() {
391
784
  return _extends.apply(this, arguments);
392
785
  }
393
786
 
394
- const renderAdapters = (adapters, configs, children) => {
395
- let currentChildren = children;
396
- for (const adapterName of Object.keys(adapters)) {
397
- const adapter = adapters[adapterName];
398
- const config = configs[adapterName];
399
- if (config != null) {
400
- currentChildren = adapter(currentChildren, config);
401
- }
787
+ const Adapter = ({
788
+ children,
789
+ adapter,
790
+ config
791
+ }) => {
792
+ if (config == null) {
793
+ return React.createElement(React.Fragment, null, children);
794
+ }
795
+ return adapter(children, config);
796
+ };
797
+
798
+ const Adapt = ({
799
+ children,
800
+ adapters,
801
+ configs
802
+ }) => {
803
+ const thisAdapterName = Object.keys(adapters).at(-1);
804
+ if (thisAdapterName == null) {
805
+ return React.createElement(React.Fragment, null, children);
402
806
  }
403
- return currentChildren;
807
+ const thisAdapter = adapters[thisAdapterName];
808
+ const thisConfig = configs[thisAdapterName];
809
+ const restAdapters = Object.fromEntries(Object.entries(adapters).slice(0, -1));
810
+ const restConfigs = Object.fromEntries(Object.entries(configs).filter(([name]) => name !== thisAdapterName));
811
+ return React.createElement(Adapter, {
812
+ adapter: thisAdapter,
813
+ config: thisConfig
814
+ }, React.createElement(Adapt, {
815
+ adapters: restAdapters,
816
+ configs: restConfigs
817
+ }, children));
404
818
  };
405
819
 
406
820
  const makeTestHarness = (adapters, defaultConfigs) => {
407
821
  return (Component, configs) => {
408
822
  const fullConfig = _extends({}, defaultConfigs, configs);
409
- const harnessedComponent = React.forwardRef((props, ref) => renderAdapters(adapters, fullConfig, React.createElement(Component, _extends({}, props, {
823
+ const harnessedComponent = React.forwardRef((props, ref) => React.createElement(Adapt, {
824
+ adapters: adapters,
825
+ configs: fullConfig
826
+ }, React.createElement(Component, _extends({}, props, {
410
827
  ref: ref
411
828
  }))));
412
829
  harnessedComponent.displayName = `testHarness(${Component.displayName || Component.name || "Component"})`;
@@ -416,7 +833,7 @@ const makeTestHarness = (adapters, defaultConfigs) => {
416
833
 
417
834
  const HookHarness = ({
418
835
  children
419
- }) => children;
836
+ }) => React.createElement(React.Fragment, null, children);
420
837
  const makeHookHarness = (adapters, defaultConfigs) => {
421
838
  const testHarness = makeTestHarness(adapters, defaultConfigs);
422
839
  return configs => testHarness(HookHarness, configs);
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import type { TestHarnessConfigs, TestHarnessAdapters } from "./types";
3
+ type Props<TAdapters extends TestHarnessAdapters> = {
4
+ children: React.ReactNode;
5
+ adapters: TAdapters;
6
+ configs: TestHarnessConfigs<TAdapters>;
7
+ };
8
+ /**
9
+ * Render a set of adapters around the given children.
10
+ *
11
+ * Adapters are rendered with the last adapter being the outermost and the first
12
+ * adapter being the innermost, with children being the innermost of all. This
13
+ * ensures that we are backwards compatible with previous releases of the
14
+ * test harness.
15
+ */
16
+ export declare const Adapt: <TAdapters extends TestHarnessAdapters>({ children, adapters, configs, }: Props<TAdapters>) => React.ReactElement;
17
+ export {};
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import type { TestHarnessAdapter } from "./types";
3
+ type Props<TConfig, TAdapter extends TestHarnessAdapter<TConfig>> = {
4
+ children: React.ReactNode;
5
+ adapter: TAdapter;
6
+ config: TConfig | null | undefined;
7
+ };
8
+ /**
9
+ * Component that optionally renders a given adapter with the given config.
10
+ *
11
+ * If the config is nullish, then the children are rendered in a fragment,
12
+ * otherwise the children are rendered within the given adapter with the
13
+ * given config.
14
+ */
15
+ export declare const Adapter: <TConfig, TAdapter extends TestHarnessAdapter<TConfig>>({ children, adapter, config, }: Props<TConfig, TAdapter>) => React.ReactElement;
16
+ export {};
@@ -28,6 +28,7 @@ export declare const DefaultAdapters: {
28
28
  location: import("history").LocationDescriptor<unknown>;
29
29
  path?: string | undefined;
30
30
  }>>;
31
+ readonly ssr: import("../types").TestHarnessAdapter<true | null>;
31
32
  };
32
33
  /**
33
34
  * The default configurations to use with the `DefaultAdapters`.
@@ -0,0 +1,12 @@
1
+ import type { TestHarnessAdapter } from "../types";
2
+ type Config = true | null;
3
+ export declare const defaultConfig: Config;
4
+ /**
5
+ * Test harness adapter for supporting portals.
6
+ *
7
+ * Some components rely on rendering with a React Portal. This adapter ensures
8
+ * that the DOM contains a mounting point for the portal with the expected
9
+ * identifier.
10
+ */
11
+ export declare const adapter: TestHarnessAdapter<Config>;
12
+ export {};
@@ -29,4 +29,5 @@ export declare const testHarness: <TProps extends object>(Component: import("rea
29
29
  location: import("history").LocationDescriptor<unknown>;
30
30
  path?: string | undefined;
31
31
  }>>;
32
+ readonly ssr: import("./types").TestHarnessAdapter<true | null>;
32
33
  }>> | undefined) => import("react").ForwardRefExoticComponent<import("react").PropsWithoutRef<TProps> & import("react").RefAttributes<unknown>>;
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  /**
3
3
  * A adapter to be composed with our test harness infrastructure.
4
4
  */
5
- export type TestHarnessAdapter<TConfig> = (children: React.ReactNode, config: TConfig) => React.ReactElement<any>;
5
+ export type TestHarnessAdapter<TConfig> = (children: React.ReactNode, config: TConfig) => React.ReactElement;
6
6
  /**
7
7
  * A general map of adapters by their identifiers.
8
8
  *