@khanacademy/wonder-blocks-testing 9.0.0 → 9.2.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.
- package/CHANGELOG.md +19 -0
- package/dist/es/index.js +21 -390
- package/dist/harness/adapt.d.ts +17 -0
- package/dist/index.js +22 -390
- package/package.json +3 -2
- package/src/harness/__tests__/adapt.test.tsx +200 -0
- package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
- package/src/harness/adapt.tsx +36 -0
- package/src/harness/adapters/__tests__/ssr.test.tsx +46 -5
- package/src/harness/adapters/ssr.tsx +7 -3
- package/src/harness/make-test-harness.tsx +6 -8
- package/tsconfig-build.json +1 -0
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/harness/render-adapters.d.ts +0 -6
- package/src/harness/__tests__/render-adapters.test.tsx +0 -97
- package/src/harness/render-adapters.tsx +0 -26
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var addonActions = require('@storybook/addon-actions');
|
|
|
7
7
|
var wonderBlocksData = require('@khanacademy/wonder-blocks-data');
|
|
8
8
|
var reactRouterDom = require('react-router-dom');
|
|
9
9
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
10
|
-
var
|
|
10
|
+
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
11
11
|
|
|
12
12
|
function _interopNamespace(e) {
|
|
13
13
|
if (e && e.__esModule) return e;
|
|
@@ -384,391 +384,16 @@ const adapter$1 = (children, config) => {
|
|
|
384
384
|
return React__namespace.createElement(reactRouterDom.MemoryRouter, routerProps, wrappedWithRoute);
|
|
385
385
|
};
|
|
386
386
|
|
|
387
|
-
function _extends$1() {
|
|
388
|
-
_extends$1 = Object.assign ? Object.assign.bind() : function (target) {
|
|
389
|
-
for (var i = 1; i < arguments.length; i++) {
|
|
390
|
-
var source = arguments[i];
|
|
391
|
-
for (var key in source) {
|
|
392
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
393
|
-
target[key] = source[key];
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return target;
|
|
398
|
-
};
|
|
399
|
-
return _extends$1.apply(this, arguments);
|
|
400
|
-
}
|
|
401
|
-
function _objectWithoutPropertiesLoose(source, excluded) {
|
|
402
|
-
if (source == null) return {};
|
|
403
|
-
var target = {};
|
|
404
|
-
var sourceKeys = Object.keys(source);
|
|
405
|
-
var key, i;
|
|
406
|
-
for (i = 0; i < sourceKeys.length; i++) {
|
|
407
|
-
key = sourceKeys[i];
|
|
408
|
-
if (excluded.indexOf(key) >= 0) continue;
|
|
409
|
-
target[key] = source[key];
|
|
410
|
-
}
|
|
411
|
-
return target;
|
|
412
|
-
}
|
|
413
|
-
function flatten(list) {
|
|
414
|
-
const result = [];
|
|
415
|
-
if (!list) {
|
|
416
|
-
return result;
|
|
417
|
-
} else if (Array.isArray(list)) {
|
|
418
|
-
for (const item of list) {
|
|
419
|
-
result.push(...flatten(item));
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
result.push(list);
|
|
423
|
-
}
|
|
424
|
-
return result;
|
|
425
|
-
}
|
|
426
|
-
function processStyleList(style) {
|
|
427
|
-
const stylesheetStyles = [];
|
|
428
|
-
const inlineStyles = [];
|
|
429
|
-
if (!style) {
|
|
430
|
-
return {
|
|
431
|
-
style: {},
|
|
432
|
-
className: ""
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
const shouldInlineStyles = typeof global !== "undefined" && global.SNAPSHOT_INLINE_APHRODITE;
|
|
436
|
-
flatten(style).forEach(child => {
|
|
437
|
-
const _definition = child._definition;
|
|
438
|
-
if (_definition != null) {
|
|
439
|
-
if (shouldInlineStyles) {
|
|
440
|
-
const def = {};
|
|
441
|
-
for (const [key, value] of Object.entries(_definition)) {
|
|
442
|
-
def[key.replace(/-[a-z]/g, match => match[1].toUpperCase())] = value;
|
|
443
|
-
}
|
|
444
|
-
inlineStyles.push(def);
|
|
445
|
-
} else {
|
|
446
|
-
stylesheetStyles.push(child);
|
|
447
|
-
}
|
|
448
|
-
} else {
|
|
449
|
-
inlineStyles.push(child);
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
const inlineStylesObject = Object.assign({}, ...inlineStyles);
|
|
453
|
-
if (inlineStyles.length > 0 && !shouldInlineStyles) {
|
|
454
|
-
const inlineStylesStyleSheet = aphrodite.StyleSheet.create({
|
|
455
|
-
inlineStyles: inlineStylesObject
|
|
456
|
-
});
|
|
457
|
-
stylesheetStyles.push(inlineStylesStyleSheet.inlineStyles);
|
|
458
|
-
}
|
|
459
|
-
return {
|
|
460
|
-
style: shouldInlineStyles ? inlineStylesObject : {},
|
|
461
|
-
className: aphrodite.css(...stylesheetStyles)
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
const _excluded$2 = ["children", "style", "tag", "testId"];
|
|
465
|
-
const isHeaderRegex = /^h[1-6]$/;
|
|
466
|
-
const styles$1 = aphrodite.StyleSheet.create({
|
|
467
|
-
text: {
|
|
468
|
-
WebkitFontSmoothing: "antialiased",
|
|
469
|
-
MozOsxFontSmoothing: "grayscale"
|
|
470
|
-
},
|
|
471
|
-
header: {
|
|
472
|
-
marginTop: 0,
|
|
473
|
-
marginBottom: 0
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
React__namespace.forwardRef(function Text(_ref, ref) {
|
|
477
|
-
let {
|
|
478
|
-
children,
|
|
479
|
-
style,
|
|
480
|
-
tag: Tag = "span",
|
|
481
|
-
testId
|
|
482
|
-
} = _ref,
|
|
483
|
-
otherProps = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
484
|
-
const isHeader = isHeaderRegex.test(Tag);
|
|
485
|
-
const styleAttributes = processStyleList([styles$1.text, isHeader && styles$1.header, style]);
|
|
486
|
-
return React__namespace.createElement(Tag, _extends$1({}, otherProps, {
|
|
487
|
-
style: styleAttributes.style,
|
|
488
|
-
className: styleAttributes.className,
|
|
489
|
-
"data-test-id": testId,
|
|
490
|
-
ref: ref
|
|
491
|
-
}), children);
|
|
492
|
-
});
|
|
493
|
-
const _excluded$1 = ["className", "style"];
|
|
494
|
-
function addStyle(Component, defaultStyle) {
|
|
495
|
-
return React__namespace.forwardRef((props, ref) => {
|
|
496
|
-
const {
|
|
497
|
-
className,
|
|
498
|
-
style
|
|
499
|
-
} = props,
|
|
500
|
-
otherProps = _objectWithoutPropertiesLoose(props, _excluded$1);
|
|
501
|
-
const reset = typeof Component === "string" ? overrides[Component] : null;
|
|
502
|
-
const {
|
|
503
|
-
className: aphroditeClassName,
|
|
504
|
-
style: inlineStyles
|
|
505
|
-
} = processStyleList([reset, defaultStyle, style]);
|
|
506
|
-
return React__namespace.createElement(Component, _extends$1({}, otherProps, {
|
|
507
|
-
ref: ref,
|
|
508
|
-
className: [aphroditeClassName, className].filter(Boolean).join(" "),
|
|
509
|
-
style: inlineStyles
|
|
510
|
-
}));
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
const overrides = aphrodite.StyleSheet.create({
|
|
514
|
-
button: {
|
|
515
|
-
margin: 0,
|
|
516
|
-
"::-moz-focus-inner": {
|
|
517
|
-
border: 0
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
const _excluded = ["testId", "tag"];
|
|
522
|
-
const styles = aphrodite.StyleSheet.create({
|
|
523
|
-
default: {
|
|
524
|
-
alignItems: "stretch",
|
|
525
|
-
borderWidth: 0,
|
|
526
|
-
borderStyle: "solid",
|
|
527
|
-
boxSizing: "border-box",
|
|
528
|
-
display: "flex",
|
|
529
|
-
flexDirection: "column",
|
|
530
|
-
margin: 0,
|
|
531
|
-
padding: 0,
|
|
532
|
-
position: "relative",
|
|
533
|
-
zIndex: 0,
|
|
534
|
-
minHeight: 0,
|
|
535
|
-
minWidth: 0
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
const StyledDiv = addStyle("div", styles.default);
|
|
539
|
-
const StyledArticle = addStyle("article", styles.default);
|
|
540
|
-
const StyledAside = addStyle("aside", styles.default);
|
|
541
|
-
const StyledNav = addStyle("nav", styles.default);
|
|
542
|
-
const StyledSection = addStyle("section", styles.default);
|
|
543
|
-
React__namespace.forwardRef(function View(props, ref) {
|
|
544
|
-
const {
|
|
545
|
-
testId,
|
|
546
|
-
tag = "div"
|
|
547
|
-
} = props,
|
|
548
|
-
restProps = _objectWithoutPropertiesLoose(props, _excluded);
|
|
549
|
-
const commonProps = _extends$1({}, restProps, {
|
|
550
|
-
"data-test-id": testId
|
|
551
|
-
});
|
|
552
|
-
switch (tag) {
|
|
553
|
-
case "article":
|
|
554
|
-
return React__namespace.createElement(StyledArticle, _extends$1({}, commonProps, {
|
|
555
|
-
ref: ref
|
|
556
|
-
}));
|
|
557
|
-
case "aside":
|
|
558
|
-
return React__namespace.createElement(StyledAside, _extends$1({}, commonProps, {
|
|
559
|
-
ref: ref
|
|
560
|
-
}));
|
|
561
|
-
case "nav":
|
|
562
|
-
return React__namespace.createElement(StyledNav, _extends$1({}, commonProps, {
|
|
563
|
-
ref: ref
|
|
564
|
-
}));
|
|
565
|
-
case "section":
|
|
566
|
-
return React__namespace.createElement(StyledSection, _extends$1({}, commonProps, {
|
|
567
|
-
ref: ref
|
|
568
|
-
}));
|
|
569
|
-
case "div":
|
|
570
|
-
return React__namespace.createElement(StyledDiv, _extends$1({}, commonProps, {
|
|
571
|
-
ref: ref
|
|
572
|
-
}));
|
|
573
|
-
default:
|
|
574
|
-
throw Error(`${tag} is not an allowed value for the 'tag' prop`);
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
let RenderState = function (RenderState) {
|
|
578
|
-
RenderState["Root"] = "root";
|
|
579
|
-
RenderState["Initial"] = "initial";
|
|
580
|
-
RenderState["Standard"] = "standard";
|
|
581
|
-
return RenderState;
|
|
582
|
-
}({});
|
|
583
|
-
const RenderStateContext = React__namespace.createContext(RenderState.Root);
|
|
584
|
-
RenderStateContext.displayName = "RenderStateContext";
|
|
585
|
-
class WithSSRPlaceholder extends React__namespace.Component {
|
|
586
|
-
constructor(...args) {
|
|
587
|
-
super(...args);
|
|
588
|
-
this.state = {
|
|
589
|
-
mounted: false
|
|
590
|
-
};
|
|
591
|
-
this._isTheRootComponent = false;
|
|
592
|
-
}
|
|
593
|
-
componentDidMount() {
|
|
594
|
-
if (this._isTheRootComponent) {
|
|
595
|
-
this.setState({
|
|
596
|
-
mounted: true
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
_renderAsRootComponent() {
|
|
601
|
-
const {
|
|
602
|
-
mounted
|
|
603
|
-
} = this.state;
|
|
604
|
-
const {
|
|
605
|
-
children,
|
|
606
|
-
placeholder
|
|
607
|
-
} = this.props;
|
|
608
|
-
this._isTheRootComponent = true;
|
|
609
|
-
if (mounted) {
|
|
610
|
-
return React__namespace.createElement(RenderStateContext.Provider, {
|
|
611
|
-
value: RenderState.Standard
|
|
612
|
-
}, children());
|
|
613
|
-
}
|
|
614
|
-
if (placeholder) {
|
|
615
|
-
return React__namespace.createElement(RenderStateContext.Provider, {
|
|
616
|
-
value: RenderState.Initial
|
|
617
|
-
}, placeholder());
|
|
618
|
-
}
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
_maybeRender(renderState) {
|
|
622
|
-
const {
|
|
623
|
-
children,
|
|
624
|
-
placeholder
|
|
625
|
-
} = this.props;
|
|
626
|
-
switch (renderState) {
|
|
627
|
-
case RenderState.Root:
|
|
628
|
-
return this._renderAsRootComponent();
|
|
629
|
-
case RenderState.Initial:
|
|
630
|
-
if (placeholder) {
|
|
631
|
-
return placeholder();
|
|
632
|
-
}
|
|
633
|
-
return null;
|
|
634
|
-
case RenderState.Standard:
|
|
635
|
-
return children();
|
|
636
|
-
}
|
|
637
|
-
{
|
|
638
|
-
var _JSON$stringify;
|
|
639
|
-
console.log(`We got a render state we don't understand: "${(_JSON$stringify = JSON.stringify(renderState)) != null ? _JSON$stringify : ""}"`);
|
|
640
|
-
return this._maybeRender(RenderState.Root);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
render() {
|
|
644
|
-
return React__namespace.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
class UniqueIDFactory {
|
|
648
|
-
constructor(scope) {
|
|
649
|
-
this._uniqueFactoryName = void 0;
|
|
650
|
-
this.get = key => {
|
|
651
|
-
const normalizedKey = key.toLowerCase();
|
|
652
|
-
if (!this._hasValidIdChars(key)) {
|
|
653
|
-
throw new Error(`Invalid identifier key: ${key}`);
|
|
654
|
-
}
|
|
655
|
-
return `${this._uniqueFactoryName}-${normalizedKey}`;
|
|
656
|
-
};
|
|
657
|
-
scope = typeof scope === "string" ? scope : "";
|
|
658
|
-
const normalizedScope = scope.toLowerCase();
|
|
659
|
-
if (!this._hasValidIdChars(normalizedScope)) {
|
|
660
|
-
throw new Error(`Invalid factory scope: ${scope}`);
|
|
661
|
-
}
|
|
662
|
-
this._uniqueFactoryName = `uid-${normalizedScope}-${UniqueIDFactory._factoryUniquenessCounter++}`;
|
|
663
|
-
}
|
|
664
|
-
_hasValidIdChars(value) {
|
|
665
|
-
if (typeof value !== "string") {
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
const invalidCharsReplaced = value.replace(/[^\d\w-]/g, "-");
|
|
669
|
-
return value === invalidCharsReplaced;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
UniqueIDFactory._factoryUniquenessCounter = 0;
|
|
673
|
-
class SsrIDFactory {
|
|
674
|
-
get(id) {
|
|
675
|
-
return id;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
SsrIDFactory.Default = new SsrIDFactory();
|
|
679
|
-
var SsrIDFactory$1 = SsrIDFactory.Default;
|
|
680
|
-
class UniqueIDProvider extends React__namespace.Component {
|
|
681
|
-
constructor(...args) {
|
|
682
|
-
super(...args);
|
|
683
|
-
this._idFactory = void 0;
|
|
684
|
-
}
|
|
685
|
-
_performRender(firstRender) {
|
|
686
|
-
const {
|
|
687
|
-
children,
|
|
688
|
-
mockOnFirstRender,
|
|
689
|
-
scope
|
|
690
|
-
} = this.props;
|
|
691
|
-
if (firstRender) {
|
|
692
|
-
if (mockOnFirstRender) {
|
|
693
|
-
return children(SsrIDFactory$1);
|
|
694
|
-
}
|
|
695
|
-
return null;
|
|
696
|
-
}
|
|
697
|
-
if (!this._idFactory) {
|
|
698
|
-
this._idFactory = new UniqueIDFactory(scope);
|
|
699
|
-
}
|
|
700
|
-
return children(this._idFactory);
|
|
701
|
-
}
|
|
702
|
-
render() {
|
|
703
|
-
return React__namespace.createElement(WithSSRPlaceholder, {
|
|
704
|
-
placeholder: () => this._performRender(true)
|
|
705
|
-
}, () => this._performRender(false));
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
class IDProvider extends React__namespace.Component {
|
|
709
|
-
renderChildren(ids) {
|
|
710
|
-
const {
|
|
711
|
-
id,
|
|
712
|
-
children
|
|
713
|
-
} = this.props;
|
|
714
|
-
const uniqueId = ids ? ids.get(IDProvider.defaultId) : id;
|
|
715
|
-
if (!uniqueId) {
|
|
716
|
-
throw new Error("Did not get an identifier factory nor a id prop");
|
|
717
|
-
}
|
|
718
|
-
return children(uniqueId);
|
|
719
|
-
}
|
|
720
|
-
render() {
|
|
721
|
-
const {
|
|
722
|
-
id,
|
|
723
|
-
scope
|
|
724
|
-
} = this.props;
|
|
725
|
-
if (id) {
|
|
726
|
-
return this.renderChildren();
|
|
727
|
-
} else {
|
|
728
|
-
return React__namespace.createElement(UniqueIDProvider, {
|
|
729
|
-
scope: scope,
|
|
730
|
-
mockOnFirstRender: true
|
|
731
|
-
}, ids => this.renderChildren(ids));
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
IDProvider.defaultId = "wb-id";
|
|
736
|
-
const useRenderState = () => React.useContext(RenderStateContext);
|
|
737
|
-
const {
|
|
738
|
-
useEffect,
|
|
739
|
-
useState
|
|
740
|
-
} = React__namespace;
|
|
741
|
-
const RenderStateRoot = ({
|
|
742
|
-
children,
|
|
743
|
-
throwIfNested: _throwIfNested = true
|
|
744
|
-
}) => {
|
|
745
|
-
const [firstRender, setFirstRender] = useState(true);
|
|
746
|
-
const renderState = useRenderState();
|
|
747
|
-
useEffect(() => {
|
|
748
|
-
setFirstRender(false);
|
|
749
|
-
}, []);
|
|
750
|
-
if (renderState !== RenderState.Root) {
|
|
751
|
-
if (_throwIfNested) {
|
|
752
|
-
throw new Error("There's already a <RenderStateRoot> above this instance in " + "the render tree. This instance should be removed.");
|
|
753
|
-
}
|
|
754
|
-
return React__namespace.createElement(React__namespace.Fragment, null, children);
|
|
755
|
-
}
|
|
756
|
-
const value = firstRender ? RenderState.Initial : RenderState.Standard;
|
|
757
|
-
return React__namespace.createElement(RenderStateContext.Provider, {
|
|
758
|
-
value: value
|
|
759
|
-
}, children);
|
|
760
|
-
};
|
|
761
|
-
|
|
762
387
|
const defaultConfig = null;
|
|
763
388
|
const adapter = (children, config) => {
|
|
764
389
|
if (config !== true) {
|
|
765
|
-
throw new wonderStuffCore.KindError("Unexpected
|
|
390
|
+
throw new wonderStuffCore.KindError("Unexpected configuration: set config to null to turn this adapter off", wonderStuffCore.Errors.InvalidInput, {
|
|
766
391
|
metadata: {
|
|
767
392
|
config
|
|
768
393
|
}
|
|
769
394
|
});
|
|
770
395
|
}
|
|
771
|
-
return React__namespace.createElement(RenderStateRoot, null, children);
|
|
396
|
+
return React__namespace.createElement(wonderBlocksCore.RenderStateRoot, null, children);
|
|
772
397
|
};
|
|
773
398
|
|
|
774
399
|
const DefaultAdapters = {
|
|
@@ -807,22 +432,29 @@ function _extends() {
|
|
|
807
432
|
return _extends.apply(this, arguments);
|
|
808
433
|
}
|
|
809
434
|
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
|
|
435
|
+
const Adapt = ({
|
|
436
|
+
children,
|
|
437
|
+
adapters,
|
|
438
|
+
configs
|
|
439
|
+
}) => Object.entries(adapters).reduce((newChildren, [name, adapter]) => {
|
|
440
|
+
const config = configs[name];
|
|
441
|
+
if (config == null) {
|
|
442
|
+
return newChildren;
|
|
443
|
+
}
|
|
444
|
+
const Adapter = ({
|
|
445
|
+
children
|
|
446
|
+
}) => adapter(children, config);
|
|
447
|
+
Adapter.displayName = `Adapter(${name})`;
|
|
448
|
+
return React__namespace.createElement(Adapter, null, newChildren);
|
|
449
|
+
}, React__namespace.createElement(React__namespace.Fragment, null, children));
|
|
821
450
|
|
|
822
451
|
const makeTestHarness = (adapters, defaultConfigs) => {
|
|
823
452
|
return (Component, configs) => {
|
|
824
453
|
const fullConfig = _extends({}, defaultConfigs, configs);
|
|
825
|
-
const harnessedComponent = React__namespace.forwardRef((props, ref) =>
|
|
454
|
+
const harnessedComponent = React__namespace.forwardRef((props, ref) => React__namespace.createElement(Adapt, {
|
|
455
|
+
adapters: adapters,
|
|
456
|
+
configs: fullConfig
|
|
457
|
+
}, React__namespace.createElement(Component, _extends({}, props, {
|
|
826
458
|
ref: ref
|
|
827
459
|
}))));
|
|
828
460
|
harnessedComponent.displayName = `testHarness(${Component.displayName || Component.name || "Component"})`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@babel/runtime": "^7.18.6",
|
|
17
|
-
"@khanacademy/wonder-blocks-
|
|
17
|
+
"@khanacademy/wonder-blocks-core": "^6.0.2",
|
|
18
|
+
"@khanacademy/wonder-blocks-data": "^13.0.1"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|
|
20
21
|
"@khanacademy/wonder-stuff-core": "^1.2.2",
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {render} from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import {Adapt} from "../adapt";
|
|
5
|
+
|
|
6
|
+
import type {TestHarnessAdapter, TestHarnessConfigs} from "../types";
|
|
7
|
+
|
|
8
|
+
describe("Adapt", () => {
|
|
9
|
+
it("should render children if no adapters", () => {
|
|
10
|
+
// Arrange
|
|
11
|
+
const children = <div>Adapt me!</div>;
|
|
12
|
+
|
|
13
|
+
// Act
|
|
14
|
+
const {container: result} = render(
|
|
15
|
+
<Adapt adapters={{}} configs={{}}>
|
|
16
|
+
{children}
|
|
17
|
+
</Adapt>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Assert
|
|
21
|
+
expect(result).toMatchInlineSnapshot(`
|
|
22
|
+
<div>
|
|
23
|
+
<div>
|
|
24
|
+
Adapt me!
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should invoke the adapter with its corresponding config", () => {
|
|
31
|
+
// Arrange
|
|
32
|
+
const children = <div>Adapt me!</div>;
|
|
33
|
+
const adapters = {
|
|
34
|
+
adapterA: jest
|
|
35
|
+
.fn()
|
|
36
|
+
.mockReturnValue("ADAPTER A") as TestHarnessAdapter<string>,
|
|
37
|
+
} as const;
|
|
38
|
+
const configs: TestHarnessConfigs<typeof adapters> = {
|
|
39
|
+
adapterA: "APPLY A CONFIG",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Act
|
|
43
|
+
render(
|
|
44
|
+
<Adapt adapters={adapters} configs={configs}>
|
|
45
|
+
{children}
|
|
46
|
+
</Adapt>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect(adapters.adapterA).toHaveBeenCalledWith(
|
|
51
|
+
expect.anything(),
|
|
52
|
+
"APPLY A CONFIG",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should render each adapter and the children", () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
const children = "Adapt me!";
|
|
59
|
+
const adapter: TestHarnessAdapter<string> = (c: any, conf: any) => (
|
|
60
|
+
<>
|
|
61
|
+
{conf}
|
|
62
|
+
{c}
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
const adapters = {
|
|
66
|
+
adapterA: adapter,
|
|
67
|
+
adapterB: adapter,
|
|
68
|
+
adapterC: adapter,
|
|
69
|
+
} as const;
|
|
70
|
+
const configs: TestHarnessConfigs<typeof adapters> = {
|
|
71
|
+
adapterA: "A",
|
|
72
|
+
adapterB: "B",
|
|
73
|
+
adapterC: "C",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
const {container: result} = render(
|
|
78
|
+
<Adapt adapters={adapters} configs={configs}>
|
|
79
|
+
{children}
|
|
80
|
+
</Adapt>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
84
|
+
expect(result).toMatchInlineSnapshot(`
|
|
85
|
+
<div>
|
|
86
|
+
C
|
|
87
|
+
B
|
|
88
|
+
A
|
|
89
|
+
Adapt me!
|
|
90
|
+
</div>
|
|
91
|
+
`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should skip adapters where the corresponding config is null", () => {
|
|
95
|
+
// Arrange
|
|
96
|
+
const children = "Adapt me!";
|
|
97
|
+
const adapter: TestHarnessAdapter<string> = (c: any, conf: any) => (
|
|
98
|
+
<>
|
|
99
|
+
{conf}
|
|
100
|
+
{c}
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
const adapters = {
|
|
104
|
+
adapterA: adapter,
|
|
105
|
+
adapterB: adapter,
|
|
106
|
+
adapterC: adapter,
|
|
107
|
+
} as const;
|
|
108
|
+
const configs: TestHarnessConfigs<typeof adapters> = {
|
|
109
|
+
adapterA: "A",
|
|
110
|
+
adapterB: null,
|
|
111
|
+
adapterC: "C",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Act
|
|
115
|
+
const {container: result} = render(
|
|
116
|
+
<Adapt adapters={adapters} configs={configs}>
|
|
117
|
+
{children}
|
|
118
|
+
</Adapt>,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Assert
|
|
122
|
+
expect(result).toMatchInlineSnapshot(`
|
|
123
|
+
<div>
|
|
124
|
+
C
|
|
125
|
+
A
|
|
126
|
+
Adapt me!
|
|
127
|
+
</div>
|
|
128
|
+
`);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should render such that contexts are properly setup in order", () => {
|
|
132
|
+
// Arrange
|
|
133
|
+
const MyContext = React.createContext("root");
|
|
134
|
+
const ContextRoot = ({children}: any): React.ReactElement => {
|
|
135
|
+
const value = React.useContext(MyContext);
|
|
136
|
+
if (value !== "root") {
|
|
137
|
+
throw new Error("Don't nest this");
|
|
138
|
+
}
|
|
139
|
+
return (
|
|
140
|
+
<MyContext.Provider value={"other"}>
|
|
141
|
+
{children}
|
|
142
|
+
</MyContext.Provider>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
const adapterNoContext: TestHarnessAdapter<string> = (
|
|
146
|
+
c: any,
|
|
147
|
+
conf: any,
|
|
148
|
+
) => (
|
|
149
|
+
<>
|
|
150
|
+
{conf}
|
|
151
|
+
{c}
|
|
152
|
+
</>
|
|
153
|
+
);
|
|
154
|
+
const adapterWithContext: TestHarnessAdapter<string> = (
|
|
155
|
+
c: any,
|
|
156
|
+
conf: string,
|
|
157
|
+
) => (
|
|
158
|
+
<ContextRoot>
|
|
159
|
+
{conf}
|
|
160
|
+
{c}
|
|
161
|
+
</ContextRoot>
|
|
162
|
+
);
|
|
163
|
+
const adapters = {
|
|
164
|
+
adapterA: adapterNoContext,
|
|
165
|
+
adapterB: adapterWithContext,
|
|
166
|
+
adapterC: adapterNoContext,
|
|
167
|
+
} as const;
|
|
168
|
+
const configs: TestHarnessConfigs<typeof adapters> = {
|
|
169
|
+
adapterA: "A",
|
|
170
|
+
adapterB: "B",
|
|
171
|
+
adapterC: "C",
|
|
172
|
+
};
|
|
173
|
+
const ChildComponent = (props: any): React.ReactElement => {
|
|
174
|
+
const value = React.useContext(MyContext);
|
|
175
|
+
if (value === "default") {
|
|
176
|
+
throw new Error("Context not setup properly");
|
|
177
|
+
}
|
|
178
|
+
return <div>{value}</div>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Act
|
|
182
|
+
const {container: result} = render(
|
|
183
|
+
<Adapt adapters={adapters} configs={configs}>
|
|
184
|
+
<ChildComponent />
|
|
185
|
+
</Adapt>,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Assert
|
|
189
|
+
expect(result).toMatchInlineSnapshot(`
|
|
190
|
+
<div>
|
|
191
|
+
C
|
|
192
|
+
B
|
|
193
|
+
A
|
|
194
|
+
<div>
|
|
195
|
+
other
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`);
|
|
199
|
+
});
|
|
200
|
+
});
|