├── .gitignore ├── package.json ├── src └── utils.ts ├── tests └── example.spec.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xstate-test-playwright", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Matt Pocock ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "playwright test", 9 | "test:headed": "playwright test --headed" 10 | }, 11 | "devDependencies": { 12 | "@playwright/test": "^1.22.0", 13 | "@types/node": "^17.0.33", 14 | "typescript": "^4.6.4" 15 | }, 16 | "dependencies": { 17 | "@xstate/test": "^0.5.1", 18 | "xstate": "^4.32.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "@playwright/test"; 2 | import { TestPlan } from "@xstate/test/lib/types"; 3 | import { AnyStateMachine, StateMachine } from "xstate"; 4 | 5 | /** 6 | * Adds state.meta.test to machines by their state id - 7 | * allowing you to specify the tests separately to the 8 | * machine itself 9 | */ 10 | export const addTestsToMachine = ( 11 | /** 12 | * The machine you want to add tests to 13 | */ 14 | machine: AnyStateMachine, 15 | /** 16 | * The tests, specified as a keyed object, where: 17 | * 18 | * 1. Keys are the state ids 19 | * 2. Values are functions that take your test context 20 | * and return a promise 21 | */ 22 | tests: Record Promise>, 23 | ) => { 24 | Object.entries(tests).forEach(([stateId, test]) => { 25 | const node = machine.getStateNodeById(`${machine.id}.${stateId}`); 26 | 27 | if (tests[stateId]) { 28 | node.meta = { 29 | test: tests[stateId], 30 | }; 31 | } 32 | }); 33 | 34 | return machine; 35 | }; 36 | 37 | /** 38 | * Deduplicates your path plans so that A -> B 39 | * is not executed separately to A -> B -> C 40 | */ 41 | export const dedupPathPlans = ( 42 | pathPlans: TestPlan[], 43 | ): TestPlan[] => { 44 | const planPathSegments = pathPlans.map((plan) => { 45 | const planSegments = plan.paths[0].segments.map((segment) => 46 | JSON.stringify(segment.event), 47 | ); 48 | 49 | return planSegments; 50 | }); 51 | 52 | /** 53 | * Filter out the paths that are just shorter versions 54 | * of other paths 55 | */ 56 | const filteredPathPlans = pathPlans.filter((plan, index) => { 57 | const planSegments = planPathSegments[index]; 58 | 59 | if (planSegments.length === 0) return false; 60 | 61 | const concatenatedPlanSegments = planSegments.join(""); 62 | 63 | return !planPathSegments.some((planPathSegmentsToCompare) => { 64 | const concatenatedSegmentToCompare = planPathSegmentsToCompare.join(""); 65 | /** 66 | * Filter IN (return false) if it's the same as the current plan, 67 | * because it's not a valid comparison 68 | */ 69 | if (concatenatedSegmentToCompare === concatenatedPlanSegments) { 70 | return false; 71 | } 72 | 73 | /** 74 | * Filter IN (return false) if the plan to compare against has length 0 75 | */ 76 | if (planPathSegmentsToCompare.length === 0) { 77 | return false; 78 | } 79 | 80 | /** 81 | * We filter OUT (return true) 82 | */ 83 | return concatenatedSegmentToCompare.includes(concatenatedPlanSegments); 84 | }); 85 | }); 86 | 87 | return filteredPathPlans; 88 | }; 89 | -------------------------------------------------------------------------------- /tests/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { Page, test } from "@playwright/test"; 2 | import { createModel } from "@xstate/test"; 3 | import { createMachine } from "xstate"; 4 | import { addTestsToMachine, dedupPathPlans } from "../src/utils"; 5 | 6 | const machine = 7 | /** @xstate-layout N4IgpgJg5mDOIC5QBU4BcAEBbAhgYwAsBLAOzADoB5EjACQHsswMAFHGAYkoAcwaoArkQhwMsMHjRF6JRKG71YRKTLkgAHogDsADnIAmAMwAWE4YCsx8+YCcARlMAaEAE9EAWn0295uzp0ADIZa5gHmOiYAbAC+0c6osJi4hKQUAOJCIrBiEio09LwkHGn0GGilycRk2dzsYGoKSnlqmgjudpF25JFahmZhNoYBpobObm1aNuQ2AXZ9PXaW9nZ2sfHo2PhV6Zmi4pLS+YXFpeUY+FIAbspEorUwDYo3qkgaiHb6xgZa+rorxsZfmFjGN3uRjAFgd59AFIgEdJZIoY1iAEkktqkqDQALIY6oYe5gDgAYQANkQ8ABrDBgdRoPgQUhQTYpaqPJqHFoeCH6cgBLSwgI2SI2Xw6LQg1xgiFQnQwuEI4xIlFolnbLEYXGsu51EnkqkYUg3HCkjB4GT0uns56yV6tdyRHrTPzGFYWLSRLx2UETKYzOZwmxBzohLSxOIgEj0LJqVWVTHUOiMZhsB6vRo2rltQw6LorSZ2CUDCJKn3uIbmPl+QxI-ThPz6VYRuN4nbCPa5Q4YAp8a3NO2IcIGBE1uw2XS-cKjKVtD6GcFRD3+kz8-QqjbxsgarXbGp1PucgfZoMGYw2UzmSKWXw9Ms6SLgyH2HQ2OsvmFN9aJNUJmgAQQOa4pB1NN5CeftQFaJ0jDlfwZgRcwvHMH03QMR04TsAI60LLQQnXb9NwoP9MFpekSEZEhmUI7ICDAHARAAJwPF5II8Qw-HIfwVmFQFBhrCUUNmTjGxHRZQkBOt8PRbVyGIw0SGNU1zRIS1MFo+iwCY9NwMPViJmGcgHDguYx0dX4y0rIUZnhTDLyvd8pJ-MhmNtPT3B+PQjJfEzhU9LQy2CLRpnFSxYU+T1DBscNoiAA */ 8 | createMachine({ 9 | id: "Test machine", 10 | initial: "On Home Page", 11 | states: { 12 | "On Home Page": { 13 | on: { 14 | "Open guides section": { 15 | target: "Guides section open", 16 | }, 17 | }, 18 | }, 19 | "Guides section open": { 20 | on: { 21 | "Go to machines page": { 22 | target: "On Machines page", 23 | }, 24 | "Go to activities page": { 25 | target: "On Activities page", 26 | }, 27 | }, 28 | }, 29 | "On Machines page": { 30 | on: { 31 | "Click extending machines": { 32 | target: "At extending machines header", 33 | }, 34 | "Click initial context": { 35 | target: "At initial context header", 36 | }, 37 | }, 38 | }, 39 | "On Activities page": {}, 40 | "At extending machines header": {}, 41 | "At initial context header": {}, 42 | }, 43 | }); 44 | 45 | const machineWithTests = addTestsToMachine(machine, { 46 | "On Home Page": async (page) => { 47 | await page.locator('text="JavaScript state machines and statecharts"'); 48 | }, 49 | "Guides section open": async (page) => { 50 | await page.locator('text="Machines"'); 51 | }, 52 | "On Machines page": async (page) => { 53 | await page.locator( 54 | 'text="A state machine is a finite set of states that can transition to each other deterministically due to events."', 55 | ); 56 | }, 57 | "On Activities page": async (page) => { 58 | await page.locator( 59 | 'text="Activites are deprecated and will be removed in XState version 5."', 60 | ); 61 | }, 62 | "At extending machines header": async (page) => { 63 | await page.locator('text="Existing machines can be extended"'); 64 | }, 65 | "At initial context header": async (page) => { 66 | await page.locator('text="Initial context"'); 67 | }, 68 | }); 69 | 70 | const model = createModel(machineWithTests, { 71 | events: { 72 | "Open guides section": { 73 | exec: async (page) => { 74 | await page.click("text='Guides'"); 75 | }, 76 | }, 77 | "Go to machines page": { 78 | exec: async (page) => { 79 | await page.click("text='Machines'"); 80 | }, 81 | }, 82 | "Go to activities page": { 83 | exec: async (page) => { 84 | await page.click("text='Activities'"); 85 | }, 86 | }, 87 | "Click extending machines": { 88 | exec: async (page) => { 89 | await page.click("text='Extending Machines'"); 90 | }, 91 | }, 92 | "Click initial context": { 93 | exec: async (page) => { 94 | await page.click("text='Initial Context'"); 95 | }, 96 | }, 97 | }, 98 | }); 99 | 100 | const pathPlans = model.getShortestPathPlans(); 101 | 102 | test.describe.configure({ mode: "parallel" }); 103 | 104 | test.describe("XState Docs", () => { 105 | dedupPathPlans(pathPlans).forEach((plan) => { 106 | test(plan.description, async ({ page }) => { 107 | await page.goto("https://xstate.js.org/docs"); 108 | await page.waitForLoadState("load"); 109 | await plan.test(page); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@playwright/test@^1.22.0": 6 | version "1.22.0" 7 | resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.22.0.tgz#c120d673508291ee63095d727b6f998ea0c6a03f" 8 | integrity sha512-ExcAjiECo3uTG5Sl5H4a7rKp/5TEHTI87dv9NHYEoUFuOHPhSVxB7QsuM70ktO+wTTZ9KzhwzcegxAGRmUFKEA== 9 | dependencies: 10 | playwright-core "1.22.0" 11 | 12 | "@types/node@^17.0.33": 13 | version "17.0.33" 14 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506" 15 | integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ== 16 | 17 | "@xstate/graph@^1.4.1": 18 | version "1.4.2" 19 | resolved "https://registry.yarnpkg.com/@xstate/graph/-/graph-1.4.2.tgz#14987fc1f6d4cf11dca31fe5e6cb14e802f7b299" 20 | integrity sha512-XIh6opCf9ukXRj4dXe2fv2kwFFUl15B5Ob8ELNOOqDXB2BPyNwp6TaLe5KJn/na3gzC9B7LyOo+2d0dPkC8PWQ== 21 | 22 | "@xstate/test@^0.5.1": 23 | version "0.5.1" 24 | resolved "https://registry.yarnpkg.com/@xstate/test/-/test-0.5.1.tgz#c3cf7c2f4c9b0527d562ac5663ea4cd0c8298f0b" 25 | integrity sha512-Ltz46QR9QvEuhlNy+zvs+5ylfpGXzDBh+jVC1/WL8w88uEbN+Wf1bJ/Bi5OWVWWEgXJxfcmlavN4hJOqtQuqgQ== 26 | dependencies: 27 | "@xstate/graph" "^1.4.1" 28 | chalk "^2.4.2" 29 | 30 | ansi-styles@^3.2.1: 31 | version "3.2.1" 32 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 33 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 34 | dependencies: 35 | color-convert "^1.9.0" 36 | 37 | chalk@^2.4.2: 38 | version "2.4.2" 39 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 40 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 41 | dependencies: 42 | ansi-styles "^3.2.1" 43 | escape-string-regexp "^1.0.5" 44 | supports-color "^5.3.0" 45 | 46 | color-convert@^1.9.0: 47 | version "1.9.3" 48 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 49 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 50 | dependencies: 51 | color-name "1.1.3" 52 | 53 | color-name@1.1.3: 54 | version "1.1.3" 55 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 56 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 57 | 58 | escape-string-regexp@^1.0.5: 59 | version "1.0.5" 60 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 61 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 62 | 63 | has-flag@^3.0.0: 64 | version "3.0.0" 65 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 66 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 67 | 68 | playwright-core@1.22.0: 69 | version "1.22.0" 70 | resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.22.0.tgz#e738a0c83f4be0984c953ab25d2bc77545187ecc" 71 | integrity sha512-XnDPiV4NCzTtXWxQdyJ6Wg8xhST3ciUjt5mITaxoqOoYggmRtofKm0PXLehfbetXh2ppPYj5U8UhtUpdIE4wag== 72 | 73 | supports-color@^5.3.0: 74 | version "5.5.0" 75 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 76 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 77 | dependencies: 78 | has-flag "^3.0.0" 79 | 80 | typescript@^4.6.4: 81 | version "4.6.4" 82 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" 83 | integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== 84 | 85 | xstate@^4.32.0: 86 | version "4.32.0" 87 | resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.32.0.tgz#49b929386f044343d9d3608fe371827178d086d4" 88 | integrity sha512-62gETqwnw4pBRe+tVWMt8hLgWEU8lq2qO8VN5PWmTELceRVt3I1bu1cwdraVRHUn4Bb2lnhNzn1A73oShuC+8g== 89 | --------------------------------------------------------------------------------