@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.
- package/CHANGELOG.md +17 -0
- package/dist/es/index.js +450 -33
- package/dist/harness/adapt.d.ts +17 -0
- package/dist/harness/adapter.d.ts +16 -0
- 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 +449 -33
- 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__/adapt.test.tsx +200 -0
- package/src/harness/__tests__/adapter.test.tsx +67 -0
- package/src/harness/__tests__/hook-harness.test.ts +4 -2
- package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
- package/src/harness/__tests__/test-harness.test.ts +4 -2
- package/src/harness/__tests__/types.typestest.tsx +6 -13
- package/src/harness/adapt.tsx +55 -0
- package/src/harness/adapter.tsx +27 -0
- package/src/harness/adapters/__tests__/data.test.tsx +12 -4
- package/src/harness/adapters/__tests__/ssr.test.tsx +82 -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 +37 -0
- package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
- package/src/harness/make-test-harness.tsx +6 -8
- 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/harness/render-adapters.d.ts +0 -6
- package/src/harness/__tests__/render-adapters.test.tsx +0 -87
- 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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
292
|
-
const adapter$
|
|
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$
|
|
304
|
-
const adapter$
|
|
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 (
|
|
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 (
|
|
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$
|
|
362
|
-
data: adapter$
|
|
363
|
-
portal: adapter$
|
|
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$
|
|
368
|
-
data: defaultConfig$
|
|
369
|
-
portal: defaultConfig$
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
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) =>
|
|
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>>;
|
package/dist/harness/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
*
|