@khanacademy/wonder-blocks-testing 8.0.21 → 9.0.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 +11 -0
- package/dist/es/index.js +417 -24
- package/dist/harness/adapters/adapters.d.ts +1 -0
- package/dist/harness/adapters/ssr.d.ts +12 -0
- package/dist/harness/test-harness.d.ts +1 -0
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.js +416 -24
- package/dist/mock-requester.d.ts +2 -2
- package/package.json +2 -2
- package/src/fetch/fetch-request-matches-mock.ts +2 -4
- package/src/fetch/mock-fetch.ts +1 -1
- package/src/fixtures/__tests__/fixtures.test.tsx +9 -14
- package/src/fixtures/fixtures.basic.stories.tsx +9 -3
- package/src/fixtures/fixtures.tsx +1 -2
- package/src/gql/mock-gql-fetch.ts +1 -1
- package/src/harness/__tests__/hook-harness.test.ts +4 -2
- package/src/harness/__tests__/render-adapters.test.tsx +22 -12
- package/src/harness/__tests__/test-harness.test.ts +4 -2
- package/src/harness/__tests__/types.typestest.tsx +6 -13
- package/src/harness/adapters/__tests__/data.test.tsx +12 -4
- package/src/harness/adapters/__tests__/ssr.test.tsx +41 -0
- package/src/harness/adapters/adapters.ts +3 -0
- package/src/harness/adapters/css.tsx +1 -3
- package/src/harness/adapters/router.tsx +5 -17
- package/src/harness/adapters/ssr.tsx +33 -0
- package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
- package/src/harness/{render-adapters.ts → render-adapters.tsx} +1 -2
- package/src/harness/types.ts +1 -1
- package/src/mock-requester.ts +4 -11
- package/src/respond-with.ts +2 -1
- package/src/settle-controller.ts +2 -3
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ var React = require('react');
|
|
|
6
6
|
var addonActions = require('@storybook/addon-actions');
|
|
7
7
|
var wonderBlocksData = require('@khanacademy/wonder-blocks-data');
|
|
8
8
|
var reactRouterDom = require('react-router-dom');
|
|
9
|
+
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
10
|
+
var aphrodite = require('aphrodite');
|
|
9
11
|
|
|
10
12
|
function _interopNamespace(e) {
|
|
11
13
|
if (e && e.__esModule) return e;
|
|
@@ -271,11 +273,12 @@ class SettleController {
|
|
|
271
273
|
return this._signal;
|
|
272
274
|
}
|
|
273
275
|
settle() {
|
|
274
|
-
|
|
276
|
+
var _this$_settleFn;
|
|
277
|
+
(_this$_settleFn = this._settleFn) == null ? void 0 : _this$_settleFn.call(this);
|
|
275
278
|
}
|
|
276
279
|
}
|
|
277
280
|
|
|
278
|
-
const defaultConfig$
|
|
281
|
+
const defaultConfig$4 = null;
|
|
279
282
|
const normalizeConfig = config => {
|
|
280
283
|
if (typeof config === "string") {
|
|
281
284
|
return {
|
|
@@ -300,7 +303,7 @@ const normalizeConfig = config => {
|
|
|
300
303
|
}
|
|
301
304
|
throw new Error(`Invalid config: ${config}`);
|
|
302
305
|
};
|
|
303
|
-
const adapter$
|
|
306
|
+
const adapter$4 = (children, config) => {
|
|
304
307
|
const {
|
|
305
308
|
classes,
|
|
306
309
|
style
|
|
@@ -312,8 +315,8 @@ const adapter$3 = (children, config) => {
|
|
|
312
315
|
}, children);
|
|
313
316
|
};
|
|
314
317
|
|
|
315
|
-
const defaultConfig$
|
|
316
|
-
const adapter$
|
|
318
|
+
const defaultConfig$3 = [];
|
|
319
|
+
const adapter$3 = (children, config) => {
|
|
317
320
|
let currentChildren = children;
|
|
318
321
|
const interceptors = Array.isArray(config) ? config : [config];
|
|
319
322
|
for (const interceptor of interceptors) {
|
|
@@ -324,18 +327,18 @@ const adapter$2 = (children, config) => {
|
|
|
324
327
|
return React__namespace.createElement(React__namespace.Fragment, null, currentChildren);
|
|
325
328
|
};
|
|
326
329
|
|
|
327
|
-
const defaultConfig$
|
|
328
|
-
const adapter$
|
|
330
|
+
const defaultConfig$2 = null;
|
|
331
|
+
const adapter$2 = (children, config) => React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement("div", {
|
|
329
332
|
id: config,
|
|
330
333
|
"data-test-id": config
|
|
331
334
|
}), children);
|
|
332
335
|
|
|
333
|
-
const defaultConfig = {
|
|
336
|
+
const defaultConfig$1 = {
|
|
334
337
|
location: "/"
|
|
335
338
|
};
|
|
336
339
|
const maybeWithRoute = (children, path) => {
|
|
337
340
|
if (path == null) {
|
|
338
|
-
return children;
|
|
341
|
+
return React__namespace.createElement(React__namespace.Fragment, null, children);
|
|
339
342
|
}
|
|
340
343
|
return React__namespace.createElement(reactRouterDom.Switch, null, React__namespace.createElement(reactRouterDom.Route, {
|
|
341
344
|
exact: true,
|
|
@@ -347,28 +350,28 @@ const maybeWithRoute = (children, path) => {
|
|
|
347
350
|
}
|
|
348
351
|
}));
|
|
349
352
|
};
|
|
350
|
-
const adapter = (children, config) => {
|
|
353
|
+
const adapter$1 = (children, config) => {
|
|
351
354
|
if (typeof config === "string") {
|
|
352
355
|
config = {
|
|
353
356
|
location: config
|
|
354
357
|
};
|
|
355
358
|
}
|
|
356
359
|
const wrappedWithRoute = maybeWithRoute(children, config.path);
|
|
357
|
-
if (config.forceStatic) {
|
|
360
|
+
if ("forceStatic" in config && config.forceStatic) {
|
|
358
361
|
return React__namespace.createElement(reactRouterDom.StaticRouter, {
|
|
359
362
|
location: config.location,
|
|
360
363
|
context: {}
|
|
361
364
|
}, wrappedWithRoute);
|
|
362
365
|
}
|
|
363
|
-
if (
|
|
366
|
+
if ("location" in config && config.location !== undefined) {
|
|
364
367
|
return React__namespace.createElement(reactRouterDom.MemoryRouter, {
|
|
365
368
|
initialEntries: [config.location]
|
|
366
369
|
}, wrappedWithRoute);
|
|
367
370
|
}
|
|
368
|
-
if (
|
|
371
|
+
if (!("initialEntries" in config) || config.initialEntries === undefined) {
|
|
369
372
|
throw new Error("A location or initial history entries must be provided.");
|
|
370
373
|
}
|
|
371
|
-
const entries = config.initialEntries.length === 0 ? [defaultConfig.location] : config.initialEntries;
|
|
374
|
+
const entries = config.initialEntries.length === 0 ? [defaultConfig$1.location] : config.initialEntries;
|
|
372
375
|
const routerProps = {
|
|
373
376
|
initialEntries: entries
|
|
374
377
|
};
|
|
@@ -381,17 +384,406 @@ const adapter = (children, config) => {
|
|
|
381
384
|
return React__namespace.createElement(reactRouterDom.MemoryRouter, routerProps, wrappedWithRoute);
|
|
382
385
|
};
|
|
383
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
|
+
const defaultConfig = null;
|
|
763
|
+
const adapter = (children, config) => {
|
|
764
|
+
if (config !== true) {
|
|
765
|
+
throw new wonderStuffCore.KindError("Unexpected configuraiton", wonderStuffCore.Errors.InvalidInput, {
|
|
766
|
+
metadata: {
|
|
767
|
+
config
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return React__namespace.createElement(RenderStateRoot, null, children);
|
|
772
|
+
};
|
|
773
|
+
|
|
384
774
|
const DefaultAdapters = {
|
|
385
|
-
css: adapter$
|
|
386
|
-
data: adapter$
|
|
387
|
-
portal: adapter$
|
|
388
|
-
router: adapter
|
|
775
|
+
css: adapter$4,
|
|
776
|
+
data: adapter$3,
|
|
777
|
+
portal: adapter$2,
|
|
778
|
+
router: adapter$1,
|
|
779
|
+
ssr: adapter
|
|
389
780
|
};
|
|
390
781
|
const DefaultConfigs = {
|
|
391
|
-
css: defaultConfig$
|
|
392
|
-
data: defaultConfig$
|
|
393
|
-
portal: defaultConfig$
|
|
394
|
-
router: defaultConfig
|
|
782
|
+
css: defaultConfig$4,
|
|
783
|
+
data: defaultConfig$3,
|
|
784
|
+
portal: defaultConfig$2,
|
|
785
|
+
router: defaultConfig$1,
|
|
786
|
+
ssr: defaultConfig
|
|
395
787
|
};
|
|
396
788
|
|
|
397
789
|
var adapters = /*#__PURE__*/Object.freeze({
|
|
@@ -424,7 +816,7 @@ const renderAdapters = (adapters, configs, children) => {
|
|
|
424
816
|
currentChildren = adapter(currentChildren, config);
|
|
425
817
|
}
|
|
426
818
|
}
|
|
427
|
-
return currentChildren;
|
|
819
|
+
return React__namespace.createElement(React__namespace.Fragment, null, currentChildren);
|
|
428
820
|
};
|
|
429
821
|
|
|
430
822
|
const makeTestHarness = (adapters, defaultConfigs) => {
|
|
@@ -440,7 +832,7 @@ const makeTestHarness = (adapters, defaultConfigs) => {
|
|
|
440
832
|
|
|
441
833
|
const HookHarness = ({
|
|
442
834
|
children
|
|
443
|
-
}) => children;
|
|
835
|
+
}) => React__namespace.createElement(React__namespace.Fragment, null, children);
|
|
444
836
|
const makeHookHarness = (adapters, defaultConfigs) => {
|
|
445
837
|
const testHarness = makeTestHarness(adapters, defaultConfigs);
|
|
446
838
|
return configs => testHarness(HookHarness, configs);
|
package/dist/mock-requester.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OperationMatcher, MockFn } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* A generic mock request function for using when mocking fetch or gqlFetch.
|
|
4
4
|
*/
|
|
5
|
-
export declare const mockRequester: <TOperationType
|
|
5
|
+
export declare const mockRequester: <TOperationType>(operationMatcher: OperationMatcher<any>, operationToString: (...args: Array<any>) => string) => MockFn<TOperationType>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@babel/runtime": "^7.18.6",
|
|
17
|
-
"@khanacademy/wonder-blocks-data": "^
|
|
17
|
+
"@khanacademy/wonder-blocks-data": "^13.0.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^1.2.2",
|
|
@@ -12,10 +12,8 @@ const getHref = (input: RequestInfo): string => {
|
|
|
12
12
|
return input;
|
|
13
13
|
} else if (typeof input.url === "string") {
|
|
14
14
|
return input.url;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// @ts-expect-error [FEI-5019] - TS2339 - Property 'href' does not exist on type 'Request'.
|
|
18
|
-
return input.href;
|
|
15
|
+
} else if (typeof (input as any).href === "string") {
|
|
16
|
+
return (input as any).href;
|
|
19
17
|
} else {
|
|
20
18
|
throw new Error(`Unsupported input type`);
|
|
21
19
|
}
|
package/src/fetch/mock-fetch.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {FetchMockFn, FetchMockOperation} from "./types";
|
|
|
6
6
|
* A mock for the fetch function passed to GqlRouter.
|
|
7
7
|
*/
|
|
8
8
|
export const mockFetch = (): FetchMockFn =>
|
|
9
|
-
mockRequester<FetchMockOperation
|
|
9
|
+
mockRequester<FetchMockOperation>(
|
|
10
10
|
fetchRequestMatchesMock,
|
|
11
11
|
// NOTE(somewhatabstract): The indentation is expected on the lines
|
|
12
12
|
// here.
|
|
@@ -114,10 +114,9 @@ describe("fixtures", () => {
|
|
|
114
114
|
|
|
115
115
|
it("should render the component", () => {
|
|
116
116
|
// Arrange
|
|
117
|
-
const fixture = fixtures(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
117
|
+
const fixture = fixtures((props: any) => (
|
|
118
|
+
<>{`I rendered ${JSON.stringify(props)}`}</>
|
|
119
|
+
));
|
|
121
120
|
const Fixture: any = fixture("A simple story", {});
|
|
122
121
|
|
|
123
122
|
// Act
|
|
@@ -131,16 +130,12 @@ describe("fixtures", () => {
|
|
|
131
130
|
|
|
132
131
|
it("should render the wrapper", () => {
|
|
133
132
|
// Arrange
|
|
134
|
-
const fixture = fixtures(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{},
|
|
141
|
-
// @ts-expect-error [FEI-5019] - TS2345 - Argument of type '() => string' is not assignable to parameter of type 'ComponentType<any> | undefined'.
|
|
142
|
-
() => "I am a wrapper",
|
|
143
|
-
);
|
|
133
|
+
const fixture = fixtures((props: any) => (
|
|
134
|
+
<>{`I rendered ${JSON.stringify(props)}`}</>
|
|
135
|
+
));
|
|
136
|
+
const Fixture: any = fixture("A simple story", {}, () => (
|
|
137
|
+
<>I am a wrapper</>
|
|
138
|
+
));
|
|
144
139
|
|
|
145
140
|
// Act
|
|
146
141
|
render(<Fixture aProp="aValue" />);
|
|
@@ -7,9 +7,15 @@ type Props = {
|
|
|
7
7
|
propB?: string;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const MyComponent = (props: Props): React.ReactElement =>
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const MyComponent = (props: Props): React.ReactElement => (
|
|
11
|
+
<>
|
|
12
|
+
{`I am a component. Here are my props: ${JSON.stringify(
|
|
13
|
+
props,
|
|
14
|
+
null,
|
|
15
|
+
2,
|
|
16
|
+
)}`}
|
|
17
|
+
</>
|
|
18
|
+
);
|
|
13
19
|
|
|
14
20
|
const Wrapper = (props: any) => (
|
|
15
21
|
<>
|
|
@@ -27,8 +27,7 @@ export const fixtures = <
|
|
|
27
27
|
let storyNumber = 1;
|
|
28
28
|
|
|
29
29
|
const getPropsOptions = {
|
|
30
|
-
|
|
31
|
-
log: (message, ...args) => action(message)(...args),
|
|
30
|
+
log: (message: string, ...args: Array<any>) => action(message)(...args),
|
|
32
31
|
logHandler: action,
|
|
33
32
|
} as const;
|
|
34
33
|
|
|
@@ -6,7 +6,7 @@ import type {GqlFetchMockFn, GqlMockOperation} from "./types";
|
|
|
6
6
|
* A mock for the fetch function passed to GqlRouter.
|
|
7
7
|
*/
|
|
8
8
|
export const mockGqlFetch = (): GqlFetchMockFn =>
|
|
9
|
-
mockRequester<GqlMockOperation<any, any, any
|
|
9
|
+
mockRequester<GqlMockOperation<any, any, any>>(
|
|
10
10
|
gqlRequestMatchesMock,
|
|
11
11
|
// Note that the identation at the start of each line is important.
|
|
12
12
|
// TODO(somewhatabstract): Make a stringify that indents each line of
|
|
@@ -34,7 +34,8 @@ describe("#hookHarness", () => {
|
|
|
34
34
|
const config = {
|
|
35
35
|
router: "/boo",
|
|
36
36
|
} as const;
|
|
37
|
-
// @ts-expect-error
|
|
37
|
+
// @ts-expect-error We know harnessFake isn't real, we add it in the
|
|
38
|
+
// mocks at the top of this file.
|
|
38
39
|
const [{harnessFake}, {hookHarness}] = await ws.isolateModules(() =>
|
|
39
40
|
Promise.all([
|
|
40
41
|
import("../make-hook-harness"),
|
|
@@ -54,7 +55,8 @@ describe("#hookHarness", () => {
|
|
|
54
55
|
const config = {
|
|
55
56
|
router: "/boo",
|
|
56
57
|
} as const;
|
|
57
|
-
// @ts-expect-error
|
|
58
|
+
// @ts-expect-error We know harnessFake isn't real, we add it in the
|
|
59
|
+
// mocks at the top of this file.
|
|
58
60
|
const [{returnValueFake}, {hookHarness}] = await ws.isolateModules(() =>
|
|
59
61
|
Promise.all([
|
|
60
62
|
import("../make-hook-harness"),
|