diff --git a/apps/stress-test/package.json b/apps/stress-test/package.json index 0de90bde47e82..9b9abe95b7d2a 100644 --- a/apps/stress-test/package.json +++ b/apps/stress-test/package.json @@ -15,6 +15,7 @@ "@fluentui/web-components": "^2.5.6", "@microsoft/fast-element": "^1.10.4", "react": "17.0.2", - "react-dom": "17.0.2" + "react-dom": "17.0.2", + "random-seedable": "1.0.8" } } diff --git a/apps/stress-test/src/shared/css/RandomSelector.ts b/apps/stress-test/src/shared/css/RandomSelector.ts new file mode 100644 index 0000000000000..caa8248bdcd67 --- /dev/null +++ b/apps/stress-test/src/shared/css/RandomSelector.ts @@ -0,0 +1,137 @@ +import { random, Random } from '../utils/random'; + +export const defaultSelectorTypes = [ + 'class', + 'tag', + 'nth-child', + 'pseudo-element', + 'not-class', + 'not-attribute', + 'attribute-name', + 'attribute-value', +] as const; + +type SelectorType = typeof defaultSelectorTypes[number]; + +type SelectorParams = { + seed?: number; + selectorTypes?: SelectorType[]; + tags?: string[]; + classNames?: string[]; + attributeNames?: string[]; + attributeValues?: string[]; +}; + +export class RandomSelector { + private rando: Random; + private readonly selectorTypes: SelectorType[]; + private tags: string[]; + private classNames: string[]; + private attributeNames: string[]; + private attributeValues: string[]; + + constructor({ seed, selectorTypes, tags, classNames, attributeNames, attributeValues }: SelectorParams = {}) { + this.rando = random(seed); + this.selectorTypes = selectorTypes ?? ((defaultSelectorTypes as unknown) as SelectorType[]); + this.tags = tags ?? []; + this.classNames = classNames ?? []; + this.attributeNames = attributeNames ?? []; + this.attributeValues = attributeValues ?? []; + } + + public randomSelector = (selectorTypes?: SelectorType[]): string => { + const selectorType = this._randomSelectorType(selectorTypes); + + switch (selectorType) { + case 'class': + return this._classSelector(); + + case 'tag': + return this._tagSelector(); + + case 'nth-child': + return this._nthChildSelector(); + + case 'pseudo-element': + return this._pseudoElement(); + + case 'not-class': + return this._notClass(); + + case 'not-attribute': + return this._notAttribute(); + + case 'attribute-name': + return this._attributeName(); + + case 'attribute-value': + return this._attributeValue(); + } + }; + + private _randomSelectorType = (selectorTypes?: SelectorType[]): SelectorType => { + return this.rando.choice(selectorTypes ?? this.selectorTypes); + }; + + private _classSelector = (): string => { + const selector = this._randomChoice(this.classNames) ?? this._randomString('random-classname'); + return `.${selector}`; + }; + + private _tagSelector = (): string => { + return this._randomChoice(this.tags) ?? this._randomString('random-tag'); + }; + + private _nthChildSelector = (): string => { + const choices = [':first-child', ':last-child', ':nth-child']; + const selector = this.rando.choice(choices); + + if (selector === ':nth-child') { + return `${selector}(${this.rando.range(1, 15)})`; + } + + return selector; + }; + + private _pseudoElement = (): string => { + const choices = ['::after', '::before', /*'::part',*/ '::placeholder', '::slotted']; + const selector = this.rando.choice(choices); + + return selector; + }; + + private _notClass = (): string => { + return `:not(${this._classSelector()})`; + }; + + private _notAttribute = (): string => { + return `:not(${this._attributeName()})`; + }; + + private _attributeName = (): string => { + return `[${this._randomAttributeName()}]`; + }; + + private _attributeValue = (): string => { + const name = this._randomAttributeName(); + const value = this._randomChoice(this.attributeValues) ?? this._randomString('random-attr-value'); + + return `[${name}=${value}]`; + }; + + private _randomChoice = (choices: string[]): string | undefined => { + if (choices.length > 0) { + return this.rando.choice(choices); + } + + return undefined; + }; + + private _randomAttributeName = (): string => { + return this._randomChoice(this.attributeNames) ?? this._randomString('data-random-attr'); + }; + + private _randomString = (prefix: string = 'random-string'): string => { + return `${prefix}-${this.rando.integer().toString(16)}`; + }; +} diff --git a/apps/stress-test/src/shared/react/ReactSelectorTree.tsx b/apps/stress-test/src/shared/react/ReactSelectorTree.tsx new file mode 100644 index 0000000000000..7dd0df4d8bb2c --- /dev/null +++ b/apps/stress-test/src/shared/react/ReactSelectorTree.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { TreeNode } from '../tree/RandomTree'; +import { ReactTree } from './ReactTree'; +import { RandomSelectorTreeNode, SelectorTreeNode } from '../tree/RandomSelectorTreeNode'; +import { ReactSelectorTreeComponentRenderer } from './types'; + +type ReactSelectorTreeProps = { + tree?: TreeNode; + componentRenderer: ReactSelectorTreeComponentRenderer; +}; + +const buildRenderer = (componentRenderer: ReactSelectorTreeComponentRenderer) => { + const renderer = (node: SelectorTreeNode, depth: number, index: number): JSX.Element => { + const { value } = node; + + const className = value.classNames.map(cn => cn.substring(1)).join(' '); + const attrs = value.attributes.reduce((map, attr) => { + map[attr.key] = attr.value ?? ''; + return map; + }, {} as { [key: string]: string }); + + return ( +
+ {componentRenderer(node, depth, index)} +
+ ); + }; + + return renderer; +}; +export const ReactSelectorTree: React.FC = ({ tree, componentRenderer }) => { + return <>{tree ? : null}; +}; diff --git a/apps/stress-test/src/shared/react/ReactTree.tsx b/apps/stress-test/src/shared/react/ReactTree.tsx new file mode 100644 index 0000000000000..4324d5ac94b14 --- /dev/null +++ b/apps/stress-test/src/shared/react/ReactTree.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { TreeNode } from '../tree/RandomTree'; + +export type ReactTreeItemRenderer = (node: T, depth: number, index: number) => JSX.Element; + +export type ReactTreeProps> = { + tree: T; + itemRenderer: ReactTreeItemRenderer; +}; + +export type ReactTreeNodeProps> = { + root: T; + renderer: ReactTreeItemRenderer; + depth?: number; + index?: number; +}; + +export const ReactTreeNode = >(props: ReactTreeNodeProps): JSX.Element => { + const { root, renderer, depth = 0, index = 0, ...others } = props; + + return ( +
+ {renderer(root, depth, index)} + {root.children.map((child, i) => { + return ; + })} +
+ ); +}; + +export const ReactTree = >({ tree, itemRenderer }: ReactTreeProps): JSX.Element => { + return ; +}; diff --git a/apps/stress-test/src/shared/react/types.ts b/apps/stress-test/src/shared/react/types.ts new file mode 100644 index 0000000000000..96eab1e699dc3 --- /dev/null +++ b/apps/stress-test/src/shared/react/types.ts @@ -0,0 +1,12 @@ +import { TestOptions } from '../utils/testOptions'; +import { RandomSelectorTreeNode, SelectorTreeNode } from '../tree/RandomSelectorTreeNode'; +import { TreeNode } from '../tree/RandomTree'; + +export type ReactSelectorTreeComponentRenderer = (node: SelectorTreeNode, depth: number, index: number) => JSX.Element; + +export type TestProps = { + componentRenderer: ReactSelectorTreeComponentRenderer; + tree: TreeNode; + selectors: string[]; + testOptions: TestOptions; +}; diff --git a/apps/stress-test/src/shared/tree/RandomSelectorTreeNode.ts b/apps/stress-test/src/shared/tree/RandomSelectorTreeNode.ts new file mode 100644 index 0000000000000..28b0cb2f87c27 --- /dev/null +++ b/apps/stress-test/src/shared/tree/RandomSelectorTreeNode.ts @@ -0,0 +1,139 @@ +import { RandomSelector } from '../css/RandomSelector'; +import { TreeNode, TreeNodeCreateCallback } from './RandomTree'; +import { random } from '../utils/random'; + +export type Attribute = { + key: string; + value: string | undefined; + selector: string; +}; + +export type RandomSelectorTreeNode = { + name: string; + classNames: string[]; + attributes: Attribute[]; + siblings: string[]; + pseudos: string[]; +}; + +export type SelectorTreeNode = TreeNode; + +const { coin, choice } = random(); +const randomSelector = new RandomSelector(); + +const chances: { [key: string]: number } = { + not: 0.05, + addClassName: 0.5, + addAttribute: 0.25, + buildDescendentSelector: 0.75, + addSibling: 0.25, + addPseudo: 0.25, + useDescendantCombinator: 0.2, +}; + +const buildDescendentSelector = ( + node: TreeNode | null, + selector: string = '', +): string => { + if (!node) { + return selector; + } + + selector = ( + maybeNot(choice(getSelectorsFromNode(node))) + + (coin(chances.useDescendantCombinator) ? ' > ' : ' ') + + selector + ).trim(); + + if (coin(chances.buildDescendentSelector)) { + selector = buildDescendentSelector(node.parent, selector); + } + + return selector; +}; + +const getNodeClassNames = () => { + const nodeSelectors = [randomSelector.randomSelector(['class'])]; + if (coin(chances.addClassName)) { + nodeSelectors.push(randomSelector.randomSelector(['class'])); + } + + return nodeSelectors; +}; + +const maybeNot = (selector: string): string => { + if (coin(chances.not)) { + return `:not(${selector})`; + } + + return selector; +}; + +const getAttributes = () => { + const attributes = []; + if (coin(chances.addAttribute)) { + const selector = randomSelector.randomSelector(['attribute-name', 'attribute-value']); + const [key, value] = selector.replace(/(\[|\])/g, '').split('='); + attributes.push({ key, value, selector }); + } + + return attributes; +}; + +const getSiblingSelectors = () => { + const siblings = []; + + if (coin(chances.addSibling)) { + siblings.push(randomSelector.randomSelector(['nth-child'])); + } + + return siblings; +}; + +const getPseudoSelectors = () => { + const pseudo = []; + + if (coin(chances.addPsuedo)) { + pseudo.push(randomSelector.randomSelector(['pseudo-element'])); + } + + return pseudo; +}; + +const getSelectorsFromNode = (node: TreeNode): string[] => { + return [ + ...node.value.classNames, + ...node.value.attributes.map(attr => attr.selector), + ...node.value.siblings, + ...node.value.pseudos, + ]; +}; + +export type RandomSelectorTreeCreator = (selectors: string[]) => TreeNodeCreateCallback; + +export const selectorTreeCreator: RandomSelectorTreeCreator = selectors => { + const createSelectorTree: TreeNodeCreateCallback = (parent, depth, breadth) => { + const node = { + value: { + name: `${depth}-${breadth}`, + classNames: getNodeClassNames(), + attributes: getAttributes(), + siblings: getSiblingSelectors(), + pseudos: getPseudoSelectors(), + }, + children: [], + parent, + }; + + if (coin(chances.buildDescendentSelector)) { + const descendentSelector = buildDescendentSelector(node); + selectors.push(descendentSelector); + } else { + selectors.push(...getSelectorsFromNode(node)); + } + + return node; + }; + + return createSelectorTree; +}; diff --git a/apps/stress-test/src/shared/tree/RandomTree.ts b/apps/stress-test/src/shared/tree/RandomTree.ts new file mode 100644 index 0000000000000..f41d024c1fb8e --- /dev/null +++ b/apps/stress-test/src/shared/tree/RandomTree.ts @@ -0,0 +1,107 @@ +import { random, Random } from '../utils/random'; +import { TestFixture } from '../utils/testUtils'; + +type TreeParams = { + minDepth?: number; + maxDepth?: number; + minBreadth?: number; + maxBreadth?: number; + seed?: number; +}; + +export type TreeNode = { + value: T; + children: TreeNode[]; + parent: TreeNode | null; +}; + +export type TreeNodeCreateCallback = (parent: TreeNode | null, depth: number, breath: number) => TreeNode; +export type TreeNodeVisitCallback = (node: TreeNode) => void; + +/** + * Exponential decay function. + * See: https://www.cuemath.com/exponential-decay-formula/ + * @param a - Initial amount + * @param r - Rate of decay + * @param x - Decay interval + * @returns Current amount after applying decay function. + */ +const decay = (a: number, r: number, x: number): number => { + return a * Math.pow(1 - r, x); +}; + +export class RandomTree { + private numNodes: number; + private minDepth: number; + private maxDepth: number; + private minBreadth: number; + private maxBreadth: number; + + private rando: Random; + + constructor({ minDepth = 1, maxDepth = 15, minBreadth = 1, maxBreadth = 15, seed }: TreeParams = {}) { + this.minDepth = minDepth; + this.maxDepth = maxDepth; + this.minBreadth = minBreadth; + this.maxBreadth = maxBreadth; + + this.rando = random(seed); + this.numNodes = 0; + } + + public build = (createNode: TreeNodeCreateCallback): TreeNode => { + this.numNodes = 1; + const root = createNode(null, 0, 0); + return this._doBuild(createNode, root, 1); + }; + + public fromFixture = (fixture: TestFixture['tree'], parent: TreeNode | null = null): TreeNode => { + const root: TreeNode = { + value: fixture.value as T, + children: [], + parent, + }; + + for (const child of fixture.children) { + root.children.push(this.fromFixture(child, root)); + } + + return root; + }; + + private _randomDepth = (max?: number): number => { + return this.rando.range(this.minDepth, max ?? this.maxDepth); + }; + + private _randomBreadth = (max?: number): number => { + return this.rando.range(this.minBreadth, max ?? this.maxBreadth); + }; + + private _doBuild = ( + createNode: TreeNodeCreateCallback, + parent: TreeNode, + currentDepth: number, + currentBreadth: number = this.maxBreadth, + ): TreeNode => { + const breadth = this._randomBreadth(currentBreadth); + const depth = this._randomDepth(Math.max(this.maxDepth - currentDepth, this.minDepth)); + + for (let i = 0; i < breadth; i++) { + this.numNodes++; + const node = createNode(parent, currentDepth, breadth); + + parent.children.push(node); + + if (currentDepth < depth) { + this._doBuild( + createNode, + node, + currentDepth + 1, + Math.max(Math.ceil(decay(this.maxBreadth, 0.005, this.numNodes)), this.minBreadth), + ); + } + } + + return parent; + }; +} diff --git a/apps/stress-test/src/shared/utils/random.ts b/apps/stress-test/src/shared/utils/random.ts new file mode 100644 index 0000000000000..b04b3d8d1320c --- /dev/null +++ b/apps/stress-test/src/shared/utils/random.ts @@ -0,0 +1,24 @@ +import { LCG } from 'random-seedable'; + +const defaultSeed = 4212021; + +export type Random = { + coin: (pTrue: number) => boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + choice: (choices: any[]) => any; + range: (min: number, max: number) => number; + integer: () => number; +}; + +export type RandomFn = (seed?: number) => Random; + +export const random: RandomFn = seed => { + const rando: LCG = new LCG(seed ?? defaultSeed); + + return { + coin: (pTrue = 0.5) => rando.coin(pTrue), + choice: choices => rando.choice(choices), + range: (min, max) => rando.randRange(min, max), + integer: () => rando.int(), + }; +}; diff --git a/apps/stress-test/src/shared/utils/testOptions.ts b/apps/stress-test/src/shared/utils/testOptions.ts new file mode 100644 index 0000000000000..7e54a77013ef3 --- /dev/null +++ b/apps/stress-test/src/shared/utils/testOptions.ts @@ -0,0 +1,63 @@ +export type TestOptions = { + test: string; + fixtureName: string; + rendererName: string; + [key: string]: string | number; +}; + +export type GetTestOptionsFn = () => TestOptions; + +declare global { + interface URLSearchParams { + keys: () => string[]; + } +} + +let params: TestOptions; +export const getTestOptions = () => { + if (params) { + return params; + } + params = {} as TestOptions; + + if (typeof window === 'undefined') { + return params; + } + + const searchParams = new URLSearchParams(window.location.search); + + let test = searchParams.get('test'); + if (!test) { + test = 'manual'; + } + + let numStartNodes = Number(searchParams.get('numStartNodes')); + if (!numStartNodes || isNaN(numStartNodes)) { + numStartNodes = 100; + } + + let numAddNodes = Number(searchParams.get('numAddNodes')); + if (!numAddNodes || isNaN(numAddNodes)) { + numAddNodes = 100; + } + + let numRemoveNodes = Number(searchParams.get('numRemoveNodes')); + if (!numRemoveNodes || isNaN(numRemoveNodes)) { + numRemoveNodes = 99; + } + + params.test = test as TestOptions['test']; + params.numStartNodes = numStartNodes; + params.numAddNodes = numAddNodes; + params.numRemoveNodes = numRemoveNodes; + + const ignore = ['numStartNodes', 'numAddNodes', 'numRemoveNodes']; + for (const key of searchParams.keys()) { + const value = searchParams.get(key); + if (value && !ignore.includes(key)) { + params[key] = value; + } + } + + return params; +}; diff --git a/apps/stress-test/src/shared/utils/testUtils.ts b/apps/stress-test/src/shared/utils/testUtils.ts new file mode 100644 index 0000000000000..83b5a13d36b97 --- /dev/null +++ b/apps/stress-test/src/shared/utils/testUtils.ts @@ -0,0 +1,9 @@ +export type TestFixture = { + tree: { + value: unknown; + children: TestFixture['tree'][]; + }; + selectors: string[]; +}; + +export type TestRenderer = (node: unknown, depth: number, index: number) => unknown; diff --git a/apps/stress-test/src/shared/vanilla/VanillaSelectorTree.ts b/apps/stress-test/src/shared/vanilla/VanillaSelectorTree.ts new file mode 100644 index 0000000000000..308fbd70abe43 --- /dev/null +++ b/apps/stress-test/src/shared/vanilla/VanillaSelectorTree.ts @@ -0,0 +1,33 @@ +import { TestOptions } from '../utils/testOptions'; +import { SelectorTreeNode } from '../tree/RandomSelectorTreeNode'; +import { DOMSelectorTreeComponentRenderer } from './types'; +import { renderVanillaTree } from './VanillaTree'; + +const itemRenderer = (componentRenderer: DOMSelectorTreeComponentRenderer) => ( + node: SelectorTreeNode, + depth: number, + index: number, +): HTMLElement => { + const { value } = node; + + const div = document.createElement('div'); + div.classList.add(...value.classNames.map(cn => cn.substring(1))); + value.attributes.forEach(attr => { + div.setAttribute(attr.key, attr.value ?? ''); + }); + + div.style.marginLeft = `${depth * 10}px`; + div.appendChild(componentRenderer(node, depth, index)); + + return div; +}; + +export const renderVanillaSelectorTree = ( + tree: SelectorTreeNode, + _selectors: string[], + componentRenderer: DOMSelectorTreeComponentRenderer, + _testOptions: TestOptions, +): HTMLElement => { + const vanillaTree = renderVanillaTree(tree, itemRenderer(componentRenderer)); + return vanillaTree; +}; diff --git a/apps/stress-test/src/shared/vanilla/VanillaTree.ts b/apps/stress-test/src/shared/vanilla/VanillaTree.ts new file mode 100644 index 0000000000000..eeb250045b43f --- /dev/null +++ b/apps/stress-test/src/shared/vanilla/VanillaTree.ts @@ -0,0 +1,22 @@ +import { TreeNode } from '../tree/RandomTree'; + +export type VanillaTreeItemRenderer = (node: T, depth: number, index: number) => HTMLElement; + +export const renderVanillaTree = >( + tree: T, + itemRenderer: VanillaTreeItemRenderer, + depth: number = 0, + index: number = 0, +): HTMLElement => { + const root = document.createElement('div'); + root.classList.add('vanilla-tree-node'); + + root.appendChild(itemRenderer(tree, depth, index)); + + tree.children.forEach((child, i) => { + const node = renderVanillaTree(child as T, itemRenderer, depth + 1, i + 1); + root.appendChild(node); + }); + + return root; +}; diff --git a/apps/stress-test/src/shared/vanilla/types.ts b/apps/stress-test/src/shared/vanilla/types.ts new file mode 100644 index 0000000000000..3700ac3358187 --- /dev/null +++ b/apps/stress-test/src/shared/vanilla/types.ts @@ -0,0 +1,3 @@ +import { SelectorTreeNode } from '../tree/RandomSelectorTreeNode'; + +export type DOMSelectorTreeComponentRenderer = (node: SelectorTreeNode, depth: number, index: number) => HTMLElement; diff --git a/apps/stress-test/src/shared/wc/WCSelectorTree.ts b/apps/stress-test/src/shared/wc/WCSelectorTree.ts new file mode 100644 index 0000000000000..957d862e5d621 --- /dev/null +++ b/apps/stress-test/src/shared/wc/WCSelectorTree.ts @@ -0,0 +1,64 @@ +import { TestOptions } from '../utils/testOptions'; +import { SelectorTreeNode } from '../tree/RandomSelectorTreeNode'; +import { DOMSelectorTreeComponentRenderer } from '../vanilla/types'; +import { WCTree } from './WCTree'; + +const template = document.createElement('template'); +template.innerHTML = `
`; + +export class WCSelectorTree extends HTMLElement { + private _root: HTMLElement | ShadowRoot; + private _testOptions: TestOptions; + private _tree: SelectorTreeNode | null; + private _componentRenderer: DOMSelectorTreeComponentRenderer; + + constructor(componentRenderer: DOMSelectorTreeComponentRenderer, testOptions: TestOptions) { + super(); + + this._testOptions = testOptions; + this._tree = null; + + this._componentRenderer = componentRenderer; + + if (this._testOptions?.useShadowRoot === 'true') { + this._root = this.attachShadow({ mode: 'open' }); + } else { + this._root = this; + } + } + + public set tree(value: SelectorTreeNode | null) { + this._tree = value; + + if (value === null) { + while (this._root.hasChildNodes()) { + this._root.removeChild(this._root.lastChild!); + } + } else { + const wcTree = new WCTree(this._itemRenderer, undefined, undefined, this._testOptions?.useShadowRoot === 'true'); + wcTree.tree = value; + this._root.appendChild(wcTree); + } + } + + public get tree() { + return this._tree; + } + + private _itemRenderer = (node: SelectorTreeNode, depth: number, index: number): HTMLElement => { + const { value } = node; + + const div = document.createElement('div'); + div.classList.add(...value.classNames.map(cn => cn.substring(1))); + value.attributes.forEach(attr => { + div.setAttribute(attr.key, attr.value ?? ''); + }); + + div.style.marginLeft = `${depth * 10}px`; + div.appendChild(this._componentRenderer(node, depth, index)); + + return div; + }; +} + +window.customElements.define('wc-selector-tree', WCSelectorTree); diff --git a/apps/stress-test/src/shared/wc/WCTree.ts b/apps/stress-test/src/shared/wc/WCTree.ts new file mode 100644 index 0000000000000..cd803d593e120 --- /dev/null +++ b/apps/stress-test/src/shared/wc/WCTree.ts @@ -0,0 +1,61 @@ +import { TreeNode } from '../tree/RandomTree'; + +export type WCTreeItemRenderer = (node: T, depth: number, index: number) => HTMLElement; + +const template = document.createElement('template'); +template.innerHTML = ` +
+`; + +export class WCTree> extends HTMLElement { + private _root: HTMLElement | ShadowRoot; + private _tree: T | null; + private _itemRenderer: WCTreeItemRenderer; + private _depth: number; + private _index: number; + private _useShadowRoot: boolean; + + constructor(itemRenderer: WCTreeItemRenderer, depth: number = 0, index = 0, useShadowRoot: boolean = false) { + super(); + + this._useShadowRoot = useShadowRoot; + + if (useShadowRoot) { + this._root = this.attachShadow({ mode: 'open' }); + } else { + this._root = this; + } + this._tree = null; + this._itemRenderer = itemRenderer; + this._depth = depth; + this._index = index; + } + + public set tree(value: T | null) { + this._tree = value; + this._render(); + } + + public get tree(): T | null { + return this._tree; + } + + private _render() { + if (this._tree === null) { + while (this._root.hasChildNodes()) { + this._root.removeChild(this._root.lastChild!); + } + return; + } + + this._root.appendChild(this._itemRenderer(this._tree, this._depth, this._index)); + + this._tree.children.forEach((child, i) => { + const node = new WCTree(this._itemRenderer, this._depth + 1, i + 1, this._useShadowRoot); + node.tree = child as T; + this._root.appendChild(node); + }); + } +} + +window.customElements.define('wc-tree', WCTree); diff --git a/yarn.lock b/yarn.lock index df23a510368ca..70d9478d5f106 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22573,6 +22573,11 @@ randexp@0.4.6: discontinuous-range "1.0.0" ret "~0.1.10" +random-seedable@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/random-seedable/-/random-seedable-1.0.8.tgz#d233ce5d49f64a20be398bbc3faa695cc39b8aa8" + integrity sha512-f6gzvNhAnZBht1Prn0e/tpukUNhkANntFF42uIdWDPriyEATYaRpyH8A9bYaGecUB3AL+dXeYtBUggy18fe3rw== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"