From 83c0ba14545a022d1d769e067663beb17f9d48c3 Mon Sep 17 00:00:00 2001 From: with-heart Date: Tue, 23 Jul 2024 10:51:30 -0400 Subject: [PATCH 01/29] install and create basic vitest configs --- package.json | 5 +- packages/core/vitest.config.mts | 7 + packages/xstate-graph/vitest.config.mts | 7 + packages/xstate-immer/vitest.config.mts | 7 + packages/xstate-inspect/vitest.config.mts | 7 + packages/xstate-react/vitest.config.mts | 7 + packages/xstate-solid/vitest.config.mts | 7 + packages/xstate-store/vitest.config.mts | 7 + packages/xstate-svelte/vitest.config.mts | 7 + packages/xstate-vue/vitest.config.mts | 7 + tsconfig.json | 3 +- vitest.workspace.json | 1 + yarn.lock | 592 +++++++++++++++++++++- 13 files changed, 660 insertions(+), 4 deletions(-) create mode 100644 packages/core/vitest.config.mts create mode 100644 packages/xstate-graph/vitest.config.mts create mode 100644 packages/xstate-immer/vitest.config.mts create mode 100644 packages/xstate-inspect/vitest.config.mts create mode 100644 packages/xstate-react/vitest.config.mts create mode 100644 packages/xstate-solid/vitest.config.mts create mode 100644 packages/xstate-store/vitest.config.mts create mode 100644 packages/xstate-svelte/vitest.config.mts create mode 100644 packages/xstate-vue/vitest.config.mts create mode 100644 vitest.workspace.json diff --git a/package.json b/package.json index c26ca60d8c..7214f608a3 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "build": "preconstruct build", "fix": "manypkg fix", "typecheck": "tsc", - "test": "jest", - "test:core": "jest packages/core", + "test": "vitest", + "test:core": "vitest packages/core", "changeset": "changeset", "release": "changeset publish", "version": "changeset version && node ./scripts/bump-peer-dep-ranges.js" @@ -78,6 +78,7 @@ "svelte-jester": "^2.3.2", "synckit": "^0.8.5", "typescript": "^5.5.4", + "vitest": "^2.0.3", "vue": "^3.0.11" }, "husky": { diff --git a/packages/core/vitest.config.mts b/packages/core/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/core/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-graph/vitest.config.mts b/packages/xstate-graph/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-graph/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-immer/vitest.config.mts b/packages/xstate-immer/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-immer/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-inspect/vitest.config.mts b/packages/xstate-inspect/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-inspect/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-react/vitest.config.mts b/packages/xstate-react/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-react/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-solid/vitest.config.mts b/packages/xstate-solid/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-solid/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-store/vitest.config.mts b/packages/xstate-store/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-store/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-svelte/vitest.config.mts b/packages/xstate-svelte/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-svelte/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/packages/xstate-vue/vitest.config.mts b/packages/xstate-vue/vitest.config.mts new file mode 100644 index 0000000000..58fb522a9a --- /dev/null +++ b/packages/xstate-vue/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + globals: true + } +}); diff --git a/tsconfig.json b/tsconfig.json index bc744106d1..a5bdd3c734 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "jsx": "react-jsx", "lib": ["es2019", "ESNext.Promise", "dom"], "strict": true, - "stripInternal": true + "stripInternal": true, + "types": ["vitest/globals"] }, "exclude": ["examples", "templates"] } diff --git a/vitest.workspace.json b/vitest.workspace.json new file mode 100644 index 0000000000..6ad1700069 --- /dev/null +++ b/vitest.workspace.json @@ -0,0 +1 @@ +["packages/*"] diff --git a/yarn.lock b/yarn.lock index b7598e7cc9..8a55d2e9ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ampproject/remapping@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.4", "@babel/code-frame@^7.5.5": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.4.tgz#03ae5af150be94392cb5c7ccd97db5a19a5da6aa" @@ -1279,6 +1287,121 @@ human-id "^1.0.2" prettier "^2.7.1" +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1564,6 +1687,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" @@ -1579,6 +1711,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + "@jridgewell/source-map@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" @@ -1597,6 +1734,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" @@ -1613,6 +1755,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@manypkg/cli@^0.16.1": version "0.16.2" resolved "https://registry.yarnpkg.com/@manypkg/cli/-/cli-0.16.2.tgz#e949583d83fd051d0cb732d24657967b1f81c591" @@ -1827,6 +1977,86 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/rollup-android-arm-eabi@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz#3d9fd50164b94964f5de68c3c4ce61933b3a338d" + integrity sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w== + +"@rollup/rollup-android-arm64@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz#e1a6d4bca2eb08c84fd996a4bf896ce4b6f4014c" + integrity sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw== + +"@rollup/rollup-darwin-arm64@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz#0a3fffea69489a24a96079af414b0be78df8abbc" + integrity sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA== + +"@rollup/rollup-darwin-x64@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz#13fbdb15f58f090871b0ffff047ece06ad6ad74c" + integrity sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg== + +"@rollup/rollup-linux-arm-gnueabihf@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz#e9d9219ddf6f6e946e2ee322198af12466d2c868" + integrity sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw== + +"@rollup/rollup-linux-arm-musleabihf@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz#4ba804a00b5e793196a622f6977e05f23e01f59a" + integrity sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ== + +"@rollup/rollup-linux-arm64-gnu@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz#d871e3f41de759a6db27fc99235b782ba47c15cc" + integrity sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug== + +"@rollup/rollup-linux-arm64-musl@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz#6e63f7ad4cc51bd2c693a2826fd279de9eaa05b5" + integrity sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz#1540b284d91c440bc9fa7a1714cfb71a5597e94d" + integrity sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ== + +"@rollup/rollup-linux-riscv64-gnu@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz#70ae58103b5bc7ba2e2235738b51d97022c8ef92" + integrity sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg== + +"@rollup/rollup-linux-s390x-gnu@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz#579ca5f271421a961d3c73d221202c79e02ff03a" + integrity sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA== + +"@rollup/rollup-linux-x64-gnu@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz#f0282d761b8b4e7b92b236813475248e37231849" + integrity sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA== + +"@rollup/rollup-linux-x64-musl@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz#65da807ac66c505ad14b76f1e5976006cb67dd5f" + integrity sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A== + +"@rollup/rollup-win32-arm64-msvc@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz#1eed24b91f421c2eea8bb7ca8889ba0c867e1780" + integrity sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg== + +"@rollup/rollup-win32-ia32-msvc@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz#1ed93c9cdc84e185359797a686f4d1576afcea58" + integrity sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q== + +"@rollup/rollup-win32-x64-msvc@4.19.0": + version "4.19.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz#baf9b65023ea2ecc5e6ec68f787a0fecfd8ee84c" + integrity sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag== + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" @@ -2015,6 +2245,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@1.0.5", "@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -2229,6 +2464,57 @@ dependencies: "@types/yargs-parser" "*" +"@vitest/expect@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.0.3.tgz#367727256f2a253e21a3e69cd996af51fc7899b1" + integrity sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg== + dependencies: + "@vitest/spy" "2.0.3" + "@vitest/utils" "2.0.3" + chai "^5.1.1" + tinyrainbow "^1.2.0" + +"@vitest/pretty-format@2.0.3", "@vitest/pretty-format@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.0.3.tgz#30af705250cd055890091999e467968e41872c82" + integrity sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.0.3.tgz#4310ff4583d7874f57b5a8a194062bb85f07b0df" + integrity sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ== + dependencies: + "@vitest/utils" "2.0.3" + pathe "^1.1.2" + +"@vitest/snapshot@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.0.3.tgz#31acf5906f8c12f9c7fde21b84cc28f043e983b1" + integrity sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg== + dependencies: + "@vitest/pretty-format" "2.0.3" + magic-string "^0.30.10" + pathe "^1.1.2" + +"@vitest/spy@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.0.3.tgz#62a14f6d7ec4f13caeeecac42d37f903f68c83c1" + integrity sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A== + dependencies: + tinyspy "^3.0.0" + +"@vitest/utils@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.0.3.tgz#3c57f5338e49c91e3c4ac5be8c74ae22a3c2d5b4" + integrity sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg== + dependencies: + "@vitest/pretty-format" "2.0.3" + estree-walker "^3.0.3" + loupe "^3.1.1" + tinyrainbow "^1.2.0" + "@vue/compiler-core@3.0.11": version "3.0.11" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.11.tgz#5ef579e46d7b336b8735228758d1c2c505aae69a" @@ -2590,6 +2876,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -2854,6 +3145,11 @@ builtins@^1.0.3: resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2948,6 +3244,17 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" + integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -3001,6 +3308,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + chokidar@^3.4.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -3423,7 +3735,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.0: +debug@^4.0.0, debug@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -3477,6 +3789,11 @@ dedent@^1.0.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -3742,6 +4059,35 @@ es6-promise@^3.1.2: resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3789,6 +4135,13 @@ estree-walker@^2.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -3827,6 +4180,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exit@^0.1.2, exit@~0.1.1, exit@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4228,6 +4596,11 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -4279,6 +4652,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-func-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" @@ -4322,6 +4700,11 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -4784,6 +5167,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + husky@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/husky/-/husky-3.1.0.tgz#5faad520ab860582ed94f0c1a77f0f04c90b57c0" @@ -5265,6 +5653,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -6289,6 +6682,13 @@ loose-envify@^1.1.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" + integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== + dependencies: + get-func-name "^2.0.1" + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -6347,6 +6747,13 @@ magic-string@^0.30.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.30.10: + version "0.30.10" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" + integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -6736,6 +7143,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -6837,6 +7249,11 @@ nanoid@^3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.21.tgz#25bfee7340ac4185866fbfb2c9006d299da1be7f" integrity sha512-A6oZraK4DJkAOICstsGH98dvycPr/4GGDH7ZWKmMdd3vGcOurZ6JmWFUt0DA5bzrrn2FrUjmv6mFNWvv8jpppA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -6966,6 +7383,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + npm-which@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" @@ -7077,6 +7501,13 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + open@^7.4.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" @@ -7350,6 +7781,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -7379,6 +7815,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -7389,6 +7835,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -7530,6 +7981,15 @@ postcss@^8.1.10: nanoid "^3.1.20" source-map "^0.6.1" +postcss@^8.4.39: + version "8.4.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" + integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + preferred-pm@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.0.2.tgz#bbdbef1014e34a7490349bf70d6d244b8d57a5e1" @@ -8031,6 +8491,31 @@ rollup@^2.79.1: optionalDependencies: fsevents "~2.3.2" +rollup@^4.13.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.0.tgz#83b08cc0b2bc38c26c194cb7f2cdabd84a2a8c02" + integrity sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.19.0" + "@rollup/rollup-android-arm64" "4.19.0" + "@rollup/rollup-darwin-arm64" "4.19.0" + "@rollup/rollup-darwin-x64" "4.19.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.19.0" + "@rollup/rollup-linux-arm-musleabihf" "4.19.0" + "@rollup/rollup-linux-arm64-gnu" "4.19.0" + "@rollup/rollup-linux-arm64-musl" "4.19.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.19.0" + "@rollup/rollup-linux-riscv64-gnu" "4.19.0" + "@rollup/rollup-linux-s390x-gnu" "4.19.0" + "@rollup/rollup-linux-x64-gnu" "4.19.0" + "@rollup/rollup-linux-x64-musl" "4.19.0" + "@rollup/rollup-win32-arm64-msvc" "4.19.0" + "@rollup/rollup-win32-ia32-msvc" "4.19.0" + "@rollup/rollup-win32-x64-msvc" "4.19.0" + fsevents "~2.3.2" + run-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" @@ -8222,11 +8707,21 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-git@^1.85.0: version "1.132.0" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.132.0.tgz#53ac4c5ec9e74e37c2fd461e23309f22fcdf09b1" @@ -8336,6 +8831,11 @@ source-map-js@^1.0.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -8469,6 +8969,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + staged-git-files@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b" @@ -8482,6 +8987,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + stream-consume@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" @@ -8626,6 +9136,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -8783,6 +9298,26 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +tinybench@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b" + integrity sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw== + +tinypool@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238" + integrity sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585" + integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9180,6 +9715,53 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite-node@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.0.3.tgz#449b1524178304ba764bd33062bd31a09c5e673f" + integrity sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg== + dependencies: + cac "^6.7.14" + debug "^4.3.5" + pathe "^1.1.2" + tinyrainbow "^1.2.0" + vite "^5.0.0" + +vite@^5.0.0: + version "5.3.4" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.4.tgz#b36ebd47c8a5e3a8727046375d5f10bf9fdf8715" + integrity sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.39" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.0.3.tgz#daf7e43c9415c6825922ae3a63cac452d1ac705f" + integrity sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@vitest/expect" "2.0.3" + "@vitest/pretty-format" "^2.0.3" + "@vitest/runner" "2.0.3" + "@vitest/snapshot" "2.0.3" + "@vitest/spy" "2.0.3" + "@vitest/utils" "2.0.3" + chai "^5.1.1" + debug "^4.3.5" + execa "^8.0.1" + magic-string "^0.30.10" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.8.0" + tinypool "^1.0.0" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.0.3" + why-is-node-running "^2.2.2" + vue@^3.0.11: version "3.0.11" resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f" @@ -9273,6 +9855,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.2.2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" From 0168bb9de7213ab62e62df55ff59d87e9542a70a Mon Sep 17 00:00:00 2001 From: with-heart Date: Wed, 31 Jul 2024 12:32:23 -0400 Subject: [PATCH 02/29] replace jest.* with vi.* in tests --- packages/core/test/actions.test.ts | 114 +++++++++--------- packages/core/test/activities.test.ts | 4 +- packages/core/test/actor.test.ts | 16 +-- packages/core/test/actorLogic.test.ts | 28 ++--- packages/core/test/after.test.ts | 32 ++--- packages/core/test/emit.test.ts | 16 +-- packages/core/test/errors.test.ts | 32 ++--- packages/core/test/final.test.ts | 28 ++--- packages/core/test/getNextSnapshot.test.ts | 2 +- packages/core/test/guards.test.ts | 34 +++--- packages/core/test/history.test.ts | 16 +-- packages/core/test/input.test.ts | 10 +- .../core/test/internalTransitions.test.ts | 8 +- packages/core/test/interpreter.test.ts | 30 ++--- packages/core/test/invoke.test.ts | 6 +- packages/core/test/machine.test.ts | 8 +- packages/core/test/parallel.test.ts | 4 +- packages/core/test/rehydration.test.ts | 12 +- packages/core/test/spawnChild.test.ts | 2 +- packages/core/test/stateIn.test.ts | 2 +- packages/core/test/system.test.ts | 6 +- packages/core/test/transient.test.ts | 2 +- packages/core/test/waitFor.test.ts | 2 +- packages/xstate-inspect/test/inspect.test.ts | 6 +- .../test/createActorContext.test.tsx | 8 +- packages/xstate-react/test/useActor.test.tsx | 14 +-- .../xstate-react/test/useActorRef.test.tsx | 12 +- .../xstate-react/test/useSelector.test.tsx | 4 +- .../xstate-solid/test/fromActorRef.test.tsx | 4 +- packages/xstate-solid/test/useActor.test.tsx | 16 +-- 30 files changed, 239 insertions(+), 239 deletions(-) diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index af26f1f944..1591f13ed9 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -422,9 +422,9 @@ describe('entry/exit actions', () => { }); it('should work with function actions', () => { - const entrySpy = jest.fn(); - const exitSpy = jest.fn(); - const transitionSpy = jest.fn(); + const entrySpy = vi.fn(); + const exitSpy = vi.fn(); + const transitionSpy = vi.fn(); const machine = createMachine({ initial: 'a', @@ -553,7 +553,7 @@ describe('entry/exit actions', () => { }); it("shouldn't use a referenced custom action over a builtin one when there is a naming conflict", () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { context: { @@ -580,7 +580,7 @@ describe('entry/exit actions', () => { }); it("shouldn't use a referenced custom action over an inline one when there is a naming conflict", () => { - const spy = jest.fn(); + const spy = vi.fn(); let called = false; const machine = createMachine( @@ -609,8 +609,8 @@ describe('entry/exit actions', () => { }); it('root entry/exit actions should be called on root reentering transitions', () => { - let entrySpy = jest.fn(); - let exitSpy = jest.fn(); + let entrySpy = vi.fn(); + let exitSpy = vi.fn(); const machine = createMachine({ id: 'root', @@ -1464,8 +1464,8 @@ describe('entry/exit actions', () => { describe('when stopped', () => { it('exit actions should not be called when stopping a machine', () => { - const rootSpy = jest.fn(); - const childSpy = jest.fn(); + const rootSpy = vi.fn(); + const childSpy = vi.fn(); const machine = createMachine({ exit: rootSpy, @@ -1622,7 +1622,7 @@ describe('entry/exit actions', () => { }); it('sent events from exit handlers of a stopped child should not be received by its children', () => { - const spy = jest.fn(); + const spy = vi.fn(); const grandchild = createMachine({ id: 'grandchild', @@ -1665,7 +1665,7 @@ describe('entry/exit actions', () => { }); it('sent events from exit handlers of a done child should be received by its children', () => { - const spy = jest.fn(); + const spy = vi.fn(); const grandchild = createMachine({ id: 'grandchild', @@ -1736,7 +1736,7 @@ describe('entry/exit actions', () => { }); it('should note execute referenced custom actions correctly when stopping an interpreter', () => { - const spy = jest.fn(); + const spy = vi.fn(); const parent = createMachine( { id: 'parent', @@ -1947,7 +1947,7 @@ describe('initial actions', () => { }); it('should execute actions of initial transitions only once when taking an explicit transition', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -1995,7 +1995,7 @@ describe('initial actions', () => { }); it('should execute actions of all initial transitions resolving to the initial state value', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: { target: 'a', @@ -2029,7 +2029,7 @@ describe('initial actions', () => { }); it('should execute actions of the initial transition when taking a root reentering self-transition', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ id: 'root', initial: { @@ -2066,7 +2066,7 @@ describe('initial actions', () => { describe('actions on invalid transition', () => { it('should not recall previous actions', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'idle', states: { @@ -2104,7 +2104,7 @@ describe('actions config', () => { const definedAction = () => {}; it('should reference actions defined in actions parameter of machine options (entry actions)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -2133,7 +2133,7 @@ describe('actions config', () => { }); it('should reference actions defined in actions parameter of machine options (initial state)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { entry: ['definedAction', { type: 'definedAction' }, 'undefinedAction'] @@ -2235,7 +2235,7 @@ describe('actions config', () => { describe('action meta', () => { it('should provide the original params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const testMachine = createMachine( { @@ -2269,7 +2269,7 @@ describe('action meta', () => { }); it('should provide undefined params when it was configured as string', () => { - const spy = jest.fn(); + const spy = vi.fn(); const testMachine = createMachine( { @@ -2296,7 +2296,7 @@ describe('action meta', () => { }); it('should provide the action with resolved params when they are dynamic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2322,7 +2322,7 @@ describe('action meta', () => { }); it('should resolve dynamic params using context value', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2351,7 +2351,7 @@ describe('action meta', () => { }); it('should resolve dynamic params using event value', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2506,7 +2506,7 @@ describe('forwardTo()', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -2527,7 +2527,7 @@ describe('forwardTo()', () => { describe('log()', () => { it('should log a string', () => { - const consoleSpy = jest.fn(); + const consoleSpy = vi.fn(); console.log = consoleSpy; const machine = createMachine({ entry: log('some string', 'string label') @@ -2545,7 +2545,7 @@ describe('log()', () => { }); it('should log an expression', () => { - const consoleSpy = jest.fn(); + const consoleSpy = vi.fn(); console.log = consoleSpy; const machine = createMachine({ context: { @@ -2568,7 +2568,7 @@ describe('log()', () => { describe('enqueueActions', () => { it('should execute a simple referenced action', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2589,8 +2589,8 @@ describe('enqueueActions', () => { }); it('should execute multiple different referenced actions', () => { - const spy1 = jest.fn(); - const spy2 = jest.fn(); + const spy1 = vi.fn(); + const spy2 = vi.fn(); const machine = createMachine( { @@ -2614,7 +2614,7 @@ describe('enqueueActions', () => { }); it('should execute multiple same referenced actions', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2636,7 +2636,7 @@ describe('enqueueActions', () => { }); it('should execute a parameterized action', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2668,7 +2668,7 @@ describe('enqueueActions', () => { }); it('should execute a function', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: enqueueActions(({ enqueue }) => { @@ -2682,7 +2682,7 @@ describe('enqueueActions', () => { }); it('should execute a builtin action using its own action creator', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ on: { @@ -2709,7 +2709,7 @@ describe('enqueueActions', () => { }); it('should execute a builtin action using its bound action creator', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ on: { @@ -2751,7 +2751,7 @@ describe('enqueueActions', () => { }); it('should be able to check a simple referenced guard', () => { - const spy = jest.fn().mockImplementation(() => true); + const spy = vi.fn().mockImplementation(() => true); const machine = createMachine( { context: { @@ -2774,7 +2774,7 @@ describe('enqueueActions', () => { }); it('should be able to check a parameterized guard', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -2860,7 +2860,7 @@ describe('enqueueActions', () => { } }); - const spy = jest.fn(); + const spy = vi.fn(); const parentMachine = setup({ types: {} as { events: ParentEvent }, @@ -2906,7 +2906,7 @@ describe('enqueueActions', () => { entry: 'sendToParent' }); - const parentSpy = jest.fn(); + const parentSpy = vi.fn(); const parentMachine = setup({ types: {} as { events: ParentEvent }, @@ -3159,7 +3159,7 @@ describe('sendTo', () => { entry: sendTo('child', 'a string') }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -3365,7 +3365,7 @@ describe('raise', () => { ) }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -3420,8 +3420,8 @@ describe('cancel', () => { }); it('should cancel only the delayed event in the machine that scheduled it when canceling the event with the same ID in the machine that sent it first', async () => { - const fooSpy = jest.fn(); - const barSpy = jest.fn(); + const fooSpy = vi.fn(); + const barSpy = vi.fn(); const machine = createMachine({ invoke: [ @@ -3468,8 +3468,8 @@ describe('cancel', () => { }); it('should cancel only the delayed event in the machine that scheduled it when canceling the event with the same ID in the machine that sent it second', async () => { - const fooSpy = jest.fn(); - const barSpy = jest.fn(); + const fooSpy = vi.fn(); + const barSpy = vi.fn(); const machine = createMachine({ invoke: [ @@ -3516,7 +3516,7 @@ describe('cancel', () => { }); it('should not try to clear an undefined timeout when canceling an unscheduled timer', async () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ on: { @@ -3758,7 +3758,7 @@ describe('actions', () => { }); it('should call an inline action responding to an initial raise with the raised event', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: raise({ type: 'HELLO' }), @@ -3777,7 +3777,7 @@ describe('actions', () => { }); it('should call a referenced action responding to an initial raise with the raised event', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -3803,7 +3803,7 @@ describe('actions', () => { }); it('should call an inline action responding to an initial raise with updated (non-initial) context', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ context: { count: 0 }, @@ -3823,7 +3823,7 @@ describe('actions', () => { }); it('should call a referenced action responding to an initial raise with updated (non-initial) context', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -3850,7 +3850,7 @@ describe('actions', () => { }); it('should call inline entry custom action with undefined parametrized action object', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine({ entry: (_, params) => { @@ -3863,7 +3863,7 @@ describe('actions', () => { }); it('should call inline entry builtin action with undefined parametrized action object', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine({ entry: assign((_, params) => { @@ -3877,7 +3877,7 @@ describe('actions', () => { }); it('should call inline transition custom action with undefined parametrized action object', () => { - const spy = jest.fn(); + const spy = vi.fn(); const actorRef = createActor( createMachine({ @@ -3896,7 +3896,7 @@ describe('actions', () => { }); it('should call inline transition builtin action with undefined parameters', () => { - const spy = jest.fn(); + const spy = vi.fn(); const actorRef = createActor( createMachine({ @@ -3916,7 +3916,7 @@ describe('actions', () => { }); it('should call a referenced custom action with undefined params when it has no params and it is referenced using a string', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine( @@ -3937,7 +3937,7 @@ describe('actions', () => { }); it('should call a referenced builtin action with undefined params when it has no params and it is referenced using a string', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine( @@ -3959,7 +3959,7 @@ describe('actions', () => { }); it('should call a referenced custom action with the provided parametrized action object', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine( @@ -3987,7 +3987,7 @@ describe('actions', () => { }); it('should call a referenced builtin action with the provided parametrized action object', () => { - const spy = jest.fn(); + const spy = vi.fn(); createActor( createMachine( diff --git a/packages/core/test/activities.test.ts b/packages/core/test/activities.test.ts index 93a56bae27..0174530d60 100644 --- a/packages/core/test/activities.test.ts +++ b/packages/core/test/activities.test.ts @@ -246,7 +246,7 @@ describe('invocations (activities)', () => { }); it('should remember the invocations even after an ignored event', () => { - let cleanupSpy = jest.fn(); + let cleanupSpy = vi.fn(); let active = false; const machine = createMachine({ initial: 'A', @@ -279,7 +279,7 @@ describe('invocations (activities)', () => { }); it('should remember the invocations when transitioning within the invoking state', () => { - let cleanupSpy = jest.fn(); + let cleanupSpy = vi.fn(); let active = false; const machine = createMachine({ initial: 'A', diff --git a/packages/core/test/actor.test.ts b/packages/core/test/actor.test.ts index d21488fc00..31c343f475 100644 --- a/packages/core/test/actor.test.ts +++ b/packages/core/test/actor.test.ts @@ -384,7 +384,7 @@ describe('spawning callbacks', () => { }); it('should not deliver events sent to the parent after the callback actor gets stopped', () => { - const spy = jest.fn(); + const spy = vi.fn(); let sendToParent: () => void; @@ -600,7 +600,7 @@ describe('spawning observables', () => { await waitFor(actorRef, (state) => state.matches('active')); - const spy = jest.fn(); + const spy = vi.fn(); actorRef.getSnapshot().children.childActor!.subscribe((data) => { spy(data.context); @@ -654,7 +654,7 @@ describe('spawning observables', () => { await waitFor(actorRef, (state) => state.matches('active')); - const spy = jest.fn(); + const spy = vi.fn(); actorRef.getSnapshot().children.childActor!.subscribe((data) => { spy(data); @@ -964,8 +964,8 @@ describe('actors', () => { }); it('should stop multiple inline spawned actors that have no explicit ids', () => { - const cleanup1 = jest.fn(); - const cleanup2 = jest.fn(); + const cleanup1 = vi.fn(); + const cleanup2 = vi.fn(); const parent = createMachine({ context: ({ spawn }) => ({ @@ -984,8 +984,8 @@ describe('actors', () => { }); it('should stop multiple referenced spawned actors that have no explicit ids', () => { - const cleanup1 = jest.fn(); - const cleanup2 = jest.fn(); + const cleanup1 = vi.fn(); + const cleanup2 = vi.fn(); const parent = createMachine( { @@ -1693,7 +1693,7 @@ describe('actors', () => { }); it('should be possible to pass `self` as input to a child machine from within the context factory', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ types: {} as { diff --git a/packages/core/test/actorLogic.test.ts b/packages/core/test/actorLogic.test.ts index 7f37a7f145..7a23aefb0b 100644 --- a/packages/core/test/actorLogic.test.ts +++ b/packages/core/test/actorLogic.test.ts @@ -235,7 +235,7 @@ describe('promise logic (fromPromise)', () => { it('should abort when stopping', async () => { const deferred = withResolvers(); - const fn = jest.fn(); + const fn = vi.fn(); const promiseLogic = fromPromise((ctx) => { return new Promise((res) => { ctx.signal.addEventListener('abort', fn); @@ -253,14 +253,14 @@ describe('promise logic (fromPromise)', () => { it('should not abort when stopped if promise is resolved/rejected', async () => { const resolvedDeferred = withResolvers(); - const resolvedSignalListener = jest.fn(); + const resolvedSignalListener = vi.fn(); const resolvedPromiseLogic = fromPromise((ctx) => { ctx.signal.addEventListener('abort', resolvedSignalListener); return resolvedDeferred.promise; }); const rejectedDeferred = withResolvers(); - const rejectedSignalListener = jest.fn(); + const rejectedSignalListener = vi.fn(); const rejectedPromiseLogic = fromPromise((ctx) => { ctx.signal.addEventListener('abort', rejectedSignalListener); return rejectedDeferred.promise.catch(() => {}); @@ -283,10 +283,10 @@ describe('promise logic (fromPromise)', () => { it('should not reuse the same signal for different actors with same logic', async () => { let deferredMap: Map> = new Map(); - let signalListenerMap: Map = new Map(); + let signalListenerMap: Map = new Map(); const p = fromPromise(({ self, signal }) => { const deferred = withResolvers(); - const signalListener = jest.fn(); + const signalListener = vi.fn(); deferredMap.set(self.id, deferred); signalListenerMap.set(self.id, signalListener); signal.addEventListener('abort', signalListener); @@ -343,10 +343,10 @@ describe('promise logic (fromPromise)', () => { it('should not reuse the same signal for different actors with same logic and id', async () => { let deferredList: PromiseWithResolvers[] = []; - let signalListenerList: jest.Mock[] = []; + let signalListenerList: vi.Mock[] = []; const p = fromPromise(({ signal }) => { const deferred = withResolvers(); - const fn = jest.fn(); + const fn = vi.fn(); deferredList.push(deferred); signalListenerList.push(fn); signal.addEventListener('abort', fn); @@ -407,10 +407,10 @@ describe('promise logic (fromPromise)', () => { it('should not reuse the same signal for the same actor when restarted', async () => { let deferredList: PromiseWithResolvers[] = []; - let signalListenerList: jest.Mock[] = []; + let signalListenerList: vi.Mock[] = []; const p = fromPromise(({ signal }) => { const deferred = withResolvers(); - const fn = jest.fn(); + const fn = vi.fn(); deferredList.push(deferred); signalListenerList.push(fn); signal.addEventListener('abort', fn); @@ -561,7 +561,7 @@ describe('observable logic (fromObservable)', () => { it('should resolve', () => { const actor = createActor(fromObservable(() => of(42))); - const spy = jest.fn(); + const spy = vi.fn(); actor.subscribe((snapshot) => spy(snapshot.context)); @@ -572,7 +572,7 @@ describe('observable logic (fromObservable)', () => { it('should resolve (observer .next)', () => { const actor = createActor(fromObservable(() => of(42))); - const spy = jest.fn(); + const spy = vi.fn(); actor.subscribe({ next: (snapshot) => spy(snapshot.context) @@ -586,7 +586,7 @@ describe('observable logic (fromObservable)', () => { const actor = createActor( fromObservable(() => throwError(() => 'Observable error.')) ); - const spy = jest.fn(); + const spy = vi.fn(); actor.subscribe({ error: spy @@ -604,7 +604,7 @@ describe('observable logic (fromObservable)', () => { it('should complete (observer .complete)', () => { const actor = createActor(fromObservable(() => EMPTY)); - const spy = jest.fn(); + const spy = vi.fn(); actor.subscribe({ complete: spy @@ -740,7 +740,7 @@ describe('callback logic (fromCallback)', () => { }); it('should persist the input of a callback', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { types: {} as { events: { type: 'EV'; data: number } }, diff --git a/packages/core/test/after.test.ts b/packages/core/test/after.test.ts index 73c4357b2c..655bb4399d 100644 --- a/packages/core/test/after.test.ts +++ b/packages/core/test/after.test.ts @@ -27,26 +27,26 @@ const lightMachine = createMachine({ }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describe('delayed transitions', () => { it('should transition after delay', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const actorRef = createActor(lightMachine).start(); expect(actorRef.getSnapshot().value).toBe('green'); - jest.advanceTimersByTime(500); + vi.advanceTimersByTime(500); expect(actorRef.getSnapshot().value).toBe('green'); - jest.advanceTimersByTime(510); + vi.advanceTimersByTime(510); expect(actorRef.getSnapshot().value).toBe('yellow'); }); it('should not try to clear an undefined timeout when exiting source state of a delayed transition', async () => { // https://github.com/statelyai/xstate/issues/5001 - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'green', @@ -154,8 +154,8 @@ describe('delayed transitions', () => { }); it('should defer a single send event for a delayed conditional transition (#886)', () => { - jest.useFakeTimers(); - const spy = jest.fn(); + vi.useFakeTimers(); + const spy = vi.fn(); const machine = createMachine({ initial: 'X', states: { @@ -185,7 +185,7 @@ describe('delayed transitions', () => { createActor(machine).start(); - jest.advanceTimersByTime(10); + vi.advanceTimersByTime(10); expect(spy).not.toHaveBeenCalled(); }); @@ -256,8 +256,8 @@ describe('delayed transitions', () => { describe('delay expressions', () => { it('should evaluate the expression (function) to determine the delay', () => { - jest.useFakeTimers(); - const spy = jest.fn(); + vi.useFakeTimers(); + const spy = vi.fn(); const context = { delay: 500 }; @@ -287,16 +287,16 @@ describe('delayed transitions', () => { expect(spy).toBeCalledWith(context); expect(actor.getSnapshot().value).toBe('inactive'); - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); expect(actor.getSnapshot().value).toBe('inactive'); - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(actor.getSnapshot().value).toBe('active'); }); it('should evaluate the expression (string) to determine the delay', () => { - jest.useFakeTimers(); - const spy = jest.fn(); + vi.useFakeTimers(); + const spy = vi.fn(); const machine = createMachine( { initial: 'inactive', @@ -334,10 +334,10 @@ describe('delayed transitions', () => { expect(spy).toBeCalledWith(event); expect(actor.getSnapshot().value).toBe('active'); - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); expect(actor.getSnapshot().value).toBe('active'); - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(actor.getSnapshot().value).toBe('inactive'); }); }); diff --git a/packages/core/test/emit.test.ts b/packages/core/test/emit.test.ts index 5fb2365a6a..e83eec6797 100644 --- a/packages/core/test/emit.test.ts +++ b/packages/core/test/emit.test.ts @@ -165,7 +165,7 @@ describe('event emitter', () => { }); it('listener should be able to read the updated snapshot of the emitting actor', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', @@ -195,7 +195,7 @@ describe('event emitter', () => { }); it('wildcard listeners should be able to receive all emitted events', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = setup({ types: { @@ -228,7 +228,7 @@ describe('event emitter', () => { }); it('events can be emitted from promise logic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromPromise( async ({ emit }) => { @@ -263,7 +263,7 @@ describe('event emitter', () => { }); it('events can be emitted from transition logic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromTransition< any, @@ -307,7 +307,7 @@ describe('event emitter', () => { }); it('events can be emitted from observable logic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromObservable( ({ emit }) => { @@ -350,7 +350,7 @@ describe('event emitter', () => { }); it('events can be emitted from event observable logic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromEventObservable< any, @@ -395,7 +395,7 @@ describe('event emitter', () => { }); it('events can be emitted from callback logic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromCallback( ({ emit }) => { @@ -430,7 +430,7 @@ describe('event emitter', () => { }); it('events can be emitted from callback logic (restored root)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const logic = fromCallback( ({ emit }) => { diff --git a/packages/core/test/errors.test.ts b/packages/core/test/errors.test.ts index 0a6cb0d1a1..d40946adb8 100644 --- a/packages/core/test/errors.test.ts +++ b/packages/core/test/errors.test.ts @@ -36,7 +36,7 @@ describe('error handling', () => { } }); - const spy = jest.fn().mockImplementation(() => { + const spy = vi.fn().mockImplementation(() => { throw new Error('no_infinite_loop_when_error_is_thrown_in_subscribe'); }); @@ -57,7 +57,7 @@ describe('error handling', () => { }); it(`doesn't crash the actor when an error is thrown in subscribe`, (done) => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ id: 'machine', @@ -79,7 +79,7 @@ describe('error handling', () => { } }); - const subscriber = jest.fn().mockImplementationOnce(() => { + const subscriber = vi.fn().mockImplementationOnce(() => { throw new Error('doesnt_crash_actor_when_error_is_thrown_in_subscribe'); }); @@ -119,12 +119,12 @@ describe('error handling', () => { } }); - const nextSpy = jest.fn().mockImplementation(() => { + const nextSpy = vi.fn().mockImplementation(() => { throw new Error( 'doesnt_notify_error_listener_when_error_is_thrown_in_subscribe' ); }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actor = createActor(machine).start(); @@ -207,7 +207,7 @@ describe('error handling', () => { }); it('unhandled rejection of a promise actor should be reported to the existing error listener of its parent', async () => { - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const machine = createMachine({ initial: 'pending', @@ -248,7 +248,7 @@ describe('error handling', () => { }); it('unhandled rejection of a promise actor should be reported to the existing error listener of its grandparent', async () => { - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const child = createMachine({ initial: 'pending', @@ -458,7 +458,7 @@ describe('error handling', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -489,7 +489,7 @@ describe('error handling', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -526,7 +526,7 @@ describe('error handling', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -546,7 +546,7 @@ describe('error handling', () => { }); it(`handled sync errors thrown when starting an actor shouldn't crash the parent`, () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'pending', @@ -713,7 +713,7 @@ describe('error handling', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -742,7 +742,7 @@ describe('error handling', () => { }) }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); @@ -785,7 +785,7 @@ describe('error handling', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -809,7 +809,7 @@ describe('error handling', () => { }); it(`shouldn't execute deferred initial actions that come after an action that errors`, () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: [ @@ -860,7 +860,7 @@ describe('error handling', () => { }); it('should error when a guard throws when transitioning', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { diff --git a/packages/core/test/final.test.ts b/packages/core/test/final.test.ts index 8f5d45fa1d..9782b1aab9 100644 --- a/packages/core/test/final.test.ts +++ b/packages/core/test/final.test.ts @@ -15,7 +15,7 @@ describe('final states', () => { expect(actorRef.getSnapshot().status).toBe('done'); }); it('output of a machine with a root state being final should be called with a "xstate.done.state.ROOT_ID" event', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'final', output: ({ event }) => { @@ -36,7 +36,7 @@ describe('final states', () => { `); }); it('should emit the "xstate.done.state.*" event when all nested states are in their final states', () => { - const onDoneSpy = jest.fn(); + const onDoneSpy = vi.fn(); const machine = createMachine({ id: 'm', @@ -182,7 +182,7 @@ describe('final states', () => { }); it("should only call data expression once when entering root's final state", () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'start', states: { @@ -224,7 +224,7 @@ describe('final states', () => { }); it('state output should be able to use context updated by the entry action of the reached final state', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ context: { count: 0 @@ -577,7 +577,7 @@ describe('final states', () => { expect(actorRef.getSnapshot().status).toEqual('done'); }); it('root output should be called with a "xstate.done.state.*" event of the parallel root when a direct final child of that parallel root is reached', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', states: { @@ -605,7 +605,7 @@ describe('final states', () => { }); it('root output should be called with a "xstate.done.state.*" event of the parallel root when a final child of its compound child is reached', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', states: { @@ -638,7 +638,7 @@ describe('final states', () => { }); it('root output should be called with a "xstate.done.state.*" event of the parallel root when a final descendant is reached 2 parallel levels deep', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', states: { @@ -676,7 +676,7 @@ describe('final states', () => { }); it('onDone of an outer parallel state should be called with its own "xstate.done.state.*" event when its direct parallel child completes', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -720,7 +720,7 @@ describe('final states', () => { }); it('onDone should not be called when the machine reaches its final state', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', states: { @@ -754,7 +754,7 @@ describe('final states', () => { }); it('machine should not complete when a parallel child of a compound state completes', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -780,7 +780,7 @@ describe('final states', () => { }); it('root output should only be called once when multiple parallel regions complete at once', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', @@ -801,7 +801,7 @@ describe('final states', () => { }); it('onDone of a parallel state should only be called once when multiple parallel regions complete at once', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', @@ -1083,7 +1083,7 @@ describe('final states', () => { }); it('should not resolve output of a final state if its parent is a parallel state', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'A', @@ -1112,7 +1112,7 @@ describe('final states', () => { }); it('should only call exit actions once when a child machine reaches its final state and sends an event to its parent that ends up stopping that child', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ initial: 'start', diff --git a/packages/core/test/getNextSnapshot.test.ts b/packages/core/test/getNextSnapshot.test.ts index 76759391d2..c30b70ac8f 100644 --- a/packages/core/test/getNextSnapshot.test.ts +++ b/packages/core/test/getNextSnapshot.test.ts @@ -52,7 +52,7 @@ describe('getNextSnapshot', () => { expect(s2.value).toEqual('c'); }); it('should not execute actions', () => { - const fn = jest.fn(); + const fn = vi.fn(); const machine = createMachine({ initial: 'a', diff --git a/packages/core/test/guards.test.ts b/packages/core/test/guards.test.ts index 1fdadf4e5f..2f011c88aa 100644 --- a/packages/core/test/guards.test.ts +++ b/packages/core/test/guards.test.ts @@ -239,7 +239,7 @@ describe('guard conditions', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -472,7 +472,7 @@ describe('custom guards', () => { }); it('should provide the undefined params if a guard was configured using a string', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -499,7 +499,7 @@ describe('custom guards', () => { }); it('should provide the guard with resolved params when they are dynamic', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -528,7 +528,7 @@ describe('custom guards', () => { }); it('should resolve dynamic params using context value', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -563,7 +563,7 @@ describe('custom guards', () => { }); it('should resolve dynamic params using event value', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -596,7 +596,7 @@ describe('custom guards', () => { }); it('should call a referenced `not` guard that embeds an inline function guard with undefined params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -630,7 +630,7 @@ describe('custom guards', () => { }); it('should call a string guard referenced by referenced `not` with undefined params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -662,7 +662,7 @@ describe('custom guards', () => { }); it('should call an object guard referenced by referenced `not` with its own params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -697,7 +697,7 @@ describe('custom guards', () => { }); it('should call an inline function guard embedded in referenced `and` with undefined params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -732,7 +732,7 @@ describe('custom guards', () => { }); it('should call a string guard referenced by referenced `and` with undefined params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -764,7 +764,7 @@ describe('custom guards', () => { }); it('should call an object guard referenced by referenced `and` with its own params', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -804,7 +804,7 @@ describe('custom guards', () => { describe('referencing guards', () => { it('guard should be checked when referenced by a string', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { on: { @@ -832,7 +832,7 @@ describe('referencing guards', () => { }); it('guard should be checked when referenced by a parametrized guard object', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { on: { @@ -875,7 +875,7 @@ describe('referencing guards', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine); actorRef.subscribe({ @@ -1130,7 +1130,7 @@ describe('not() guard', () => { }); it('should evaluate dynamic params of the referenced guard', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -1297,7 +1297,7 @@ describe('and() guard', () => { }); it('should evaluate dynamic params of the referenced guard', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -1468,7 +1468,7 @@ describe('or() guard', () => { }); it('should evaluate dynamic params of the referenced guard', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { diff --git a/packages/core/test/history.test.ts b/packages/core/test/history.test.ts index 4a99b205d7..eaec14e43a 100644 --- a/packages/core/test/history.test.ts +++ b/packages/core/test/history.test.ts @@ -270,7 +270,7 @@ describe('history states', () => { }); it('should execute actions of the initial transition when a history state without a default target is targeted and its parent state was never visited yet', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', @@ -301,7 +301,7 @@ describe('history states', () => { }); it('should not execute actions of the initial transition when a history state with a default target is targeted and its parent state was never visited yet', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -333,7 +333,7 @@ describe('history states', () => { }); it('should execute entry actions of a parent of the targeted history state when its parent state was never visited yet', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -363,7 +363,7 @@ describe('history states', () => { }); it('should execute actions of the initial transition when it select a history state as the initial state of its parent', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -394,7 +394,7 @@ describe('history states', () => { }); it('should execute actions of the initial transition when a history state without a default target is targeted and its parent state was already visited', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', @@ -432,7 +432,7 @@ describe('history states', () => { }); it('should not execute actions of the initial transition when a history state with a default target is targeted and its parent state was already visited', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -471,7 +471,7 @@ describe('history states', () => { }); it('should execute entry actions of a parent of the targeted history state when its parent state was already visited', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', states: { @@ -508,7 +508,7 @@ describe('history states', () => { }); it('should invoke an actor when reentering the stored configuration through the history state', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'running', diff --git a/packages/core/test/input.test.ts b/packages/core/test/input.test.ts index d1e658beb9..478e85f5e0 100644 --- a/packages/core/test/input.test.ts +++ b/packages/core/test/input.test.ts @@ -10,7 +10,7 @@ import { describe('input', () => { it('should create a machine with input', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ types: {} as { @@ -179,7 +179,7 @@ describe('input', () => { }); it('should provide a static inline input to the referenced actor', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ context: ({ input }: { input: number }) => { @@ -211,7 +211,7 @@ describe('input', () => { }); it('should provide a dynamic inline input to the referenced actor', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ context: ({ input }: { input: number }) => { @@ -255,7 +255,7 @@ describe('input', () => { }); it('should call the input factory with self when invoking', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ invoke: { @@ -270,7 +270,7 @@ describe('input', () => { }); it('should call the input factory with self when spawning', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { diff --git a/packages/core/test/internalTransitions.test.ts b/packages/core/test/internalTransitions.test.ts index 4c77391172..780e3eade2 100644 --- a/packages/core/test/internalTransitions.test.ts +++ b/packages/core/test/internalTransitions.test.ts @@ -169,7 +169,7 @@ describe('internal transitions', () => { }); it('should work with targetless transitions (in conditional array)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'foo', states: { @@ -188,7 +188,7 @@ describe('internal transitions', () => { }); it('should work with targetless transitions (in object)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'foo', states: { @@ -207,7 +207,7 @@ describe('internal transitions', () => { }); it('should work on parent with targetless transitions (in conditional array)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ on: { TARGETLESS_ARRAY: [{ actions: [spy] }] @@ -223,7 +223,7 @@ describe('internal transitions', () => { }); it('should work on parent with targetless transitions (in object)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ on: { TARGETLESS_OBJECT: { actions: [spy] } diff --git a/packages/core/test/interpreter.test.ts b/packages/core/test/interpreter.test.ts index eacb255e31..7c0646e44a 100644 --- a/packages/core/test/interpreter.test.ts +++ b/packages/core/test/interpreter.test.ts @@ -179,7 +179,7 @@ describe('interpreter', () => { }); it('should not notify subscribers of the current state upon subscription (subscribe)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const service = createActor(machine).start(); service.subscribe(spy); @@ -441,7 +441,7 @@ describe('interpreter', () => { describe('activities (deprecated)', () => { it('should start activities', () => { - const spy = jest.fn(); + const spy = vi.fn(); const activityMachine = createMachine( { @@ -473,7 +473,7 @@ describe('interpreter', () => { }); it('should stop activities', () => { - const spy = jest.fn(); + const spy = vi.fn(); const activityMachine = createMachine( { @@ -509,7 +509,7 @@ describe('interpreter', () => { }); it('should stop activities upon stopping the service', () => { - const spy = jest.fn(); + const spy = vi.fn(); const stopActivityMachine = createMachine( { @@ -998,8 +998,8 @@ describe('interpreter', () => { describe('.start()', () => { it('should initialize the service', () => { - const contextSpy = jest.fn(); - const entrySpy = jest.fn(); + const contextSpy = vi.fn(); + const entrySpy = vi.fn(); const machine = createMachine({ context: contextSpy, @@ -1019,8 +1019,8 @@ describe('interpreter', () => { }); it('should not reinitialize a started service', () => { - const contextSpy = jest.fn(); - const entrySpy = jest.fn(); + const contextSpy = vi.fn(); + const entrySpy = vi.fn(); const machine = createMachine({ context: contextSpy, @@ -1390,7 +1390,7 @@ describe('interpreter', () => { }); it('should call complete() once a final state is reached', () => { - const completeCb = jest.fn(); + const completeCb = vi.fn(); const service = createActor( createMachine({ @@ -1416,7 +1416,7 @@ describe('interpreter', () => { }); it('should call complete() once the interpreter is stopped', () => { - const completeCb = jest.fn(); + const completeCb = vi.fn(); const service = createActor(createMachine({})).start(); @@ -1726,7 +1726,7 @@ describe('interpreter', () => { }); it("shouldn't execute actions when reading a snapshot of not started actor", () => { - const spy = jest.fn(); + const spy = vi.fn(); const actorRef = createActor( createMachine({ entry: () => { @@ -1741,7 +1741,7 @@ describe('interpreter', () => { }); it(`should execute entry actions when starting the actor after reading its snapshot first`, () => { - const spy = jest.fn(); + const spy = vi.fn(); const actorRef = createActor( createMachine({ @@ -1841,7 +1841,7 @@ it('should not process events sent directly to own actor ref before initial entr }); it('should not notify the completion observer for an active logic when it gets subscribed before starting', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({}); createActor(machine).subscribe({ complete: spy }); @@ -1850,7 +1850,7 @@ it('should not notify the completion observer for an active logic when it gets s }); it('should not notify the completion observer for an errored logic when it gets subscribed after it errors', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: () => { @@ -1869,7 +1869,7 @@ it('should not notify the completion observer for an errored logic when it gets }); it('should notify the error observer for an errored logic when it gets subscribed after it errors', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: () => { diff --git a/packages/core/test/invoke.test.ts b/packages/core/test/invoke.test.ts index 344b67cfe2..959dfd32fb 100644 --- a/packages/core/test/invoke.test.ts +++ b/packages/core/test/invoke.test.ts @@ -851,7 +851,7 @@ describe('invoke', () => { }); it('should be invoked with a promise factory and stop on unhandled onError target', (done) => { - const completeSpy = jest.fn(); + const completeSpy = vi.fn(); const promiseMachine = createMachine({ id: 'invokePromise', @@ -1586,7 +1586,7 @@ describe('invoke', () => { }); it('should dispose of the callback (if disposal function provided)', () => { - const spy = jest.fn(); + const spy = vi.fn(); const intervalMachine = createMachine({ id: 'interval', initial: 'counting', @@ -1795,7 +1795,7 @@ describe('invoke', () => { } } }); - const spy = jest.fn(); + const spy = vi.fn(); const actorRef = createActor(errorMachine); actorRef.subscribe({ diff --git a/packages/core/test/machine.test.ts b/packages/core/test/machine.test.ts index 6ed47f6243..09c0b9e265 100644 --- a/packages/core/test/machine.test.ts +++ b/packages/core/test/machine.test.ts @@ -96,8 +96,8 @@ describe('machine', () => { describe('machine.provide', () => { it('should override an action', () => { - const originalEntry = jest.fn(); - const overridenEntry = jest.fn(); + const originalEntry = vi.fn(); + const overridenEntry = vi.fn(); const machine = createMachine( { @@ -122,8 +122,8 @@ describe('machine', () => { }); it('should override a guard', () => { - const originalGuard = jest.fn().mockImplementation(() => true); - const overridenGuard = jest.fn().mockImplementation(() => true); + const originalGuard = vi.fn().mockImplementation(() => true); + const overridenGuard = vi.fn().mockImplementation(() => true); const machine = createMachine( { diff --git a/packages/core/test/parallel.test.ts b/packages/core/test/parallel.test.ts index 8a4695be3d..d2d78c2c80 100644 --- a/packages/core/test/parallel.test.ts +++ b/packages/core/test/parallel.test.ts @@ -756,7 +756,7 @@ describe('parallel states', () => { }); it('should execute actions of the initial transition of a parallel region when entering the initial state nodes of a machine', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', @@ -779,7 +779,7 @@ describe('parallel states', () => { }); it('should execute actions of the initial transition of a parallel region when the parallel state is targeted with an explicit transition', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ initial: 'a', diff --git a/packages/core/test/rehydration.test.ts b/packages/core/test/rehydration.test.ts index 041153cf7e..7bf683e3b6 100644 --- a/packages/core/test/rehydration.test.ts +++ b/packages/core/test/rehydration.test.ts @@ -157,7 +157,7 @@ describe('rehydration', () => { }); it('should not replay actions when starting from a persisted state', () => { - const entrySpy = jest.fn(); + const entrySpy = vi.fn(); const machine = createMachine({ entry: entrySpy }); @@ -267,7 +267,7 @@ describe('rehydration', () => { }); it('a rehydrated done child should not re-notify the parent about its completion', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -346,7 +346,7 @@ describe('rehydration', () => { actorRef.send({ type: 'NEXT' }); const persistedState = actorRef.getPersistedSnapshot(); - const spy = jest.fn(); + const spy = vi.fn(); const actorRef2 = createActor(machine, { snapshot: persistedState }); actorRef2.subscribe({ complete: spy @@ -379,7 +379,7 @@ describe('rehydration', () => { const persistedState = actorRef.getPersistedSnapshot(); - const spy = jest.fn(); + const spy = vi.fn(); const actorRef2 = createActor(machine, { snapshot: persistedState }); actorRef2.subscribe({ error: spy @@ -390,7 +390,7 @@ describe('rehydration', () => { }); it(`shouldn't re-notify the parent about the error when rehydrating`, async () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { @@ -427,7 +427,7 @@ describe('rehydration', () => { const subject = new BehaviorSubject(0); const subjectLogic = fromObservable(() => subject); - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine( { diff --git a/packages/core/test/spawnChild.test.ts b/packages/core/test/spawnChild.test.ts index 8d54c79818..db1558a47f 100644 --- a/packages/core/test/spawnChild.test.ts +++ b/packages/core/test/spawnChild.test.ts @@ -86,7 +86,7 @@ describe('spawnChild action', () => { }); it('should handle a dynamic id', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ on: { diff --git a/packages/core/test/stateIn.test.ts b/packages/core/test/stateIn.test.ts index 01e7fff12f..f8f697728a 100644 --- a/packages/core/test/stateIn.test.ts +++ b/packages/core/test/stateIn.test.ts @@ -466,7 +466,7 @@ describe('transition "in" check', () => { }); it('should be possible to check an ID with a path', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ type: 'parallel', states: { diff --git a/packages/core/test/system.test.ts b/packages/core/test/system.test.ts index dd95672d37..d392c725e1 100644 --- a/packages/core/test/system.test.ts +++ b/packages/core/test/system.test.ts @@ -217,7 +217,7 @@ describe('system', () => { } }); - const errorSpy = jest.fn(); + const errorSpy = vi.fn(); const actorRef = createActor(machine, { systemId: 'test' }); actorRef.subscribe({ @@ -468,7 +468,7 @@ describe('system', () => { }); it('should gracefully handle re-registration of a `systemId` during a reentering transition', () => { - const spy = jest.fn(); + const spy = vi.fn(); let counter = 0; @@ -513,7 +513,7 @@ describe('system', () => { }); it('should be able to send an event to an ancestor with a registered `systemId` from an initial entry action', () => { - const spy = jest.fn(); + const spy = vi.fn(); const child = createMachine({ entry: sendTo(({ system }) => system.get('myRoot'), { diff --git a/packages/core/test/transient.test.ts b/packages/core/test/transient.test.ts index 1e1647b261..a7d1b57108 100644 --- a/packages/core/test/transient.test.ts +++ b/packages/core/test/transient.test.ts @@ -697,7 +697,7 @@ describe('transient states (eventless transitions)', () => { }); it("should execute an always transition after a raised transition even if that raised transition doesn't change the state", () => { - const spy = jest.fn(); + const spy = vi.fn(); let counter = 0; const machine = createMachine({ always: { diff --git a/packages/core/test/waitFor.test.ts b/packages/core/test/waitFor.test.ts index 18535aaf96..578c5063b8 100644 --- a/packages/core/test/waitFor.test.ts +++ b/packages/core/test/waitFor.test.ts @@ -115,7 +115,7 @@ describe('waitFor', () => { const machine = createMachine({}); const actorRef = createActor(machine).start(); - const spy = jest.fn(); + const spy = vi.fn(); actorRef.subscribe = spy; waitFor(actorRef, () => true).then(() => {}); diff --git a/packages/xstate-inspect/test/inspect.test.ts b/packages/xstate-inspect/test/inspect.test.ts index 5bebc7191f..974dcc8f5d 100644 --- a/packages/xstate-inspect/test/inspect.test.ts +++ b/packages/xstate-inspect/test/inspect.test.ts @@ -385,11 +385,11 @@ describe('@xstate/inspect', () => { }); it('browser inspector should use targetWindow if provided', () => { - const windowMock = jest.fn() as unknown as Window; - const windowSpy = jest.spyOn(window, 'open'); + const windowMock = vi.fn() as unknown as Window; + const windowSpy = vi.spyOn(window, 'open'); windowSpy.mockImplementation(() => windowMock); - const localWindowMock = jest.fn() as unknown as Window; + const localWindowMock = vi.fn() as unknown as Window; const devTools = createDevTools(); inspect({ diff --git a/packages/xstate-react/test/createActorContext.test.tsx b/packages/xstate-react/test/createActorContext.test.tsx index cf12ae8eb4..98c52d4eb9 100644 --- a/packages/xstate-react/test/createActorContext.test.tsx +++ b/packages/xstate-react/test/createActorContext.test.tsx @@ -243,7 +243,7 @@ describe('createActorContext', () => { }); it('useActorRef should throw when the actor was not provided', () => { - console.error = jest.fn(); + console.error = vi.fn(); const SomeContext = createActorContext(createMachine({})); const App = () => { @@ -258,7 +258,7 @@ describe('createActorContext', () => { }); it('useSelector should throw when the actor was not provided', () => { - console.error = jest.fn(); + console.error = vi.fn(); const SomeContext = createActorContext(createMachine({})); const App = () => { @@ -281,7 +281,7 @@ describe('createActorContext', () => { } } }); - const stubFn = jest.fn(); + const stubFn = vi.fn(); const SomeContext = createActorContext(someMachine); const Component = () => { @@ -342,7 +342,7 @@ describe('createActorContext', () => { } } }); - const stubFn = jest.fn(); + const stubFn = vi.fn(); const SomeContext = createActorContext(someMachine); const Component = () => { diff --git a/packages/xstate-react/test/useActor.test.tsx b/packages/xstate-react/test/useActor.test.tsx index ea8b20c30b..9993bc35b4 100644 --- a/packages/xstate-react/test/useActor.test.tsx +++ b/packages/xstate-react/test/useActor.test.tsx @@ -20,7 +20,7 @@ import { useActor, useSelector } from '../src/index.ts'; import { describeEachReactMode } from './utils.tsx'; afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { @@ -662,7 +662,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { }); it('should be able to use a delay provided outside of React', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const machine = createMachine( { @@ -708,7 +708,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { expect(screen.getByTestId('result').textContent).toBe('b'); act(() => { - jest.advanceTimersByTime(310); + vi.advanceTimersByTime(310); }); expect(screen.getByTestId('result').textContent).toBe('c'); @@ -880,7 +880,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { // https://github.com/statelyai/xstate/issues/1334 it('delayed transitions should work when initializing from a rehydrated state', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const testMachine = createMachine({ types: {} as { events: { @@ -931,7 +931,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { fireEvent.click(button); act(() => { - jest.advanceTimersByTime(110); + vi.advanceTimersByTime(110); }); expect(currentState!.matches('idle')).toBe(true); @@ -969,7 +969,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { }); it('should deliver messages sent from an effect to an actor registered in the system', () => { - const spy = jest.fn(); + const spy = vi.fn(); const m = createMachine({ invoke: { systemId: 'child', @@ -1001,7 +1001,7 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { it('should work with `onSnapshot`', () => { const subject = new BehaviorSubject(0); - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ invoke: [ diff --git a/packages/xstate-react/test/useActorRef.test.tsx b/packages/xstate-react/test/useActorRef.test.tsx index a802e587b9..e595226e2d 100644 --- a/packages/xstate-react/test/useActorRef.test.tsx +++ b/packages/xstate-react/test/useActorRef.test.tsx @@ -107,7 +107,7 @@ describeEachReactMode('useActorRef (%s)', ({ suiteKey, render }) => { }); it('should rerender OK when only the provided machine implementations have changed', () => { - console.warn = jest.fn(); + console.warn = vi.fn(); const machine = createMachine({ initial: 'foo', context: { id: 1 }, @@ -269,7 +269,7 @@ describeEachReactMode('useActorRef (%s)', ({ suiteKey, render }) => { }); it('should deliver messages sent from an effect to the root actor registered in the system', () => { - const spy = jest.fn(); + const spy = vi.fn(); const m = createMachine({ on: { PING: { @@ -698,7 +698,7 @@ describeEachReactMode('useActorRef (%s)', ({ suiteKey, render }) => { }); it('should be able to rehydrate an inline actor when changing machines', () => { - const spy = jest.fn(); + const spy = vi.fn(); const createSampleMachine = (counter: number) => { const child = createMachine({ @@ -766,8 +766,8 @@ describeEachReactMode('useActorRef (%s)', ({ suiteKey, render }) => { }); it("should execute action bound to a specific machine's instance when the action is provided in render", () => { - const spy1 = jest.fn(); - const spy2 = jest.fn(); + const spy1 = vi.fn(); + const spy2 = vi.fn(); const machine = createMachine({ on: { @@ -816,7 +816,7 @@ describeEachReactMode('useActorRef (%s)', ({ suiteKey, render }) => { }); it('should execute an initial entry action once', () => { - const spy = jest.fn(); + const spy = vi.fn(); const machine = createMachine({ entry: spy diff --git a/packages/xstate-react/test/useSelector.test.tsx b/packages/xstate-react/test/useSelector.test.tsx index 3de48d3a97..fed479f1b7 100644 --- a/packages/xstate-react/test/useSelector.test.tsx +++ b/packages/xstate-react/test/useSelector.test.tsx @@ -615,7 +615,7 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => { return null; } - console.error = jest.fn(); + console.error = vi.fn(); render(); const [snapshot1] = snapshots; @@ -703,7 +703,7 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => { }); it('should not log any spurious errors when used with a not-started actor', () => { - const spy = jest.fn(); + const spy = vi.fn(); console.error = spy; const machine = createMachine({}); diff --git a/packages/xstate-solid/test/fromActorRef.test.tsx b/packages/xstate-solid/test/fromActorRef.test.tsx index 4c4d4c01bd..0ebb9218a9 100644 --- a/packages/xstate-solid/test/fromActorRef.test.tsx +++ b/packages/xstate-solid/test/fromActorRef.test.tsx @@ -1145,7 +1145,7 @@ describe('fromActorRef', () => { }); it(`actor should not reevaluate a scope depending on state.matches when state.value doesn't change`, (done) => { - jest.useFakeTimers(); + vi.useFakeTimers(); interface MachineContext { counter: number; @@ -1203,7 +1203,7 @@ describe('fromActorRef', () => { }; render(() => ); - jest.advanceTimersByTime(110); + vi.advanceTimersByTime(110); }); it('actor should be updated when it changes shallow', () => { diff --git a/packages/xstate-solid/test/useActor.test.tsx b/packages/xstate-solid/test/useActor.test.tsx index 539d31108c..135247c2e2 100644 --- a/packages/xstate-solid/test/useActor.test.tsx +++ b/packages/xstate-solid/test/useActor.test.tsx @@ -28,7 +28,7 @@ function sleep(ms: number) { } afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describe('useActor', () => { @@ -1081,7 +1081,7 @@ describe('useActor', () => { }); it('should be able to use a delay provided outside of SolidJS', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const machine = createMachine( { @@ -1130,7 +1130,7 @@ describe('useActor', () => { expect(screen.getByTestId('result').textContent).toBe('b'); - jest.advanceTimersByTime(310); + vi.advanceTimersByTime(310); expect(screen.getByTestId('result').textContent).toBe('c'); }); @@ -1390,7 +1390,7 @@ describe('useActor', () => { }); it('Service should stop on component cleanup', (done) => { - jest.useFakeTimers(); + vi.useFakeTimers(); const machine = createMachine({ initial: 'a', states: { @@ -1420,7 +1420,7 @@ describe('useActor', () => { }; render(() => ); - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); }); it('.can should trigger on context change', () => { @@ -1633,7 +1633,7 @@ describe('useActor', () => { // https://github.com/davidkpiano/xstate/issues/1334 it('delayed transitions should work when initializing from a rehydrated state', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); try { const testMachine = createMachine({ types: {} as { @@ -1681,11 +1681,11 @@ describe('useActor', () => { const button = screen.getByTestId('button'); fireEvent.click(button); - jest.advanceTimersByTime(110); + vi.advanceTimersByTime(110); expect(container.textContent).toBe('idle'); } finally { - jest.useRealTimers(); + vi.useRealTimers(); } }); From db0ea0182d9844d514db76bfeb11f896eed746cb Mon Sep 17 00:00:00 2001 From: with-heart Date: Wed, 31 Jul 2024 15:54:04 -0400 Subject: [PATCH 03/29] replace done callback with promise resolve in tests --- packages/core/test/actions.test.ts | 679 +-- packages/core/test/actor.test.ts | 1284 +++--- packages/core/test/actorLogic.test.ts | 247 +- packages/core/test/after.test.ts | 220 +- packages/core/test/assert.test.ts | 166 +- packages/core/test/assign.test.ts | 45 +- packages/core/test/errors.test.ts | 882 ++-- packages/core/test/event.test.ts | 97 +- packages/core/test/final.test.ts | 93 +- packages/core/test/input.test.ts | 173 +- packages/core/test/interpreter.test.ts | 1076 ++--- packages/core/test/invoke.test.ts | 3864 +++++++++-------- packages/core/test/parallel.test.ts | 111 +- packages/core/test/predictableExec.test.ts | 316 +- packages/core/test/spawnChild.test.ts | 63 +- packages/core/test/system.test.ts | 168 +- packages/xstate-graph/test/paths.test.ts | 2 +- packages/xstate-immer/test/immer.test.ts | 126 +- packages/xstate-inspect/test/inspect.test.ts | 62 +- packages/xstate-react/test/useActor.test.tsx | 317 +- .../xstate-react/test/useActorRef.test.tsx | 69 +- .../xstate-solid/test/fromActorRef.test.tsx | 429 +- packages/xstate-solid/test/useActor.test.tsx | 444 +- .../xstate-solid/test/useActorRef.test.tsx | 134 +- 24 files changed, 5631 insertions(+), 5436 deletions(-) diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index 1591f13ed9..808c744c99 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -1387,79 +1387,81 @@ describe('entry/exit actions', () => { expect(flushTracked()).toEqual([]); }); - it("shouldn't exit (and reenter) state on targetless delayed transition", (done) => { - const machine = createMachine({ - initial: 'one', - states: { - one: { - after: { - 10: { - actions: () => { - // do smth + it("shouldn't exit (and reenter) state on targetless delayed transition", () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'one', + states: { + one: { + after: { + 10: { + actions: () => { + // do smth + } } } } } - } - }); + }); - const flushTracked = trackEntries(machine); + const flushTracked = trackEntries(machine); - createActor(machine).start(); - flushTracked(); + createActor(machine).start(); + flushTracked(); - setTimeout(() => { - expect(flushTracked()).toEqual([]); - done(); - }, 50); - }); + setTimeout(() => { + expect(flushTracked()).toEqual([]); + resolve(); + }, 50); + })); }); describe('when reaching a final state', () => { // https://github.com/statelyai/xstate/issues/1109 - it('exit actions should be called when invoked machine reaches its final state', (done) => { - let exitCalled = false; - let childExitCalled = false; - const childMachine = createMachine({ - exit: () => { - exitCalled = true; - }, - initial: 'a', - states: { - a: { - type: 'final', - exit: () => { - childExitCalled = true; + it('exit actions should be called when invoked machine reaches its final state', () => + new Promise((resolve) => { + let exitCalled = false; + let childExitCalled = false; + const childMachine = createMachine({ + exit: () => { + exitCalled = true; + }, + initial: 'a', + states: { + a: { + type: 'final', + exit: () => { + childExitCalled = true; + } } } - } - }); + }); - const parentMachine = createMachine({ - initial: 'active', - states: { - active: { - invoke: { - src: childMachine, - onDone: 'finished' + const parentMachine = createMachine({ + initial: 'active', + states: { + active: { + invoke: { + src: childMachine, + onDone: 'finished' + } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } - } - }); + }); - const actor = createActor(parentMachine); - actor.subscribe({ - complete: () => { - expect(exitCalled).toBeTruthy(); - expect(childExitCalled).toBeTruthy(); - done(); - } - }); - actor.start(); - }); + const actor = createActor(parentMachine); + actor.subscribe({ + complete: () => { + expect(exitCalled).toBeTruthy(); + expect(childExitCalled).toBeTruthy(); + resolve(); + } + }); + actor.start(); + })); }); describe('when stopped', () => { @@ -2384,120 +2386,122 @@ describe('action meta', () => { }); describe('forwardTo()', () => { - it('should forward an event to a service', (done) => { - const child = createMachine({ - types: {} as { - events: { - type: 'EVENT'; - value: number; - }; - }, - id: 'child', - initial: 'active', - states: { - active: { - on: { - EVENT: { - actions: sendParent({ type: 'SUCCESS' }), - guard: ({ event }) => event.value === 42 + it('should forward an event to a service', () => + new Promise((resolve) => { + const child = createMachine({ + types: {} as { + events: { + type: 'EVENT'; + value: number; + }; + }, + id: 'child', + initial: 'active', + states: { + active: { + on: { + EVENT: { + actions: sendParent({ type: 'SUCCESS' }), + guard: ({ event }) => event.value === 42 + } } } } - } - }); + }); - const parent = createMachine({ - types: {} as { - events: - | { - type: 'EVENT'; - value: number; - } - | { - type: 'SUCCESS'; - }; - }, - id: 'parent', - initial: 'first', - states: { - first: { - invoke: { src: child, id: 'myChild' }, - on: { - EVENT: { - actions: forwardTo('myChild') - }, - SUCCESS: 'last' - } + const parent = createMachine({ + types: {} as { + events: + | { + type: 'EVENT'; + value: number; + } + | { + type: 'SUCCESS'; + }; }, - last: { - type: 'final' + id: 'parent', + initial: 'first', + states: { + first: { + invoke: { src: child, id: 'myChild' }, + on: { + EVENT: { + actions: forwardTo('myChild') + }, + SUCCESS: 'last' + } + }, + last: { + type: 'final' + } } - } - }); + }); - const service = createActor(parent); - service.subscribe({ complete: () => done() }); - service.start(); + const service = createActor(parent); + service.subscribe({ complete: () => resolve() }); + service.start(); - service.send({ type: 'EVENT', value: 42 }); - }); + service.send({ type: 'EVENT', value: 42 }); + })); - it('should forward an event to a service (dynamic)', (done) => { - const child = createMachine({ - types: {} as { - events: { - type: 'EVENT'; - value: number; - }; - }, - id: 'child', - initial: 'active', - states: { - active: { - on: { - EVENT: { - actions: sendParent({ type: 'SUCCESS' }), - guard: ({ event }) => event.value === 42 + it('should forward an event to a service (dynamic)', () => + new Promise((resolve) => { + const child = createMachine({ + types: {} as { + events: { + type: 'EVENT'; + value: number; + }; + }, + id: 'child', + initial: 'active', + states: { + active: { + on: { + EVENT: { + actions: sendParent({ type: 'SUCCESS' }), + guard: ({ event }) => event.value === 42 + } } } } - } - }); + }); - const parent = createMachine({ - types: {} as { - context: { child?: AnyActorRef }; - events: { type: 'EVENT'; value: number } | { type: 'SUCCESS' }; - }, - id: 'parent', - initial: 'first', - context: { - child: undefined - }, - states: { - first: { - entry: assign({ - child: ({ spawn }) => spawn(child, { id: 'x' }) - }), - on: { - EVENT: { - actions: forwardTo(({ context }) => context.child!) - }, - SUCCESS: 'last' - } + const parent = createMachine({ + types: {} as { + context: { child?: AnyActorRef }; + events: { type: 'EVENT'; value: number } | { type: 'SUCCESS' }; }, - last: { - type: 'final' + id: 'parent', + initial: 'first', + context: { + child: undefined + }, + states: { + first: { + entry: assign({ + child: ({ spawn }) => spawn(child, { id: 'x' }) + }), + on: { + EVENT: { + actions: forwardTo(({ context }) => context.child!) + }, + SUCCESS: 'last' + } + }, + last: { + type: 'final' + } } - } - }); + }); - const service = createActor(parent); - service.subscribe({ complete: () => done() }); - service.start(); + const service = createActor(parent); + service.subscribe({ complete: () => resolve() }); + service.start(); - service.send({ type: 'EVENT', value: 42 }); - }); + service.send({ type: 'EVENT', value: 42 }); + })); it('should not cause an infinite loop when forwarding to undefined', () => { const machine = createMachine({ @@ -2956,76 +2960,78 @@ describe('sendParent', () => { }); describe('sendTo', () => { - it('should be able to send an event to an actor', (done) => { - const childMachine = createMachine({ - types: {} as { - events: { type: 'EVENT' }; - }, - initial: 'waiting', - states: { - waiting: { - on: { - EVENT: { - actions: () => done() + it('should be able to send an event to an actor', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => resolve() + } } } } - } - }); + }); - const parentMachine = createMachine({ - types: {} as { - context: { - child: ActorRefFrom; - }; - }, - context: ({ spawn }) => ({ - child: spawn(childMachine) - }), - entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) - }); + const parentMachine = createMachine({ + types: {} as { + context: { + child: ActorRefFrom; + }; + }, + context: ({ spawn }) => ({ + child: spawn(childMachine) + }), + entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + }); - createActor(parentMachine).start(); - }); + createActor(parentMachine).start(); + })); - it('should be able to send an event from expression to an actor', (done) => { - const childMachine = createMachine({ - types: {} as { - events: { type: 'EVENT'; count: number }; - }, - initial: 'waiting', - states: { - waiting: { - on: { - EVENT: { - actions: () => done() + it('should be able to send an event from expression to an actor', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT'; count: number }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => resolve() + } } } } - } - }); + }); - const parentMachine = createMachine({ - types: {} as { - context: { - child: ActorRefFrom; - count: number; - }; - }, - context: ({ spawn }) => { - return { - child: spawn(childMachine, { id: 'child' }), - count: 42 - }; - }, - entry: sendTo( - ({ context }) => context.child, - ({ context }) => ({ type: 'EVENT', count: context.count }) - ) - }); + const parentMachine = createMachine({ + types: {} as { + context: { + child: ActorRefFrom; + count: number; + }; + }, + context: ({ spawn }) => { + return { + child: spawn(childMachine, { id: 'child' }), + count: 42 + }; + }, + entry: sendTo( + ({ context }) => context.child, + ({ context }) => ({ type: 'EVENT', count: context.count }) + ) + }); - createActor(parentMachine).start(); - }); + createActor(parentMachine).start(); + })); it('should report a type error for an invalid event', () => { const childMachine = createMachine({ @@ -3058,62 +3064,64 @@ describe('sendTo', () => { }); }); - it('should be able to send an event to a named actor', (done) => { - const childMachine = createMachine({ - types: {} as { - events: { type: 'EVENT' }; - }, - initial: 'waiting', - states: { - waiting: { - on: { - EVENT: { - actions: () => done() + it('should be able to send an event to a named actor', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => resolve() + } } } } - } - }); + }); - const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, - context: ({ spawn }) => ({ - child: spawn(childMachine, { id: 'child' }) - }), - // No type-safety for the event yet - entry: sendTo('child', { type: 'EVENT' }) - }); + const parentMachine = createMachine({ + types: {} as { context: { child: ActorRefFrom } }, + context: ({ spawn }) => ({ + child: spawn(childMachine, { id: 'child' }) + }), + // No type-safety for the event yet + entry: sendTo('child', { type: 'EVENT' }) + }); - createActor(parentMachine).start(); - }); + createActor(parentMachine).start(); + })); - it('should be able to send an event directly to an ActorRef', (done) => { - const childMachine = createMachine({ - types: {} as { - events: { type: 'EVENT' }; - }, - initial: 'waiting', - states: { - waiting: { - on: { - EVENT: { - actions: () => done() + it('should be able to send an event directly to an ActorRef', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => resolve() + } } } } - } - }); + }); - const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, - context: ({ spawn }) => ({ - child: spawn(childMachine) - }), - entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) - }); + const parentMachine = createMachine({ + types: {} as { context: { child: ActorRefFrom } }, + context: ({ spawn }) => ({ + child: spawn(childMachine) + }), + entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + }); - createActor(parentMachine).start(); - }); + createActor(parentMachine).start(); + })); it('should be able to read from event', () => { expect.assertions(1); @@ -3178,70 +3186,72 @@ describe('sendTo', () => { }); describe('raise', () => { - it('should be able to send a delayed event to itself', (done) => { - const machine = createMachine({ - initial: 'a', - states: { - a: { - entry: raise( - { type: 'EVENT' }, - { - delay: 1 + it('should be able to send a delayed event to itself', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + entry: raise( + { type: 'EVENT' }, + { + delay: 1 + } + ), + on: { + TO_B: 'b' } - ), - on: { - TO_B: 'b' - } - }, - b: { - on: { - EVENT: 'c' + }, + b: { + on: { + EVENT: 'c' + } + }, + c: { + type: 'final' } - }, - c: { - type: 'final' } - } - }); + }); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - service.subscribe({ complete: () => done() }); + service.subscribe({ complete: () => resolve() }); - // Ensures that the delayed self-event is sent when in the `b` state - service.send({ type: 'TO_B' }); - }); + // Ensures that the delayed self-event is sent when in the `b` state + service.send({ type: 'TO_B' }); + })); - it('should be able to send a delayed event to itself with delay = 0', (done) => { - const machine = createMachine({ - initial: 'a', - states: { - a: { - entry: raise( - { type: 'EVENT' }, - { - delay: 0 + it('should be able to send a delayed event to itself with delay = 0', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + entry: raise( + { type: 'EVENT' }, + { + delay: 0 + } + ), + on: { + EVENT: 'b' } - ), - on: { - EVENT: 'b' - } - }, - b: {} - } - }); + }, + b: {} + } + }); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - // The state should not be changed yet; `delay: 0` is equivalent to `setTimeout(..., 0)` - expect(service.getSnapshot().value).toEqual('a'); + // The state should not be changed yet; `delay: 0` is equivalent to `setTimeout(..., 0)` + expect(service.getSnapshot().value).toEqual('a'); - setTimeout(() => { - // The state should be changed now - expect(service.getSnapshot().value).toEqual('b'); - done(); - }); - }); + setTimeout(() => { + // The state should be changed now + expect(service.getSnapshot().value).toEqual('b'); + resolve(); + }); + })); it('should be able to raise an event and respond to it in the same state', () => { const machine = createMachine({ @@ -3264,36 +3274,37 @@ describe('raise', () => { expect(service.getSnapshot().value).toEqual('b'); }); - it('should be able to raise a delayed event and respond to it in the same state', (done) => { - const machine = createMachine({ - initial: 'a', - states: { - a: { - entry: raise( - { type: 'TO_B' }, - { - delay: 100 + it('should be able to raise a delayed event and respond to it in the same state', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + entry: raise( + { type: 'TO_B' }, + { + delay: 100 + } + ), + on: { + TO_B: 'b' } - ), - on: { - TO_B: 'b' + }, + b: { + type: 'final' } - }, - b: { - type: 'final' } - } - }); + }); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - service.subscribe({ complete: () => done() }); + service.subscribe({ complete: () => resolve() }); - setTimeout(() => { - // didn't transition yet - expect(service.getSnapshot().value).toEqual('a'); - }, 50); - }); + setTimeout(() => { + // didn't transition yet + expect(service.getSnapshot().value).toEqual('a'); + }, 50); + })); it('should accept event expression', () => { const machine = createMachine({ diff --git a/packages/core/test/actor.test.ts b/packages/core/test/actor.test.ts index 31c343f475..514e0115fa 100644 --- a/packages/core/test/actor.test.ts +++ b/packages/core/test/actor.test.ts @@ -120,68 +120,69 @@ describe('spawning machines', () => { } }); - it('should spawn machines', (done) => { - const todoMachine = createMachine({ - id: 'todo', - initial: 'incomplete', - states: { - incomplete: { - on: { SET_COMPLETE: 'complete' } - }, - complete: { - entry: sendParent({ type: 'TODO_COMPLETED' }) + it('should spawn machines', () => + new Promise((resolve) => { + const todoMachine = createMachine({ + id: 'todo', + initial: 'incomplete', + states: { + incomplete: { + on: { SET_COMPLETE: 'complete' } + }, + complete: { + entry: sendParent({ type: 'TODO_COMPLETED' }) + } } - } - }); + }); - const todosMachine = createMachine({ - types: {} as { - context: typeof context; - events: TodoEvent; - }, - id: 'todos', - context, - initial: 'active', - states: { - active: { - on: { - TODO_COMPLETED: 'success' + const todosMachine = createMachine({ + types: {} as { + context: typeof context; + events: TodoEvent; + }, + id: 'todos', + context, + initial: 'active', + states: { + active: { + on: { + TODO_COMPLETED: 'success' + } + }, + success: { + type: 'final' } }, - success: { - type: 'final' - } - }, - on: { - ADD: { - actions: assign({ - todoRefs: ({ context, event, spawn }) => ({ - ...context.todoRefs, - [event.id]: spawn(todoMachine) + on: { + ADD: { + actions: assign({ + todoRefs: ({ context, event, spawn }) => ({ + ...context.todoRefs, + [event.id]: spawn(todoMachine) + }) }) - }) - }, - SET_COMPLETE: { - actions: sendTo( - ({ context, event }) => { - return context.todoRefs[event.id]; - }, - { type: 'SET_COMPLETE' } - ) + }, + SET_COMPLETE: { + actions: sendTo( + ({ context, event }) => { + return context.todoRefs[event.id]; + }, + { type: 'SET_COMPLETE' } + ) + } } - } - }); - const service = createActor(todosMachine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); + }); + const service = createActor(todosMachine); + service.subscribe({ + complete: () => { + resolve(); + } + }); + service.start(); - service.send({ type: 'ADD', id: 42 }); - service.send({ type: 'SET_COMPLETE', id: 42 }); - }); + service.send({ type: 'ADD', id: 42 }); + service.send({ type: 'SET_COMPLETE', id: 42 }); + })); it('should spawn referenced machines', () => { const childMachine = createMachine({ @@ -220,168 +221,172 @@ describe('spawning machines', () => { expect(actor.getSnapshot().value).toBe('success'); }); - it('should allow bidirectional communication between parent/child actors', (done) => { - const actor = createActor(clientMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + it('should allow bidirectional communication between parent/child actors', () => + new Promise((resolve) => { + const actor = createActor(clientMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); }); const aaa = 'dadasda'; describe('spawning promises', () => { - it('should be able to spawn a promise', (done) => { - const promiseMachine = createMachine({ - types: {} as { - context: { promiseRef?: PromiseActorRef }; - }, - id: 'promise', - initial: 'idle', - context: { - promiseRef: undefined - }, - states: { - idle: { - entry: assign({ - promiseRef: ({ spawn }) => { - const ref = spawn( - fromPromise( - () => - new Promise((res) => { - res('response'); - }) - ), - { id: 'my-promise' } - ); + it('should be able to spawn a promise', () => + new Promise((resolve) => { + const promiseMachine = createMachine({ + types: {} as { + context: { promiseRef?: PromiseActorRef }; + }, + id: 'promise', + initial: 'idle', + context: { + promiseRef: undefined + }, + states: { + idle: { + entry: assign({ + promiseRef: ({ spawn }) => { + const ref = spawn( + fromPromise( + () => + new Promise((res) => { + res('response'); + }) + ), + { id: 'my-promise' } + ); - return ref; - } - }), - on: { - 'xstate.done.actor.my-promise': { - target: 'success', - guard: ({ event }) => event.output === 'response' + return ref; + } + }), + on: { + 'xstate.done.actor.my-promise': { + target: 'success', + guard: ({ event }) => event.output === 'response' + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const promiseService = createActor(promiseMachine); - promiseService.subscribe({ - complete: () => { - done(); - } - }); + const promiseService = createActor(promiseMachine); + promiseService.subscribe({ + complete: () => { + resolve(); + } + }); - promiseService.start(); - }); + promiseService.start(); + })); - it('should be able to spawn a referenced promise', (done) => { - const promiseMachine = setup({ - actors: { - somePromise: fromPromise(() => Promise.resolve('response')) - } - }).createMachine({ - types: {} as { - context: { promiseRef?: PromiseActorRef }; - }, - id: 'promise', - initial: 'idle', - context: { - promiseRef: undefined - }, - states: { - idle: { - entry: assign({ - promiseRef: ({ spawn }) => - spawn('somePromise', { id: 'my-promise' }) - }), - on: { - 'xstate.done.actor.my-promise': { - target: 'success', - guard: ({ event }) => event.output === 'response' + it('should be able to spawn a referenced promise', () => + new Promise((resolve) => { + const promiseMachine = setup({ + actors: { + somePromise: fromPromise(() => Promise.resolve('response')) + } + }).createMachine({ + types: {} as { + context: { promiseRef?: PromiseActorRef }; + }, + id: 'promise', + initial: 'idle', + context: { + promiseRef: undefined + }, + states: { + idle: { + entry: assign({ + promiseRef: ({ spawn }) => + spawn('somePromise', { id: 'my-promise' }) + }), + on: { + 'xstate.done.actor.my-promise': { + target: 'success', + guard: ({ event }) => event.output === 'response' + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const promiseService = createActor(promiseMachine); - promiseService.subscribe({ - complete: () => { - done(); - } - }); + const promiseService = createActor(promiseMachine); + promiseService.subscribe({ + complete: () => { + resolve(); + } + }); - promiseService.start(); - }); + promiseService.start(); + })); }); describe('spawning callbacks', () => { - it('should be able to spawn an actor from a callback', (done) => { - const callbackMachine = createMachine({ - types: {} as { + it('should be able to spawn an actor from a callback', () => + new Promise((resolve) => { + const callbackMachine = createMachine({ + types: {} as { + context: { + callbackRef?: CallbackActorRef<{ type: 'START' }>; + }; + }, + id: 'callback', + initial: 'idle', context: { - callbackRef?: CallbackActorRef<{ type: 'START' }>; - }; - }, - id: 'callback', - initial: 'idle', - context: { - callbackRef: undefined - }, - states: { - idle: { - entry: assign({ - callbackRef: ({ spawn }) => - spawn( - fromCallback<{ type: 'START' }>(({ sendBack, receive }) => { - receive((event) => { - if (event.type === 'START') { - setTimeout(() => { - sendBack({ type: 'SEND_BACK' }); - }, 10); - } - }); + callbackRef: undefined + }, + states: { + idle: { + entry: assign({ + callbackRef: ({ spawn }) => + spawn( + fromCallback<{ type: 'START' }>(({ sendBack, receive }) => { + receive((event) => { + if (event.type === 'START') { + setTimeout(() => { + sendBack({ type: 'SEND_BACK' }); + }, 10); + } + }); + }) + ) + }), + on: { + START_CB: { + actions: sendTo(({ context }) => context.callbackRef!, { + type: 'START' }) - ) - }), - on: { - START_CB: { - actions: sendTo(({ context }) => context.callbackRef!, { - type: 'START' - }) - }, - SEND_BACK: 'success' + }, + SEND_BACK: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const callbackService = createActor(callbackMachine); - callbackService.subscribe({ - complete: () => { - done(); - } - }); + const callbackService = createActor(callbackMachine); + callbackService.subscribe({ + complete: () => { + resolve(); + } + }); - callbackService.start(); - callbackService.send({ type: 'START_CB' }); - }); + callbackService.start(); + callbackService.send({ type: 'START_CB' }); + })); it('should not deliver events sent to the parent after the callback actor gets stopped', () => { const spy = vi.fn(); @@ -423,62 +428,26 @@ describe('spawning callbacks', () => { }); describe('spawning observables', () => { - it('should spawn an observable', (done) => { - const observableLogic = fromObservable(() => interval(10)); - const observableMachine = createMachine({ - id: 'observable', - initial: 'idle', - context: { - observableRef: undefined! as ActorRefFrom - }, - states: { - idle: { - entry: assign({ - observableRef: ({ spawn }) => { - const ref = spawn(observableLogic, { - id: 'int', - syncSnapshot: true - }); - - return ref; - } - }), - on: { - 'xstate.snapshot.int': { - target: 'success', - guard: ({ event }) => event.snapshot.context === 5 - } - } - }, - success: { - type: 'final' - } - } - }); - - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } - }); - - observableService.start(); - }); - - it('should spawn a referenced observable', (done) => { - const observableMachine = createMachine( - { + it('should spawn an observable', () => + new Promise((resolve) => { + const observableLogic = fromObservable(() => interval(10)); + const observableMachine = createMachine({ id: 'observable', initial: 'idle', context: { - observableRef: undefined! as AnyActorRef + observableRef: undefined! as ActorRefFrom }, states: { idle: { entry: assign({ - observableRef: ({ spawn }) => - spawn('interval', { id: 'int', syncSnapshot: true }) + observableRef: ({ spawn }) => { + const ref = spawn(observableLogic, { + id: 'int', + syncSnapshot: true + }); + + return ref; + } }), on: { 'xstate.snapshot.int': { @@ -491,71 +460,110 @@ describe('spawning observables', () => { type: 'final' } } - }, - { - actors: { - interval: fromObservable(() => interval(10)) + }); + + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); } - } - ); + }); - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } - }); + observableService.start(); + })); - observableService.start(); - }); + it('should spawn a referenced observable', () => + new Promise((resolve) => { + const observableMachine = createMachine( + { + id: 'observable', + initial: 'idle', + context: { + observableRef: undefined! as AnyActorRef + }, + states: { + idle: { + entry: assign({ + observableRef: ({ spawn }) => + spawn('interval', { id: 'int', syncSnapshot: true }) + }), + on: { + 'xstate.snapshot.int': { + target: 'success', + guard: ({ event }) => event.snapshot.context === 5 + } + } + }, + success: { + type: 'final' + } + } + }, + { + actors: { + interval: fromObservable(() => interval(10)) + } + } + ); - it(`should read the latest snapshot of the event's origin while handling that event`, (done) => { - const observableLogic = fromObservable(() => interval(10)); - const observableMachine = createMachine({ - id: 'observable', - initial: 'idle', - context: { - observableRef: undefined! as ActorRefFrom - }, - states: { - idle: { - entry: assign({ - observableRef: ({ spawn }) => { - const ref = spawn(observableLogic, { - id: 'int', - syncSnapshot: true - }); + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); + } + }); - return ref; - } - }), - on: { - 'xstate.snapshot.int': { - target: 'success', - guard: ({ context, event }) => { - return ( - event.snapshot.context === 1 && - context.observableRef.getSnapshot().context === 1 - ); + observableService.start(); + })); + + it(`should read the latest snapshot of the event's origin while handling that event`, () => + new Promise((resolve) => { + const observableLogic = fromObservable(() => interval(10)); + const observableMachine = createMachine({ + id: 'observable', + initial: 'idle', + context: { + observableRef: undefined! as ActorRefFrom + }, + states: { + idle: { + entry: assign({ + observableRef: ({ spawn }) => { + const ref = spawn(observableLogic, { + id: 'int', + syncSnapshot: true + }); + + return ref; + } + }), + on: { + 'xstate.snapshot.int': { + target: 'success', + guard: ({ context, event }) => { + return ( + event.snapshot.context === 1 && + context.observableRef.getSnapshot().context === 1 + ); + } } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } - }); + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); + } + }); - observableService.start(); - }); + observableService.start(); + })); it('should notify direct child listeners with final snapshot before it gets stopped', async () => { const intervalActor = fromObservable(() => interval(10)); @@ -652,79 +660,44 @@ describe('spawning observables', () => { const actorRef = createActor(parentMachine); actorRef.start(); - await waitFor(actorRef, (state) => state.matches('active')); - - const spy = vi.fn(); - - actorRef.getSnapshot().children.childActor!.subscribe((data) => { - spy(data); - }); - - await waitFor(actorRef, (state) => state.status !== 'active'); - spy.mockClear(); - - // wait for potential next event from the interval actor - await sleep(15); - - expect(spy).not.toHaveBeenCalled(); - }); -}); - -describe('spawning event observables', () => { - it('should spawn an event observable', (done) => { - const eventObservableLogic = fromEventObservable(() => - interval(10).pipe(map((val) => ({ type: 'COUNT', val }))) - ); - const observableMachine = createMachine({ - id: 'observable', - initial: 'idle', - context: { - observableRef: undefined! as ActorRefFrom - }, - states: { - idle: { - entry: assign({ - observableRef: ({ spawn }) => { - const ref = spawn(eventObservableLogic, { id: 'int' }); - - return ref; - } - }), - on: { - COUNT: { - target: 'success', - guard: ({ event }) => event.val === 5 - } - } - }, - success: { - type: 'final' - } - } - }); - - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } + await waitFor(actorRef, (state) => state.matches('active')); + + const spy = vi.fn(); + + actorRef.getSnapshot().children.childActor!.subscribe((data) => { + spy(data); }); - observableService.start(); + await waitFor(actorRef, (state) => state.status !== 'active'); + spy.mockClear(); + + // wait for potential next event from the interval actor + await sleep(15); + + expect(spy).not.toHaveBeenCalled(); }); +}); - it('should spawn a referenced event observable', (done) => { - const observableMachine = createMachine( - { +describe('spawning event observables', () => { + it('should spawn an event observable', () => + new Promise((resolve) => { + const eventObservableLogic = fromEventObservable(() => + interval(10).pipe(map((val) => ({ type: 'COUNT', val }))) + ); + const observableMachine = createMachine({ id: 'observable', initial: 'idle', context: { - observableRef: undefined! as AnyActorRef + observableRef: undefined! as ActorRefFrom }, states: { idle: { entry: assign({ - observableRef: ({ spawn }) => spawn('interval', { id: 'int' }) + observableRef: ({ spawn }) => { + const ref = spawn(eventObservableLogic, { id: 'int' }); + + return ref; + } }), on: { COUNT: { @@ -737,93 +710,133 @@ describe('spawning event observables', () => { type: 'final' } } - }, - { - actors: { - interval: fromEventObservable(() => - interval(10).pipe(map((val) => ({ type: 'COUNT', val }))) - ) + }); + + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); } - } - ); + }); - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } - }); + observableService.start(); + })); - observableService.start(); - }); + it('should spawn a referenced event observable', () => + new Promise((resolve) => { + const observableMachine = createMachine( + { + id: 'observable', + initial: 'idle', + context: { + observableRef: undefined! as AnyActorRef + }, + states: { + idle: { + entry: assign({ + observableRef: ({ spawn }) => spawn('interval', { id: 'int' }) + }), + on: { + COUNT: { + target: 'success', + guard: ({ event }) => event.val === 5 + } + } + }, + success: { + type: 'final' + } + } + }, + { + actors: { + interval: fromEventObservable(() => + interval(10).pipe(map((val) => ({ type: 'COUNT', val }))) + ) + } + } + ); + + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); + } + }); + + observableService.start(); + })); }); describe('communicating with spawned actors', () => { - it('should treat an interpreter as an actor', (done) => { - const existingMachine = createMachine({ - types: { - events: {} as { - type: 'ACTIVATE'; - origin: AnyActorRef; - } - }, - initial: 'inactive', - states: { - inactive: { - on: { ACTIVATE: 'active' } + it('should treat an interpreter as an actor', () => + new Promise((resolve) => { + const existingMachine = createMachine({ + types: { + events: {} as { + type: 'ACTIVATE'; + origin: AnyActorRef; + } }, - active: { - entry: sendTo(({ event }) => event.origin, { type: 'EXISTING.DONE' }) + initial: 'inactive', + states: { + inactive: { + on: { ACTIVATE: 'active' } + }, + active: { + entry: sendTo(({ event }) => event.origin, { + type: 'EXISTING.DONE' + }) + } } - } - }); + }); - const existingService = createActor(existingMachine).start(); + const existingService = createActor(existingMachine).start(); - const parentMachine = createMachine({ - types: {} as { - context: { existingRef?: typeof existingService }; - }, - initial: 'pending', - context: { - existingRef: undefined - }, - states: { - pending: { - entry: assign({ - // No need to spawn an existing service: - existingRef: existingService - }), - on: { - 'EXISTING.DONE': 'success' - }, - after: { - 100: { - actions: sendTo( - ({ context }) => context.existingRef!, - ({ self }) => ({ - type: 'ACTIVATE', - origin: self - }) - ) + const parentMachine = createMachine({ + types: {} as { + context: { existingRef?: typeof existingService }; + }, + initial: 'pending', + context: { + existingRef: undefined + }, + states: { + pending: { + entry: assign({ + // No need to spawn an existing service: + existingRef: existingService + }), + on: { + 'EXISTING.DONE': 'success' + }, + after: { + 100: { + actions: sendTo( + ({ context }) => context.existingRef!, + ({ self }) => ({ + type: 'ACTIVATE', + origin: self + }) + ) + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const parentService = createActor(parentMachine); - parentService.subscribe({ - complete: () => { - done(); - } - }); + const parentService = createActor(parentMachine); + parentService.subscribe({ + complete: () => { + resolve(); + } + }); - parentService.start(); - }); + parentService.start(); + })); }); describe('actors', () => { @@ -1012,124 +1025,214 @@ describe('actors', () => { }); describe('with actor logic', () => { - it('should work with a transition function logic', (done) => { - const countLogic = fromTransition((count: number, event: any) => { - if (event.type === 'INC') { - return count + 1; - } else if (event.type === 'DEC') { - return count - 1; - } - return count; - }, 0); - - const countMachine = createMachine({ - types: {} as { - context: { count: ActorRefFrom | undefined }; - }, - context: { - count: undefined - }, - entry: assign({ - count: ({ spawn }) => spawn(countLogic) - }), - on: { - INC: { - actions: forwardTo(({ context }) => context.count!) + it('should work with a transition function logic', () => + new Promise((resolve) => { + const countLogic = fromTransition((count: number, event: any) => { + if (event.type === 'INC') { + return count + 1; + } else if (event.type === 'DEC') { + return count - 1; } - } - }); + return count; + }, 0); - const countService = createActor(countMachine); - countService.subscribe((state) => { - if (state.context.count?.getSnapshot().context === 2) { - done(); - } - }); - countService.start(); - - countService.send({ type: 'INC' }); - countService.send({ type: 'INC' }); - - expect( - countService.getSnapshot().context.count?.getSnapshot().context - ).toBe(2); - }); + const countMachine = createMachine({ + types: {} as { + context: { count: ActorRefFrom | undefined }; + }, + context: { + count: undefined + }, + entry: assign({ + count: ({ spawn }) => spawn(countLogic) + }), + on: { + INC: { + actions: forwardTo(({ context }) => context.count!) + } + } + }); - it('should work with a promise logic (fulfill)', (done) => { - const countMachine = createMachine({ - types: {} as { + const countService = createActor(countMachine); + countService.subscribe((state) => { + if (state.context.count?.getSnapshot().context === 2) { + resolve(); + } + }); + countService.start(); + + countService.send({ type: 'INC' }); + countService.send({ type: 'INC' }); + + expect( + countService.getSnapshot().context.count?.getSnapshot().context + ).toBe(2); + })); + + it('should work with a promise logic (fulfill)', () => + new Promise((resolve) => { + const countMachine = createMachine({ + types: {} as { + context: { + count: ActorRefFrom> | undefined; + }; + }, context: { - count: ActorRefFrom> | undefined; - }; - }, - context: { - count: undefined - }, - entry: assign({ - count: ({ spawn }) => - spawn( + count: undefined + }, + entry: assign({ + count: ({ spawn }) => + spawn( + fromPromise( + () => + new Promise((res) => { + setTimeout(() => res(42)); + }) + ), + { id: 'test' } + ) + }), + initial: 'pending', + states: { + pending: { + on: { + 'xstate.done.actor.test': { + target: 'success', + guard: ({ event }) => event.output === 42 + } + } + }, + success: { + type: 'final' + } + } + }); + + const countService = createActor(countMachine); + countService.subscribe({ + complete: () => { + resolve(); + } + }); + countService.start(); + })); + + it('should work with a promise logic (reject)', () => + new Promise((resolve) => { + const errorMessage = 'An error occurred'; + const countMachine = createMachine({ + types: {} as { + context: { count: ActorRefFrom> }; + }, + context: ({ spawn }) => ({ + count: spawn( fromPromise( () => - new Promise((res) => { - setTimeout(() => res(42)); + new Promise((_, rej) => { + setTimeout(() => rej(errorMessage), 1); }) ), { id: 'test' } ) - }), - initial: 'pending', - states: { - pending: { - on: { - 'xstate.done.actor.test': { - target: 'success', - guard: ({ event }) => event.output === 42 + }), + initial: 'pending', + states: { + pending: { + on: { + 'xstate.error.actor.test': { + target: 'success', + guard: ({ event }) => { + return event.error === errorMessage; + } + } } + }, + success: { + type: 'final' } + } + }); + + const countService = createActor(countMachine); + countService.subscribe({ + complete: () => { + resolve(); + } + }); + countService.start(); + })); + + it('actor logic should have reference to the parent', () => + new Promise((resolve) => { + const pongLogic: ActorLogic, EventObject> = { + transition: (state, event, { self }) => { + if (event.type === 'PING') { + self._parent?.send({ type: 'PONG' }); + } + + return state; }, - success: { - type: 'final' + getInitialSnapshot: () => ({ + status: 'active', + output: undefined, + error: undefined + }), + getPersistedSnapshot: (s) => s + }; + + const pingMachine = createMachine({ + types: {} as { + context: { ponger: ActorRefFrom | undefined }; + }, + initial: 'waiting', + context: { + ponger: undefined + }, + entry: assign({ + ponger: ({ spawn }) => spawn(pongLogic) + }), + states: { + waiting: { + entry: sendTo(({ context }) => context.ponger!, { type: 'PING' }), + invoke: { + id: 'ponger', + src: pongLogic + }, + on: { + PONG: 'success' + } + }, + success: { + type: 'final' + } } - } - }); + }); - const countService = createActor(countMachine); - countService.subscribe({ - complete: () => { - done(); - } - }); - countService.start(); - }); + const pingService = createActor(pingMachine); + pingService.subscribe({ + complete: () => { + resolve(); + } + }); + pingService.start(); + })); + }); - it('should work with a promise logic (reject)', (done) => { - const errorMessage = 'An error occurred'; - const countMachine = createMachine({ - types: {} as { - context: { count: ActorRefFrom> }; - }, + it('should be able to spawn callback actors in (lazy) initial context', () => + new Promise((resolve) => { + const machine = createMachine({ + types: {} as { context: { ref: CallbackActorRef } }, context: ({ spawn }) => ({ - count: spawn( - fromPromise( - () => - new Promise((_, rej) => { - setTimeout(() => rej(errorMessage), 1); - }) - ), - { id: 'test' } + ref: spawn( + fromCallback(({ sendBack }) => { + sendBack({ type: 'TEST' }); + }) ) }), - initial: 'pending', + initial: 'waiting', states: { - pending: { - on: { - 'xstate.error.actor.test': { - target: 'success', - guard: ({ event }) => { - return event.error === errorMessage; - } - } - } + waiting: { + on: { TEST: 'success' } }, success: { type: 'final' @@ -1137,53 +1240,30 @@ describe('actors', () => { } }); - const countService = createActor(countMachine); - countService.subscribe({ + const actor = createActor(machine); + actor.subscribe({ complete: () => { - done(); + resolve(); } }); - countService.start(); - }); + actor.start(); + })); - it('actor logic should have reference to the parent', (done) => { - const pongLogic: ActorLogic, EventObject> = { - transition: (state, event, { self }) => { - if (event.type === 'PING') { - self._parent?.send({ type: 'PONG' }); - } + it('should be able to spawn machines in (lazy) initial context', () => + new Promise((resolve) => { + const childMachine = createMachine({ + entry: sendParent({ type: 'TEST' }) + }); - return state; - }, - getInitialSnapshot: () => ({ - status: 'active', - output: undefined, - error: undefined + const machine = createMachine({ + types: {} as { context: { ref: ActorRefFrom } }, + context: ({ spawn }) => ({ + ref: spawn(childMachine) }), - getPersistedSnapshot: (s) => s - }; - - const pingMachine = createMachine({ - types: {} as { - context: { ponger: ActorRefFrom | undefined }; - }, initial: 'waiting', - context: { - ponger: undefined - }, - entry: assign({ - ponger: ({ spawn }) => spawn(pongLogic) - }), states: { waiting: { - entry: sendTo(({ context }) => context.ponger!, { type: 'PING' }), - invoke: { - id: 'ponger', - src: pongLogic - }, - on: { - PONG: 'success' - } + on: { TEST: 'success' } }, success: { type: 'final' @@ -1191,75 +1271,14 @@ describe('actors', () => { } }); - const pingService = createActor(pingMachine); - pingService.subscribe({ + const actor = createActor(machine); + actor.subscribe({ complete: () => { - done(); + resolve(); } }); - pingService.start(); - }); - }); - - it('should be able to spawn callback actors in (lazy) initial context', (done) => { - const machine = createMachine({ - types: {} as { context: { ref: CallbackActorRef } }, - context: ({ spawn }) => ({ - ref: spawn( - fromCallback(({ sendBack }) => { - sendBack({ type: 'TEST' }); - }) - ) - }), - initial: 'waiting', - states: { - waiting: { - on: { TEST: 'success' } - }, - success: { - type: 'final' - } - } - }); - - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); - - it('should be able to spawn machines in (lazy) initial context', (done) => { - const childMachine = createMachine({ - entry: sendParent({ type: 'TEST' }) - }); - - const machine = createMachine({ - types: {} as { context: { ref: ActorRefFrom } }, - context: ({ spawn }) => ({ - ref: spawn(childMachine) - }), - initial: 'waiting', - states: { - waiting: { - on: { TEST: 'success' } - }, - success: { - type: 'final' - } - } - }); - - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + actor.start(); + })); // https://github.com/statelyai/xstate/issues/2507 it('should not crash on child machine sync completion during self-initialization', () => { @@ -1305,7 +1324,10 @@ describe('actors', () => { it('should not crash on child promise-like sync completion during self-initialization', () => { const promiseLogic = fromPromise( - () => ({ then: (fn: any) => fn(null) }) as any + () => + ({ + then: (fn: any) => fn(null) + }) as any ); const parentMachine = createMachine({ types: {} as { diff --git a/packages/core/test/actorLogic.test.ts b/packages/core/test/actorLogic.test.ts index 7a23aefb0b..f7b29a7802 100644 --- a/packages/core/test/actorLogic.test.ts +++ b/packages/core/test/actorLogic.test.ts @@ -33,44 +33,47 @@ describe('promise logic (fromPromise)', () => { expect(snapshot.output).toBe('hello'); }); - it('should resolve', (done) => { - const actor = createActor(fromPromise(() => Promise.resolve(42))); + it('should resolve', () => + new Promise((resolve) => { + const actor = createActor(fromPromise(() => Promise.resolve(42))); - actor.subscribe((state) => { - if (state.output === 42) { - done(); - } - }); + actor.subscribe((state) => { + if (state.output === 42) { + resolve(); + } + }); - actor.start(); - }); + actor.start(); + })); - it('should resolve (observer .next)', (done) => { - const actor = createActor(fromPromise(() => Promise.resolve(42))); + it('should resolve (observer .next)', () => + new Promise((resolve) => { + const actor = createActor(fromPromise(() => Promise.resolve(42))); - actor.subscribe({ - next: (state) => { - if (state.output === 42) { - done(); + actor.subscribe({ + next: (state) => { + if (state.output === 42) { + resolve(); + } } - } - }); + }); - actor.start(); - }); + actor.start(); + })); - it('should reject (observer .error)', (done) => { - const actor = createActor(fromPromise(() => Promise.reject('Error'))); + it('should reject (observer .error)', () => + new Promise((resolve) => { + const actor = createActor(fromPromise(() => Promise.reject('Error'))); - actor.subscribe({ - error: (data) => { - expect(data).toBe('Error'); - done(); - } - }); + actor.subscribe({ + error: (data) => { + expect(data).toBe('Error'); + resolve(); + } + }); - actor.start(); - }); + actor.start(); + })); it('should complete (observer .complete)', async () => { const actor = createActor(fromPromise(() => Promise.resolve(42))); @@ -95,45 +98,47 @@ describe('promise logic (fromPromise)', () => { expect(called).toBe(false); }); - it('should persist an unresolved promise', (done) => { - const promiseLogic = fromPromise( - () => - new Promise((res) => { - setTimeout(() => res(42), 10); - }) - ); - - const actor = createActor(promiseLogic); - actor.start(); + it('should persist an unresolved promise', () => + new Promise((resolve) => { + const promiseLogic = fromPromise( + () => + new Promise((res) => { + setTimeout(() => res(42), 10); + }) + ); - const resolvedPersistedState = actor.getPersistedSnapshot(); - actor.stop(); + const actor = createActor(promiseLogic); + actor.start(); - const restoredActor = createActor(promiseLogic, { - snapshot: resolvedPersistedState - }).start(); + const resolvedPersistedState = actor.getPersistedSnapshot(); + actor.stop(); - setTimeout(() => { - expect(restoredActor.getSnapshot().output).toBe(42); - done(); - }, 20); - }); + const restoredActor = createActor(promiseLogic, { + snapshot: resolvedPersistedState + }).start(); - it('should persist a resolved promise', (done) => { - const promiseLogic = fromPromise( - () => - new Promise((res) => { - res(42); - }) - ); + setTimeout(() => { + expect(restoredActor.getSnapshot().output).toBe(42); + resolve(); + }, 20); + })); + + it('should persist a resolved promise', () => + new Promise((resolve) => { + const promiseLogic = fromPromise( + () => + new Promise((res) => { + res(42); + }) + ); - const actor = createActor(promiseLogic); - actor.start(); + const actor = createActor(promiseLogic); + actor.start(); - setTimeout(() => { - const resolvedPersistedState = actor.getPersistedSnapshot(); + setTimeout(() => { + const resolvedPersistedState = actor.getPersistedSnapshot(); - expect(resolvedPersistedState).toMatchInlineSnapshot(` + expect(resolvedPersistedState).toMatchInlineSnapshot(` { "error": undefined, "input": undefined, @@ -142,13 +147,13 @@ describe('promise logic (fromPromise)', () => { } `); - const restoredActor = createActor(promiseLogic, { - snapshot: resolvedPersistedState - }).start(); - expect(restoredActor.getSnapshot().output).toBe(42); - done(); - }, 5); - }); + const restoredActor = createActor(promiseLogic, { + snapshot: resolvedPersistedState + }).start(); + expect(restoredActor.getSnapshot().output).toBe(42); + resolve(); + }, 5); + })); it('should not invoke a resolved promise again', async () => { let createdPromises = 0; @@ -705,39 +710,40 @@ describe('callback logic (fromCallback)', () => { createActor(callbackLogic).start(); }); - it('can send self reference in an event to parent', (done) => { - const machine = createMachine({ - types: {} as { - events: { type: 'PING'; ref: AnyActorRef }; - }, - invoke: { - src: fromCallback(({ self, sendBack, receive }) => { - receive((event) => { - switch (event.type) { - case 'PONG': { - done(); + it('can send self reference in an event to parent', () => + new Promise((resolve) => { + const machine = createMachine({ + types: {} as { + events: { type: 'PING'; ref: AnyActorRef }; + }, + invoke: { + src: fromCallback(({ self, sendBack, receive }) => { + receive((event) => { + switch (event.type) { + case 'PONG': { + resolve(); + } } - } - }); + }); - sendBack({ - type: 'PING', - ref: self - }); - }) - }, - on: { - PING: { - actions: sendTo( - ({ event }) => event.ref, - () => ({ type: 'PONG' }) - ) + sendBack({ + type: 'PING', + ref: self + }); + }) + }, + on: { + PING: { + actions: sendTo( + ({ event }) => event.ref, + () => ({ type: 'PONG' }) + ) + } } - } - }); + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); it('should persist the input of a callback', () => { const spy = vi.fn(); @@ -1186,35 +1192,36 @@ describe('composable actor logic', () => { expect(logs).toEqual([42]); }); - it('should work with observables', (done) => { - const logs: any[] = []; + it('should work with observables', () => + new Promise((resolve) => { + const logs: any[] = []; - function withLogs(actorLogic: T): T { - return { - ...actorLogic, - transition: (state: Snapshot, event, actorScope) => { - const s = actorLogic.transition(state, event, actorScope); + function withLogs(actorLogic: T): T { + return { + ...actorLogic, + transition: (state: Snapshot, event, actorScope) => { + const s = actorLogic.transition(state, event, actorScope); - if (s.status === 'active') { - logs.push(s.context); - } + if (s.status === 'active') { + logs.push(s.context); + } - return s; - } - }; - } + return s; + } + }; + } - const observableLogic = fromObservable(() => interval(10).pipe(take(4))); + const observableLogic = fromObservable(() => interval(10).pipe(take(4))); - const actor = createActor(withLogs(observableLogic)).start(); + const actor = createActor(withLogs(observableLogic)).start(); - actor.subscribe({ - complete: () => { - expect(logs).toEqual([0, 1, 2, 3]); - done(); - } - }); - }); + actor.subscribe({ + complete: () => { + expect(logs).toEqual([0, 1, 2, 3]); + resolve(); + } + }); + })); it('higher-level logic wrapping a machine should be able to persist a snapshot', () => { const logged: any[] = []; diff --git a/packages/core/test/after.test.ts b/packages/core/test/after.test.ts index 655bb4399d..42efde948c 100644 --- a/packages/core/test/after.test.ts +++ b/packages/core/test/after.test.ts @@ -85,73 +85,79 @@ describe('delayed transitions', () => { `); }); - it('should be able to transition with delay from nested initial state', (done) => { - const machine = createMachine({ - initial: 'nested', - states: { - nested: { - initial: 'wait', - states: { - wait: { - after: { - 10: '#end' + it('should be able to transition with delay from nested initial state', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'nested', + states: { + nested: { + initial: 'wait', + states: { + wait: { + after: { + 10: '#end' + } } } + }, + end: { + id: 'end', + type: 'final' } - }, - end: { - id: 'end', - type: 'final' } - } - }); - - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + }); - it('parent state should enter child state without re-entering self (relative target)', (done) => { - const actual: string[] = []; - const machine = createMachine({ - initial: 'one', - states: { - one: { - initial: 'two', - entry: () => actual.push('entered one'), - states: { - two: { - entry: () => actual.push('entered two') + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); + + it('parent state should enter child state without re-entering self (relative target)', () => + new Promise((resolve) => { + const actual: string[] = []; + const machine = createMachine({ + initial: 'one', + states: { + one: { + initial: 'two', + entry: () => actual.push('entered one'), + states: { + two: { + entry: () => actual.push('entered two') + }, + three: { + entry: () => actual.push('entered three'), + always: '#end' + } }, - three: { - entry: () => actual.push('entered three'), - always: '#end' + after: { + 10: '.three' } }, - after: { - 10: '.three' + end: { + id: 'end', + type: 'final' } - }, - end: { - id: 'end', - type: 'final' } - } - }); + }); - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - expect(actual).toEqual(['entered one', 'entered two', 'entered three']); - done(); - } - }); - actor.start(); - }); + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + expect(actual).toEqual([ + 'entered one', + 'entered two', + 'entered three' + ]); + resolve(); + } + }); + actor.start(); + })); it('should defer a single send event for a delayed conditional transition (#886)', () => { vi.useFakeTimers(); @@ -190,69 +196,73 @@ describe('delayed transitions', () => { }); // TODO: figure out correct behavior for restoring delayed transitions - it.skip('should execute an after transition after starting from a state resolved using `.getPersistedSnapshot`', (done) => { - const machine = createMachine({ - id: 'machine', - initial: 'a', - states: { - a: { - on: { next: 'withAfter' } - }, - - withAfter: { - after: { - 1: { target: 'done' } - } - }, - - done: { - type: 'final' - } - } - }); - - const actorRef1 = createActor(machine).start(); - actorRef1.send({ type: 'next' }); - const withAfterState = actorRef1.getPersistedSnapshot(); - - const actorRef2 = createActor(machine, { snapshot: withAfterState }); - actorRef2.subscribe({ complete: () => done() }); - actorRef2.start(); - }); - - it('should execute an after transition after starting from a persisted state', (done) => { - const createMyMachine = () => - createMachine({ - initial: 'A', + it.skip('should execute an after transition after starting from a state resolved using `.getPersistedSnapshot`', () => + new Promise((resolve) => { + const machine = createMachine({ + id: 'machine', + initial: 'a', states: { - A: { - on: { - NEXT: 'B' - } + a: { + on: { next: 'withAfter' } }, - B: { + + withAfter: { after: { - 1: 'C' + 1: { target: 'done' } } }, - C: { + + done: { type: 'final' } } }); - let service = createActor(createMyMachine()).start(); + const actorRef1 = createActor(machine).start(); + actorRef1.send({ type: 'next' }); + const withAfterState = actorRef1.getPersistedSnapshot(); - const persistedSnapshot = JSON.parse(JSON.stringify(service.getSnapshot())); + const actorRef2 = createActor(machine, { snapshot: withAfterState }); + actorRef2.subscribe({ complete: () => resolve() }); + actorRef2.start(); + })); - service = createActor(createMyMachine(), { - snapshot: persistedSnapshot - }).start(); + it('should execute an after transition after starting from a persisted state', () => + new Promise((resolve) => { + const createMyMachine = () => + createMachine({ + initial: 'A', + states: { + A: { + on: { + NEXT: 'B' + } + }, + B: { + after: { + 1: 'C' + } + }, + C: { + type: 'final' + } + } + }); - service.send({ type: 'NEXT' }); + let service = createActor(createMyMachine()).start(); - service.subscribe({ complete: () => done() }); - }); + const persistedSnapshot = JSON.parse( + JSON.stringify(service.getSnapshot()) + ); + + service = createActor(createMyMachine(), { + snapshot: persistedSnapshot + }).start(); + + service.send({ type: 'NEXT' }); + + service.subscribe({ complete: () => resolve() }); + })); describe('delay expressions', () => { it('should evaluate the expression (function) to determine the delay', () => { diff --git a/packages/core/test/assert.test.ts b/packages/core/test/assert.test.ts index 035f1d0d62..6cc054d600 100644 --- a/packages/core/test/assert.test.ts +++ b/packages/core/test/assert.test.ts @@ -1,100 +1,102 @@ import { createActor, createMachine, assertEvent } from '../src'; describe('assertion helpers', () => { - it('assertEvent asserts the correct event type', (done) => { - const machine = createMachine( - { - types: { - events: {} as - | { type: 'greet'; message: string } - | { type: 'count'; value: number } + it('assertEvent asserts the correct event type', () => + new Promise((resolve) => { + const machine = createMachine( + { + types: { + events: {} as + | { type: 'greet'; message: string } + | { type: 'count'; value: number } + }, + on: { + greet: { actions: 'greet' }, + count: { actions: 'greet' } + } }, - on: { - greet: { actions: 'greet' }, - count: { actions: 'greet' } - } - }, - { - actions: { - greet: ({ event }) => { - // @ts-expect-error - event.message; - - assertEvent(event, 'greet'); - event.message satisfies string; - - // @ts-expect-error - event.count; + { + actions: { + greet: ({ event }) => { + // @ts-expect-error + event.message; + + assertEvent(event, 'greet'); + event.message satisfies string; + + // @ts-expect-error + event.count; + } } } - } - ); - - const actor = createActor(machine); - - actor.subscribe({ - error(err) { - expect(err).toMatchInlineSnapshot( - `[Error: Expected event {"type":"count","value":42} to have type "greet"]` - ); - done(); - } - }); - - actor.start(); - - actor.send({ type: 'count', value: 42 }); - }); - - it('assertEvent asserts multiple event types', (done) => { - const machine = createMachine( - { - types: { - events: {} as - | { type: 'greet'; message: string } - | { type: 'notify'; message: string; level: 'info' | 'error' } - | { type: 'count'; value: number } - }, - on: { - greet: { actions: 'greet' }, - count: { actions: 'greet' } + ); + + const actor = createActor(machine); + + actor.subscribe({ + error(err) { + expect(err).toMatchInlineSnapshot( + `[Error: Expected event {"type":"count","value":42} to have type "greet"]` + ); + resolve(); } - }, - { - actions: { - greet: ({ event }) => { - // @ts-expect-error - event.message; + }); + + actor.start(); + + actor.send({ type: 'count', value: 42 }); + })); + + it('assertEvent asserts multiple event types', () => + new Promise((resolve) => { + const machine = createMachine( + { + types: { + events: {} as + | { type: 'greet'; message: string } + | { type: 'notify'; message: string; level: 'info' | 'error' } + | { type: 'count'; value: number } + }, + on: { + greet: { actions: 'greet' }, + count: { actions: 'greet' } + } + }, + { + actions: { + greet: ({ event }) => { + // @ts-expect-error + event.message; - assertEvent(event, ['greet', 'notify']); - event.message satisfies string; + assertEvent(event, ['greet', 'notify']); + event.message satisfies string; - // @ts-expect-error - event.level; + // @ts-expect-error + event.level; - assertEvent(event, ['notify']); - event.level satisfies 'info' | 'error'; + assertEvent(event, ['notify']); + event.level satisfies 'info' | 'error'; - // @ts-expect-error - event.count; + // @ts-expect-error + event.count; + } } } - } - ); + ); - const actor = createActor(machine); + const actor = createActor(machine); - actor.subscribe({ - error(err) { - expect(err).toMatchInlineSnapshot( - `[Error: Expected event {"type":"count","value":42} to have one of types "greet", "notify"]` - ); - done(); - } - }); + actor.subscribe({ + error(err) { + expect(err).toMatchInlineSnapshot( + `[Error: Expected event {"type":"count","value":42} to have one of types "greet", "notify"]` + ); + resolve(); + } + }); - actor.start(); + actor.start(); - actor.send({ type: 'count', value: 42 }); - }); + actor.send({ type: 'count', value: 42 }); + })); }); diff --git a/packages/core/test/assign.test.ts b/packages/core/test/assign.test.ts index b0e8055fb4..537f8bbc36 100644 --- a/packages/core/test/assign.test.ts +++ b/packages/core/test/assign.test.ts @@ -338,31 +338,32 @@ describe('assign meta', () => { expect(actor.getSnapshot().context.count).toEqual(11); }); - it('a parameterized action that resolves to assign() should be provided the params', (done) => { - const machine = createMachine( - { - on: { - EVENT: { - actions: { - type: 'inc', - params: { value: 5 } + it('a parameterized action that resolves to assign() should be provided the params', () => + new Promise((resolve) => { + const machine = createMachine( + { + on: { + EVENT: { + actions: { + type: 'inc', + params: { value: 5 } + } } } + }, + { + actions: { + inc: assign(({ context }, params) => { + expect(params).toEqual({ value: 5 }); + resolve(); + return context; + }) + } } - }, - { - actions: { - inc: assign(({ context }, params) => { - expect(params).toEqual({ value: 5 }); - done(); - return context; - }) - } - } - ); + ); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - service.send({ type: 'EVENT' }); - }); + service.send({ type: 'EVENT' }); + })); }); diff --git a/packages/core/test/errors.test.ts b/packages/core/test/errors.test.ts index d40946adb8..19960ddc91 100644 --- a/packages/core/test/errors.test.ts +++ b/packages/core/test/errors.test.ts @@ -21,190 +21,195 @@ afterEach(() => { describe('error handling', () => { // https://github.com/statelyai/xstate/issues/4004 - it('does not cause an infinite loop when an error is thrown in subscribe', (done) => { - const machine = createMachine({ - id: 'machine', - initial: 'initial', - context: { - count: 0 - }, - states: { - initial: { - on: { activate: 'active' } + it('does not cause an infinite loop when an error is thrown in subscribe', () => + new Promise((resolve) => { + const machine = createMachine({ + id: 'machine', + initial: 'initial', + context: { + count: 0 }, - active: {} - } - }); - - const spy = vi.fn().mockImplementation(() => { - throw new Error('no_infinite_loop_when_error_is_thrown_in_subscribe'); - }); - - const actor = createActor(machine).start(); + states: { + initial: { + on: { activate: 'active' } + }, + active: {} + } + }); - actor.subscribe(spy); - actor.send({ type: 'activate' }); + const spy = vi.fn().mockImplementation(() => { + throw new Error('no_infinite_loop_when_error_is_thrown_in_subscribe'); + }); - expect(spy).toHaveBeenCalledTimes(1); + const actor = createActor(machine).start(); - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual( - 'no_infinite_loop_when_error_is_thrown_in_subscribe' - ); - done(); - }); - }); + actor.subscribe(spy); + actor.send({ type: 'activate' }); - it(`doesn't crash the actor when an error is thrown in subscribe`, (done) => { - const spy = vi.fn(); + expect(spy).toHaveBeenCalledTimes(1); - const machine = createMachine({ - id: 'machine', - initial: 'initial', - context: { - count: 0 - }, - states: { - initial: { - on: { activate: 'active' } + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual( + 'no_infinite_loop_when_error_is_thrown_in_subscribe' + ); + resolve(); + }); + })); + + it(`doesn't crash the actor when an error is thrown in subscribe`, () => + new Promise((resolve) => { + const spy = vi.fn(); + + const machine = createMachine({ + id: 'machine', + initial: 'initial', + context: { + count: 0 }, - active: { - on: { - do: { - actions: spy + states: { + initial: { + on: { activate: 'active' } + }, + active: { + on: { + do: { + actions: spy + } } } } - } - }); + }); - const subscriber = vi.fn().mockImplementationOnce(() => { - throw new Error('doesnt_crash_actor_when_error_is_thrown_in_subscribe'); - }); + const subscriber = vi.fn().mockImplementationOnce(() => { + throw new Error('doesnt_crash_actor_when_error_is_thrown_in_subscribe'); + }); - const actor = createActor(machine).start(); + const actor = createActor(machine).start(); - actor.subscribe(subscriber); - actor.send({ type: 'activate' }); + actor.subscribe(subscriber); + actor.send({ type: 'activate' }); - expect(subscriber).toHaveBeenCalledTimes(1); - expect(actor.getSnapshot().status).toEqual('active'); + expect(subscriber).toHaveBeenCalledTimes(1); + expect(actor.getSnapshot().status).toEqual('active'); - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual( - 'doesnt_crash_actor_when_error_is_thrown_in_subscribe' - ); + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual( + 'doesnt_crash_actor_when_error_is_thrown_in_subscribe' + ); - actor.send({ type: 'do' }); - expect(spy).toHaveBeenCalledTimes(1); + actor.send({ type: 'do' }); + expect(spy).toHaveBeenCalledTimes(1); - done(); - }); - }); + resolve(); + }); + })); - it(`doesn't notify error listener when an error is thrown in subscribe`, (done) => { - const machine = createMachine({ - id: 'machine', - initial: 'initial', - context: { - count: 0 - }, - states: { - initial: { - on: { activate: 'active' } + it(`doesn't notify error listener when an error is thrown in subscribe`, () => + new Promise((resolve) => { + const machine = createMachine({ + id: 'machine', + initial: 'initial', + context: { + count: 0 }, - active: {} - } - }); - - const nextSpy = vi.fn().mockImplementation(() => { - throw new Error( - 'doesnt_notify_error_listener_when_error_is_thrown_in_subscribe' - ); - }); - const errorSpy = vi.fn(); - - const actor = createActor(machine).start(); - - actor.subscribe({ - next: nextSpy, - error: errorSpy - }); - actor.send({ type: 'activate' }); - - expect(nextSpy).toHaveBeenCalledTimes(1); - expect(errorSpy).toHaveBeenCalledTimes(0); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual( - 'doesnt_notify_error_listener_when_error_is_thrown_in_subscribe' - ); - done(); - }); - }); - - it('unhandled sync errors thrown when starting a child actor should be reported globally', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('unhandled_sync_error_in_actor_start'); - }), - onDone: 'success' + states: { + initial: { + on: { activate: 'active' } + }, + active: {} + } + }); + + const nextSpy = vi.fn().mockImplementation(() => { + throw new Error( + 'doesnt_notify_error_listener_when_error_is_thrown_in_subscribe' + ); + }); + const errorSpy = vi.fn(); + + const actor = createActor(machine).start(); + + actor.subscribe({ + next: nextSpy, + error: errorSpy + }); + actor.send({ type: 'activate' }); + + expect(nextSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledTimes(0); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual( + 'doesnt_notify_error_listener_when_error_is_thrown_in_subscribe' + ); + resolve(); + }); + })); + + it('unhandled sync errors thrown when starting a child actor should be reported globally', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('unhandled_sync_error_in_actor_start'); + }), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - - createActor(machine).start(); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual('unhandled_sync_error_in_actor_start'); - done(); - }); - }); - - it('unhandled rejection of a promise actor should be reported globally in absence of error listener', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - Promise.reject( - new Error( - 'unhandled_rejection_in_promise_actor_without_error_listener' + }); + + createActor(machine).start(); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual('unhandled_sync_error_in_actor_start'); + resolve(); + }); + })); + + it('unhandled rejection of a promise actor should be reported globally in absence of error listener', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + Promise.reject( + new Error( + 'unhandled_rejection_in_promise_actor_without_error_listener' + ) ) - ) - ), - onDone: 'success' + ), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - createActor(machine).start(); + createActor(machine).start(); - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual( - 'unhandled_rejection_in_promise_actor_without_error_listener' - ); - done(); - }); - }); + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual( + 'unhandled_rejection_in_promise_actor_without_error_listener' + ); + resolve(); + }); + })); it('unhandled rejection of a promise actor should be reported to the existing error listener of its parent', async () => { const errorSpy = vi.fn(); @@ -303,142 +308,146 @@ describe('error handling', () => { `); }); - it('handled sync errors thrown when starting a child actor should not be reported globally', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('handled_sync_error_in_actor_start'); - }), - onError: 'failed' + it('handled sync errors thrown when starting a child actor should not be reported globally', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('handled_sync_error_in_actor_start'); + }), + onError: 'failed' + } + }, + failed: { + type: 'final' } - }, - failed: { - type: 'final' } - } - }); - - createActor(machine).start(); - - installGlobalOnErrorHandler(() => { - done.fail(); - }); - - setTimeout(() => { - done(); - }, 10); - }); - - it('handled sync errors thrown when starting a child actor should be reported globally when not all of its own observers come with an error listener', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('handled_sync_error_in_actor_start'); - }), - onError: 'failed' + }); + + createActor(machine).start(); + + installGlobalOnErrorHandler(() => { + done.fail(); + }); + + setTimeout(() => { + resolve(); + }, 10); + })); + + it('handled sync errors thrown when starting a child actor should be reported globally when not all of its own observers come with an error listener', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('handled_sync_error_in_actor_start'); + }), + onError: 'failed' + } + }, + failed: { + type: 'final' } - }, - failed: { - type: 'final' } - } - }); - - const actorRef = createActor(machine); - const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; - childActorRef.subscribe({ - error: function preventUnhandledErrorListener() {} - }); - childActorRef.subscribe(() => {}); - actorRef.start(); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual('handled_sync_error_in_actor_start'); - done(); - }); - }); - - it('handled sync errors thrown when starting a child actor should not be reported globally when all of its own observers come with an error listener', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('handled_sync_error_in_actor_start'); - }), - onError: 'failed' + }); + + const actorRef = createActor(machine); + const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; + childActorRef.subscribe({ + error: function preventUnhandledErrorListener() {} + }); + childActorRef.subscribe(() => {}); + actorRef.start(); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual('handled_sync_error_in_actor_start'); + resolve(); + }); + })); + + it('handled sync errors thrown when starting a child actor should not be reported globally when all of its own observers come with an error listener', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('handled_sync_error_in_actor_start'); + }), + onError: 'failed' + } + }, + failed: { + type: 'final' } - }, - failed: { - type: 'final' } - } - }); - - const actorRef = createActor(machine); - const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; - childActorRef.subscribe({ - error: function preventUnhandledErrorListener() {} - }); - childActorRef.subscribe({ - error: function preventUnhandledErrorListener() {} - }); - actorRef.start(); - - installGlobalOnErrorHandler(() => { - done.fail(); - }); - - setTimeout(() => { - done(); - }, 10); - }); - - it('unhandled sync errors thrown when starting a child actor should be reported twice globally when not all of its own observers come with an error listener and when the root has no error listener of its own', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('handled_sync_error_in_actor_start'); - }) + }); + + const actorRef = createActor(machine); + const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; + childActorRef.subscribe({ + error: function preventUnhandledErrorListener() {} + }); + childActorRef.subscribe({ + error: function preventUnhandledErrorListener() {} + }); + actorRef.start(); + + installGlobalOnErrorHandler(() => { + done.fail(); + }); + + setTimeout(() => { + resolve(); + }, 10); + })); + + it('unhandled sync errors thrown when starting a child actor should be reported twice globally when not all of its own observers come with an error listener and when the root has no error listener of its own', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('handled_sync_error_in_actor_start'); + }) + } } } - } - }); - - const actorRef = createActor(machine); - const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; - childActorRef.subscribe({ - error: function preventUnhandledErrorListener() {} - }); - childActorRef.subscribe({}); - actorRef.start(); - - const actual: string[] = []; - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - actual.push(ev.error.message); - - if (actual.length === 2) { - expect(actual).toEqual([ - 'handled_sync_error_in_actor_start', - 'handled_sync_error_in_actor_start' - ]); - done(); - } - }); - }); + }); + + const actorRef = createActor(machine); + const childActorRef = Object.values(actorRef.getSnapshot().children)[0]; + childActorRef.subscribe({ + error: function preventUnhandledErrorListener() {} + }); + childActorRef.subscribe({}); + actorRef.start(); + + const actual: string[] = []; + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + actual.push(ev.error.message); + + if (actual.length === 2) { + expect(actual).toEqual([ + 'handled_sync_error_in_actor_start', + 'handled_sync_error_in_actor_start' + ]); + resolve(); + } + }); + })); it(`handled sync errors shouldn't notify the error listener`, () => { const machine = createMachine({ @@ -506,44 +515,45 @@ describe('error handling', () => { `); }); - it(`unhandled sync errors should not notify the global listener when the root error listener is present`, (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error( - 'unhandled_sync_error_in_actor_start_with_root_error_listener' - ); - }), - onDone: 'success' + it(`unhandled sync errors should not notify the global listener when the root error listener is present`, () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error( + 'unhandled_sync_error_in_actor_start_with_root_error_listener' + ); + }), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const errorSpy = vi.fn(); + const errorSpy = vi.fn(); - const actorRef = createActor(machine); - actorRef.subscribe({ - error: errorSpy - }); - actorRef.start(); + const actorRef = createActor(machine); + actorRef.subscribe({ + error: errorSpy + }); + actorRef.start(); - expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledTimes(1); - installGlobalOnErrorHandler(() => { - done.fail(); - }); + installGlobalOnErrorHandler(() => { + done.fail(); + }); - setTimeout(() => { - done(); - }, 10); - }); + setTimeout(() => { + resolve(); + }, 10); + })); it(`handled sync errors thrown when starting an actor shouldn't crash the parent`, () => { const spy = vi.fn(); @@ -578,133 +588,137 @@ describe('error handling', () => { expect(spy).toHaveBeenCalledTimes(1); }); - it(`unhandled sync errors thrown when starting an actor should crash the parent`, (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('unhandled_sync_error_in_actor_start'); - }) + it(`unhandled sync errors thrown when starting an actor should crash the parent`, () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('unhandled_sync_error_in_actor_start'); + }) + } } } - } - }); - - const actorRef = createActor(machine); - actorRef.start(); - - expect(actorRef.getSnapshot().status).toBe('error'); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual('unhandled_sync_error_in_actor_start'); - done(); - }); - }); - - it(`error thrown by the error listener should be reported globally`, (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error('handled_sync_error_in_actor_start'); - }) + }); + + const actorRef = createActor(machine); + actorRef.start(); + + expect(actorRef.getSnapshot().status).toBe('error'); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual('unhandled_sync_error_in_actor_start'); + resolve(); + }); + })); + + it(`error thrown by the error listener should be reported globally`, () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error('handled_sync_error_in_actor_start'); + }) + } } } - } - }); + }); - const actorRef = createActor(machine); - actorRef.subscribe({ - error: () => { - throw new Error('error_thrown_by_error_listener'); - } - }); - actorRef.start(); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual('error_thrown_by_error_listener'); - done(); - }); - }); - - it(`error should be reported globally if not every observer comes with an error listener`, (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error( - 'error_thrown_when_not_every_observer_comes_with_an_error_listener' - ); - }) + const actorRef = createActor(machine); + actorRef.subscribe({ + error: () => { + throw new Error('error_thrown_by_error_listener'); + } + }); + actorRef.start(); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual('error_thrown_by_error_listener'); + resolve(); + }); + })); + + it(`error should be reported globally if not every observer comes with an error listener`, () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error( + 'error_thrown_when_not_every_observer_comes_with_an_error_listener' + ); + }) + } } } - } - }); - - const actorRef = createActor(machine); - actorRef.subscribe({ - error: function preventUnhandledErrorListener() {} - }); - actorRef.subscribe(() => {}); - actorRef.start(); - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - expect(ev.error.message).toEqual( - 'error_thrown_when_not_every_observer_comes_with_an_error_listener' - ); - done(); - }); - }); - - it(`uncaught error and an error thrown by the error listener should both be reported globally when not every observer comes with an error listener`, (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromCallback(() => { - throw new Error( - 'error_thrown_when_not_every_observer_comes_with_an_error_listener' - ); - }) + }); + + const actorRef = createActor(machine); + actorRef.subscribe({ + error: function preventUnhandledErrorListener() {} + }); + actorRef.subscribe(() => {}); + actorRef.start(); + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + expect(ev.error.message).toEqual( + 'error_thrown_when_not_every_observer_comes_with_an_error_listener' + ); + resolve(); + }); + })); + + it(`uncaught error and an error thrown by the error listener should both be reported globally when not every observer comes with an error listener`, () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromCallback(() => { + throw new Error( + 'error_thrown_when_not_every_observer_comes_with_an_error_listener' + ); + }) + } } } - } - }); - - const actorRef = createActor(machine); - actorRef.subscribe({ - error: () => { - throw new Error('error_thrown_by_error_listener'); - } - }); - actorRef.subscribe(() => {}); - actorRef.start(); + }); - let actual: string[] = []; - - installGlobalOnErrorHandler((ev) => { - ev.preventDefault(); - actual.push(ev.error.message); - - if (actual.length === 2) { - expect(actual).toEqual([ - 'error_thrown_by_error_listener', - 'error_thrown_when_not_every_observer_comes_with_an_error_listener' - ]); - done(); - } - }); - }); + const actorRef = createActor(machine); + actorRef.subscribe({ + error: () => { + throw new Error('error_thrown_by_error_listener'); + } + }); + actorRef.subscribe(() => {}); + actorRef.start(); + + let actual: string[] = []; + + installGlobalOnErrorHandler((ev) => { + ev.preventDefault(); + actual.push(ev.error.message); + + if (actual.length === 2) { + expect(actual).toEqual([ + 'error_thrown_by_error_listener', + 'error_thrown_when_not_every_observer_comes_with_an_error_listener' + ]); + resolve(); + } + }); + })); it('error thrown in initial custom entry action should error the actor', () => { const machine = createMachine({ diff --git a/packages/core/test/event.test.ts b/packages/core/test/event.test.ts index 12d0964c6b..01808bfbd2 100644 --- a/packages/core/test/event.test.ts +++ b/packages/core/test/event.test.ts @@ -7,63 +7,64 @@ import { import { sendTo } from '../src/actions/send'; describe('events', () => { - it('should be able to respond to sender by sending self', (done) => { - const authServerMachine = createMachine({ - types: { - events: {} as { type: 'CODE'; sender: AnyActorRef } - }, - id: 'authServer', - initial: 'waitingForCode', - states: { - waitingForCode: { - on: { - CODE: { - actions: sendTo( - ({ event }) => { - expect(event.sender).toBeDefined(); - return event.sender; - }, - { type: 'TOKEN' }, - { delay: 10 } - ) + it('should be able to respond to sender by sending self', () => + new Promise((resolve) => { + const authServerMachine = createMachine({ + types: { + events: {} as { type: 'CODE'; sender: AnyActorRef } + }, + id: 'authServer', + initial: 'waitingForCode', + states: { + waitingForCode: { + on: { + CODE: { + actions: sendTo( + ({ event }) => { + expect(event.sender).toBeDefined(); + return event.sender; + }, + { type: 'TOKEN' }, + { delay: 10 } + ) + } } } } - } - }); + }); - const authClientMachine = createMachine({ - id: 'authClient', - initial: 'idle', - states: { - idle: { - on: { AUTH: 'authorizing' } - }, - authorizing: { - invoke: { - id: 'auth-server', - src: authServerMachine + const authClientMachine = createMachine({ + id: 'authClient', + initial: 'idle', + states: { + idle: { + on: { AUTH: 'authorizing' } }, - entry: sendTo('auth-server', ({ self }) => ({ - type: 'CODE', - sender: self - })), - on: { - TOKEN: 'authorized' + authorizing: { + invoke: { + id: 'auth-server', + src: authServerMachine + }, + entry: sendTo('auth-server', ({ self }) => ({ + type: 'CODE', + sender: self + })), + on: { + TOKEN: 'authorized' + } + }, + authorized: { + type: 'final' } - }, - authorized: { - type: 'final' } - } - }); + }); - const service = createActor(authClientMachine); - service.subscribe({ complete: () => done() }); - service.start(); + const service = createActor(authClientMachine); + service.subscribe({ complete: () => resolve() }); + service.start(); - service.send({ type: 'AUTH' }); - }); + service.send({ type: 'AUTH' }); + })); }); describe('nested transitions', () => { diff --git a/packages/core/test/final.test.ts b/packages/core/test/final.test.ts index 9782b1aab9..3e0e32ebd7 100644 --- a/packages/core/test/final.test.ts +++ b/packages/core/test/final.test.ts @@ -125,61 +125,62 @@ describe('final states', () => { expect(actual).toEqual(['bazAction', 'barAction', 'fooAction']); }); - it('should call output expressions on nested final nodes', (done) => { - interface Ctx { - revealedSecret?: string; - } + it('should call output expressions on nested final nodes', () => + new Promise((resolve) => { + interface Ctx { + revealedSecret?: string; + } - const machine = createMachine({ - types: {} as { context: Ctx }, - initial: 'secret', - context: { - revealedSecret: undefined - }, - states: { - secret: { - initial: 'wait', - states: { - wait: { - on: { - REQUEST_SECRET: 'reveal' + const machine = createMachine({ + types: {} as { context: Ctx }, + initial: 'secret', + context: { + revealedSecret: undefined + }, + states: { + secret: { + initial: 'wait', + states: { + wait: { + on: { + REQUEST_SECRET: 'reveal' + } + }, + reveal: { + type: 'final', + output: () => ({ + secret: 'the secret' + }) } }, - reveal: { - type: 'final', - output: () => ({ - secret: 'the secret' + onDone: { + target: 'success', + actions: assign({ + revealedSecret: ({ event }) => { + return (event.output as any).secret; + } }) } }, - onDone: { - target: 'success', - actions: assign({ - revealedSecret: ({ event }) => { - return (event.output as any).secret; - } - }) + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - - const service = createActor(machine); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().context).toEqual({ - revealedSecret: 'the secret' - }); - done(); - } - }); - service.start(); + }); + + const service = createActor(machine); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().context).toEqual({ + revealedSecret: 'the secret' + }); + resolve(); + } + }); + service.start(); - service.send({ type: 'REQUEST_SECRET' }); - }); + service.send({ type: 'REQUEST_SECRET' }); + })); it("should only call data expression once when entering root's final state", () => { const spy = vi.fn(); diff --git a/packages/core/test/input.test.ts b/packages/core/test/input.test.ts index 478e85f5e0..0d82d6a147 100644 --- a/packages/core/test/input.test.ts +++ b/packages/core/test/input.test.ts @@ -30,16 +30,17 @@ describe('input', () => { expect(spy).toHaveBeenCalledWith(42); }); - it('initial event should have input property', (done) => { - const machine = createMachine({ - entry: ({ event }) => { - expect(event.input.greeting).toBe('hello'); - done(); - } - }); + it('initial event should have input property', () => + new Promise((resolve) => { + const machine = createMachine({ + entry: ({ event }) => { + expect(event.input.greeting).toBe('hello'); + resolve(); + } + }); - createActor(machine, { input: { greeting: 'hello' } }).start(); - }); + createActor(machine, { input: { greeting: 'hello' } }).start(); + })); it('should error if input is expected but not provided', () => { const machine = createMachine({ @@ -69,56 +70,58 @@ describe('input', () => { }).not.toThrowError(); }); - it('should provide input data to invoked machines', (done) => { - const invokedMachine = createMachine({ - types: {} as { - input: { greeting: string }; - context: { greeting: string }; - }, - context: ({ input }) => input, - entry: ({ context, event }) => { - expect(context.greeting).toBe('hello'); - expect(event.input.greeting).toBe('hello'); - done(); - } - }); + it('should provide input data to invoked machines', () => + new Promise((resolve) => { + const invokedMachine = createMachine({ + types: {} as { + input: { greeting: string }; + context: { greeting: string }; + }, + context: ({ input }) => input, + entry: ({ context, event }) => { + expect(context.greeting).toBe('hello'); + expect(event.input.greeting).toBe('hello'); + resolve(); + } + }); - const machine = createMachine({ - invoke: { - src: invokedMachine, - input: { greeting: 'hello' } - } - }); + const machine = createMachine({ + invoke: { + src: invokedMachine, + input: { greeting: 'hello' } + } + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); - it('should provide input data to spawned machines', (done) => { - const spawnedMachine = createMachine({ - types: {} as { - input: { greeting: string }; - context: { greeting: string }; - }, - context({ input }) { - return input; - }, - entry: ({ context, event }) => { - expect(context.greeting).toBe('hello'); - expect(event.input.greeting).toBe('hello'); - done(); - } - }); + it('should provide input data to spawned machines', () => + new Promise((resolve) => { + const spawnedMachine = createMachine({ + types: {} as { + input: { greeting: string }; + context: { greeting: string }; + }, + context({ input }) { + return input; + }, + entry: ({ context, event }) => { + expect(context.greeting).toBe('hello'); + expect(event.input.greeting).toBe('hello'); + resolve(); + } + }); - const machine = createMachine({ - entry: assign(({ spawn }) => { - return { - ref: spawn(spawnedMachine, { input: { greeting: 'hello' } }) - }; - }) - }); + const machine = createMachine({ + entry: assign(({ spawn }) => { + return { + ref: spawn(spawnedMachine, { input: { greeting: 'hello' } }) + }; + }) + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); it('should create a promise with input', async () => { const promiseLogic = fromPromise<{ count: number }, { count: number }>( @@ -147,36 +150,38 @@ describe('input', () => { expect(transitionActor.getSnapshot().context).toEqual({ count: 42 }); }); - it('should create an observable actor with input', (done) => { - const observableLogic = fromObservable< - { count: number }, - { count: number } - >(({ input }) => of(input)); - - const observableActor = createActor(observableLogic, { - input: { count: 42 } - }); - - const sub = observableActor.subscribe((state) => { - if (state.context?.count !== 42) return; - expect(state.context).toEqual({ count: 42 }); - done(); - sub.unsubscribe(); - }); - - observableActor.start(); - }); - - it('should create a callback actor with input', (done) => { - const callbackLogic = fromCallback(({ input }) => { - expect(input).toEqual({ count: 42 }); - done(); - }); - - createActor(callbackLogic, { - input: { count: 42 } - }).start(); - }); + it('should create an observable actor with input', () => + new Promise((resolve) => { + const observableLogic = fromObservable< + { count: number }, + { count: number } + >(({ input }) => of(input)); + + const observableActor = createActor(observableLogic, { + input: { count: 42 } + }); + + const sub = observableActor.subscribe((state) => { + if (state.context?.count !== 42) return; + expect(state.context).toEqual({ count: 42 }); + resolve(); + sub.unsubscribe(); + }); + + observableActor.start(); + })); + + it('should create a callback actor with input', () => + new Promise((resolve) => { + const callbackLogic = fromCallback(({ input }) => { + expect(input).toEqual({ count: 42 }); + resolve(); + }); + + createActor(callbackLogic, { + input: { count: 42 } + }).start(); + })); it('should provide a static inline input to the referenced actor', () => { const spy = vi.fn(); diff --git a/packages/core/test/interpreter.test.ts b/packages/core/test/interpreter.test.ts index 7c0646e44a..a98b79adac 100644 --- a/packages/core/test/interpreter.test.ts +++ b/packages/core/test/interpreter.test.ts @@ -61,49 +61,50 @@ describe('interpreter', () => { expect(service.getSnapshot().value).toEqual('foo'); }); - it('initially spawned actors should not be spawned when reading initial state', (done) => { - let promiseSpawned = 0; + it('initially spawned actors should not be spawned when reading initial state', () => + new Promise((resolve) => { + let promiseSpawned = 0; - const machine = createMachine({ - initial: 'idle', - context: { - actor: undefined! as ActorRefFrom> - }, - states: { - idle: { - entry: assign({ - actor: ({ spawn }) => { - return spawn( - fromPromise( - () => - new Promise(() => { - promiseSpawned++; - }) - ) - ); - } - }) + const machine = createMachine({ + initial: 'idle', + context: { + actor: undefined! as ActorRefFrom> + }, + states: { + idle: { + entry: assign({ + actor: ({ spawn }) => { + return spawn( + fromPromise( + () => + new Promise(() => { + promiseSpawned++; + }) + ) + ); + } + }) + } } - } - }); + }); - const service = createActor(machine); + const service = createActor(machine); - expect(promiseSpawned).toEqual(0); + expect(promiseSpawned).toEqual(0); - service.getSnapshot(); - service.getSnapshot(); - service.getSnapshot(); + service.getSnapshot(); + service.getSnapshot(); + service.getSnapshot(); - expect(promiseSpawned).toEqual(0); + expect(promiseSpawned).toEqual(0); - service.start(); + service.start(); - setTimeout(() => { - expect(promiseSpawned).toEqual(1); - done(); - }, 100); - }); + setTimeout(() => { + expect(promiseSpawned).toEqual(1); + resolve(); + }, 100); + })); it('does not execute actions from a restored state', () => { let called = false; @@ -365,78 +366,79 @@ describe('interpreter', () => { expect(stopped).toBe(true); }); - it('can send an event after a delay (delayed transitions)', (done) => { - const clock = new SimulatedClock(); - const letterMachine = createMachine( - { - types: {} as { - events: { type: 'FIRE_DELAY'; value: number }; - }, - id: 'letter', - context: { - delay: 100 - }, - initial: 'a', - states: { - a: { - after: { - delayA: 'b' - } + it('can send an event after a delay (delayed transitions)', () => + new Promise((resolve) => { + const clock = new SimulatedClock(); + const letterMachine = createMachine( + { + types: {} as { + events: { type: 'FIRE_DELAY'; value: number }; }, - b: { - after: { - someDelay: 'c' - } - }, - c: { - entry: raise({ type: 'FIRE_DELAY', value: 200 }, { delay: 20 }), - on: { - FIRE_DELAY: 'd' - } + id: 'letter', + context: { + delay: 100 }, - d: { - after: { - delayD: 'e' + initial: 'a', + states: { + a: { + after: { + delayA: 'b' + } + }, + b: { + after: { + someDelay: 'c' + } + }, + c: { + entry: raise({ type: 'FIRE_DELAY', value: 200 }, { delay: 20 }), + on: { + FIRE_DELAY: 'd' + } + }, + d: { + after: { + delayD: 'e' + } + }, + e: { + after: { someDelay: 'f' } + }, + f: { + type: 'final' } - }, - e: { - after: { someDelay: 'f' } - }, - f: { - type: 'final' + } + }, + { + delays: { + someDelay: ({ context }) => { + return context.delay + 50; + }, + delayA: ({ context }) => context.delay, + delayD: ({ context, event }) => context.delay + event.value } } - }, - { - delays: { - someDelay: ({ context }) => { - return context.delay + 50; - }, - delayA: ({ context }) => context.delay, - delayD: ({ context, event }) => context.delay + event.value - } - } - ); + ); - const actor = createActor(letterMachine, { clock }); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - - expect(actor.getSnapshot().value).toEqual('a'); - clock.increment(100); - expect(actor.getSnapshot().value).toEqual('b'); - clock.increment(100 + 50); - expect(actor.getSnapshot().value).toEqual('c'); - clock.increment(20); - expect(actor.getSnapshot().value).toEqual('d'); - clock.increment(100 + 200); - expect(actor.getSnapshot().value).toEqual('e'); - clock.increment(100 + 50); - }); + const actor = createActor(letterMachine, { clock }); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + + expect(actor.getSnapshot().value).toEqual('a'); + clock.increment(100); + expect(actor.getSnapshot().value).toEqual('b'); + clock.increment(100 + 50); + expect(actor.getSnapshot().value).toEqual('c'); + clock.increment(20); + expect(actor.getSnapshot().value).toEqual('d'); + clock.increment(100 + 200); + expect(actor.getSnapshot().value).toEqual('e'); + clock.increment(100 + 50); + })); }); describe('activities (deprecated)', () => { @@ -604,50 +606,51 @@ describe('interpreter', () => { expect(service.getSnapshot().value).toEqual('green'); }); - it('can cancel a delayed event using expression to resolve send id', (done) => { - const machine = createMachine({ - initial: 'first', - states: { - first: { - entry: [ - raise( - { type: 'FOO' }, - { - id: 'foo', - delay: 100 - } - ), - raise( - { type: 'BAR' }, - { - delay: 200 - } - ), - cancel(() => 'foo') - ], - on: { - FOO: 'fail', - BAR: 'pass' + it('can cancel a delayed event using expression to resolve send id', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'first', + states: { + first: { + entry: [ + raise( + { type: 'FOO' }, + { + id: 'foo', + delay: 100 + } + ), + raise( + { type: 'BAR' }, + { + delay: 200 + } + ), + cancel(() => 'foo') + ], + on: { + FOO: 'fail', + BAR: 'pass' + } + }, + fail: { + type: 'final' + }, + pass: { + type: 'final' } - }, - fail: { - type: 'final' - }, - pass: { - type: 'final' } - } - }); + }); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().value).toBe('pass'); - done(); - } - }); - }); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().value).toBe('pass'); + resolve(); + } + }); + })); it('should not throw an error if an event is sent to an uninitialized interpreter', () => { const actorRef = createActor(lightMachine); @@ -655,42 +658,43 @@ describe('interpreter', () => { expect(() => actorRef.send({ type: 'SOME_EVENT' })).not.toThrow(); }); - it('should defer events sent to an uninitialized service', (done) => { - const deferMachine = createMachine({ - id: 'defer', - initial: 'a', - states: { - a: { - on: { NEXT_A: 'b' } - }, - b: { - on: { NEXT_B: 'c' } - }, - c: { - type: 'final' + it('should defer events sent to an uninitialized service', () => + new Promise((resolve) => { + const deferMachine = createMachine({ + id: 'defer', + initial: 'a', + states: { + a: { + on: { NEXT_A: 'b' } + }, + b: { + on: { NEXT_B: 'c' } + }, + c: { + type: 'final' + } } - } - }); + }); - let state: any; - const deferService = createActor(deferMachine); + let state: any; + const deferService = createActor(deferMachine); - deferService.subscribe({ - next: (nextState) => { - state = nextState; - }, - complete: done - }); + deferService.subscribe({ + next: (nextState) => { + state = nextState; + }, + complete: resolve + }); - // uninitialized - deferService.send({ type: 'NEXT_A' }); - deferService.send({ type: 'NEXT_B' }); + // uninitialized + deferService.send({ type: 'NEXT_A' }); + deferService.send({ type: 'NEXT_B' }); - expect(state).not.toBeDefined(); + expect(state).not.toBeDefined(); - // initialized - deferService.start(); - }); + // initialized + deferService.start(); + })); it('should throw an error if initial state sent to interpreter is invalid', () => { const invalidMachine = { @@ -844,76 +848,78 @@ describe('interpreter', () => { } }); - it('should resolve send event expressions', (done) => { - const actor = createActor(machine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + it('should resolve send event expressions', () => + new Promise((resolve) => { + const actor = createActor(machine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); }); describe('sendParent() event expressions', () => { - it('should resolve sendParent event expressions', (done) => { - const childMachine = createMachine({ - types: {} as { - context: { password: string }; - input: { password: string }; - }, - id: 'child', - initial: 'start', - context: ({ input }) => ({ - password: input.password - }), - states: { - start: { - entry: sendParent(({ context }) => { - return { type: 'NEXT', password: context.password }; - }) + it('should resolve sendParent event expressions', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + context: { password: string }; + input: { password: string }; + }, + id: 'child', + initial: 'start', + context: ({ input }) => ({ + password: input.password + }), + states: { + start: { + entry: sendParent(({ context }) => { + return { type: 'NEXT', password: context.password }; + }) + } } - } - }); + }); - const parentMachine = createMachine({ - types: {} as { - events: { - type: 'NEXT'; - password: string; - }; - }, - id: 'parent', - initial: 'start', - states: { - start: { - invoke: { - id: 'child', - src: childMachine, - input: { password: 'foo' } - }, - on: { - NEXT: { - target: 'finish', - guard: ({ event }) => event.password === 'foo' + const parentMachine = createMachine({ + types: {} as { + events: { + type: 'NEXT'; + password: string; + }; + }, + id: 'parent', + initial: 'start', + states: { + start: { + invoke: { + id: 'child', + src: childMachine, + input: { password: 'foo' } + }, + on: { + NEXT: { + target: 'finish', + guard: ({ event }) => event.password === 'foo' + } } + }, + finish: { + type: 'final' } - }, - finish: { - type: 'final' } - } - }); + }); - const actor = createActor(parentMachine); - actor.subscribe({ - next: (state) => { - if (state.matches('start')) { - const childActor = state.children.child; + const actor = createActor(parentMachine); + actor.subscribe({ + next: (state) => { + if (state.matches('start')) { + const childActor = state.children.child; - expect(typeof childActor!.send).toBe('function'); - } - }, - complete: () => done() - }); - actor.start(); - }); + expect(typeof childActor!.send).toBe('function'); + } + }, + complete: () => resolve() + }); + actor.start(); + })); }); describe('.send()', () => { @@ -936,64 +942,68 @@ describe('interpreter', () => { } }); - it('can send events with a string', (done) => { - const service = createActor(sendMachine); - service.subscribe({ complete: () => done() }); - service.start(); - - service.send({ type: 'ACTIVATE' }); - }); - - it('can send events with an object', (done) => { - const service = createActor(sendMachine); - service.subscribe({ complete: () => done() }); - service.start(); - - service.send({ type: 'ACTIVATE' }); - }); - - it('can send events with an object with payload', (done) => { - const service = createActor(sendMachine); - service.subscribe({ complete: () => done() }); - service.start(); - - service.send({ type: 'EVENT', id: 42 }); - }); - - it('should receive and process all events sent simultaneously', (done) => { - const toggleMachine = createMachine({ - id: 'toggle', - initial: 'inactive', - states: { - fail: {}, - inactive: { - on: { - INACTIVATE: 'fail', - ACTIVATE: 'active' - } - }, - active: { - on: { - INACTIVATE: 'success' + it('can send events with a string', () => + new Promise((resolve) => { + const service = createActor(sendMachine); + service.subscribe({ complete: () => resolve() }); + service.start(); + + service.send({ type: 'ACTIVATE' }); + })); + + it('can send events with an object', () => + new Promise((resolve) => { + const service = createActor(sendMachine); + service.subscribe({ complete: () => resolve() }); + service.start(); + + service.send({ type: 'ACTIVATE' }); + })); + + it('can send events with an object with payload', () => + new Promise((resolve) => { + const service = createActor(sendMachine); + service.subscribe({ complete: () => resolve() }); + service.start(); + + service.send({ type: 'EVENT', id: 42 }); + })); + + it('should receive and process all events sent simultaneously', () => + new Promise((resolve) => { + const toggleMachine = createMachine({ + id: 'toggle', + initial: 'inactive', + states: { + fail: {}, + inactive: { + on: { + INACTIVATE: 'fail', + ACTIVATE: 'active' + } + }, + active: { + on: { + INACTIVATE: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const toggleService = createActor(toggleMachine); - toggleService.subscribe({ - complete: () => { - done(); - } - }); - toggleService.start(); + const toggleService = createActor(toggleMachine); + toggleService.subscribe({ + complete: () => { + resolve(); + } + }); + toggleService.start(); - toggleService.send({ type: 'ACTIVATE' }); - toggleService.send({ type: 'INACTIVATE' }); - }); + toggleService.send({ type: 'ACTIVATE' }); + toggleService.send({ type: 'INACTIVATE' }); + })); }); describe('.start()', () => { @@ -1093,64 +1103,66 @@ describe('interpreter', () => { }); describe('.stop()', () => { - it('should cancel delayed events', (done) => { - let called = false; - const delayedMachine = createMachine({ - id: 'delayed', - initial: 'foo', - states: { - foo: { - after: { - 50: { - target: 'bar', - actions: () => { - called = true; + it('should cancel delayed events', () => + new Promise((resolve) => { + let called = false; + const delayedMachine = createMachine({ + id: 'delayed', + initial: 'foo', + states: { + foo: { + after: { + 50: { + target: 'bar', + actions: () => { + called = true; + } } } - } - }, - bar: {} - } - }); + }, + bar: {} + } + }); - const delayedService = createActor(delayedMachine).start(); + const delayedService = createActor(delayedMachine).start(); - delayedService.stop(); + delayedService.stop(); - setTimeout(() => { - expect(called).toBe(false); - done(); - }, 60); - }); + setTimeout(() => { + expect(called).toBe(false); + resolve(); + }, 60); + })); - it('should not execute transitions after being stopped', (done) => { - let called = false; + it('should not execute transitions after being stopped', () => + new Promise((resolve) => { + let called = false; - const testMachine = createMachine({ - initial: 'waiting', - states: { - waiting: { - on: { - TRIGGER: 'active' - } - }, - active: { - entry: () => { - called = true; + const testMachine = createMachine({ + initial: 'waiting', + states: { + waiting: { + on: { + TRIGGER: 'active' + } + }, + active: { + entry: () => { + called = true; + } } } - } - }); + }); - const service = createActor(testMachine).start(); + const service = createActor(testMachine).start(); - service.stop(); + service.stop(); - service.send({ type: 'TRIGGER' }); + service.send({ type: 'TRIGGER' }); - setTimeout(() => { - expect(called).toBeFalsy(); - expect(console.warn).toMatchMockCallsInlineSnapshot(` + setTimeout(() => { + expect(called).toBeFalsy(); + expect(console.warn).toMatchMockCallsInlineSnapshot(` [ [ "Event "TRIGGER" was sent to stopped actor "x:43 (x:43)". This actor has already reached its final state, and will not transition. @@ -1158,9 +1170,9 @@ describe('interpreter', () => { ], ] `); - done(); - }, 10); - }); + resolve(); + }, 10); + })); it('stopping a not-started interpreter should not crash', () => { const service = createActor( @@ -1309,85 +1321,88 @@ describe('interpreter', () => { } }); - it('should be subscribable', (done) => { - let count: number; - const intervalService = createActor(intervalMachine).start(); + it('should be subscribable', () => + new Promise((resolve) => { + let count: number; + const intervalService = createActor(intervalMachine).start(); - expect(typeof intervalService.subscribe === 'function').toBeTruthy(); + expect(typeof intervalService.subscribe === 'function').toBeTruthy(); - intervalService.subscribe( - (state) => (count = state.context.count), - undefined, - () => { - expect(count).toEqual(5); - done(); - } - ); - }); + intervalService.subscribe( + (state) => (count = state.context.count), + undefined, + () => { + expect(count).toEqual(5); + resolve(); + } + ); + })); - it('should be interoperable with RxJS, etc. via Symbol.observable', (done) => { - let count = 0; - const intervalService = createActor(intervalMachine).start(); + it('should be interoperable with RxJS, etc. via Symbol.observable', () => + new Promise((resolve) => { + let count = 0; + const intervalService = createActor(intervalMachine).start(); - const state$ = from(intervalService); + const state$ = from(intervalService); - state$.subscribe({ - next: () => { - count += 1; - }, - error: undefined, - complete: () => { - expect(count).toEqual(5); - done(); - } - }); - }); - - it('should be unsubscribable', (done) => { - const countContext = { count: 0 }; - const machine = createMachine({ - types: {} as { context: typeof countContext }, - context: countContext, - initial: 'active', - states: { - active: { - always: { - target: 'finished', - guard: ({ context }) => context.count >= 5 - }, - on: { - INC: { - actions: assign({ count: ({ context }) => context.count + 1 }) + state$.subscribe({ + next: () => { + count += 1; + }, + error: undefined, + complete: () => { + expect(count).toEqual(5); + resolve(); + } + }); + })); + + it('should be unsubscribable', () => + new Promise((resolve) => { + const countContext = { count: 0 }; + const machine = createMachine({ + types: {} as { context: typeof countContext }, + context: countContext, + initial: 'active', + states: { + active: { + always: { + target: 'finished', + guard: ({ context }) => context.count >= 5 + }, + on: { + INC: { + actions: assign({ count: ({ context }) => context.count + 1 }) + } } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } - } - }); - - let count: number; - const service = createActor(machine); - service.subscribe({ - complete: () => { - expect(count).toEqual(2); - done(); - } - }); - service.start(); + }); + + let count: number; + const service = createActor(machine); + service.subscribe({ + complete: () => { + expect(count).toEqual(2); + resolve(); + } + }); + service.start(); - const subscription = service.subscribe( - (state) => (count = state.context.count) - ); + const subscription = service.subscribe( + (state) => (count = state.context.count) + ); - service.send({ type: 'INC' }); - service.send({ type: 'INC' }); - subscription.unsubscribe(); - service.send({ type: 'INC' }); - service.send({ type: 'INC' }); - service.send({ type: 'INC' }); - }); + service.send({ type: 'INC' }); + service.send({ type: 'INC' }); + subscription.unsubscribe(); + service.send({ type: 'INC' }); + service.send({ type: 'INC' }); + service.send({ type: 'INC' }); + })); it('should call complete() once a final state is reached', () => { const completeCb = vi.fn(); @@ -1506,132 +1521,134 @@ describe('interpreter', () => { expect(actor.getSnapshot().children).not.toHaveProperty('childActor'); }); - it('state.children should reference invoked child actors (promise)', (done) => { - const parentMachine = createMachine( - { - initial: 'active', - types: {} as { - actors: { - src: 'num'; - logic: PromiseActorLogic; - }; - }, - states: { - active: { - invoke: { - id: 'childActor', - src: 'num', - onDone: [ - { - target: 'success', - guard: ({ event }) => { - return event.output === 42; - } - }, - { target: 'failure' } - ] - } - }, - success: { - type: 'final' + it('state.children should reference invoked child actors (promise)', () => + new Promise((resolve) => { + const parentMachine = createMachine( + { + initial: 'active', + types: {} as { + actors: { + src: 'num'; + logic: PromiseActorLogic; + }; }, - failure: { - type: 'final' + states: { + active: { + invoke: { + id: 'childActor', + src: 'num', + onDone: [ + { + target: 'success', + guard: ({ event }) => { + return event.output === 42; + } + }, + { target: 'failure' } + ] + } + }, + success: { + type: 'final' + }, + failure: { + type: 'final' + } + } + }, + { + actors: { + num: fromPromise( + () => + new Promise((res) => { + setTimeout(() => { + res(42); + }, 100); + }) + ) } } - }, - { - actors: { - num: fromPromise( - () => - new Promise((res) => { - setTimeout(() => { - res(42); - }, 100); - }) - ) - } - } - ); - - const service = createActor(parentMachine); - - service.subscribe({ - next: (state) => { - if (state.matches('active')) { - const childActor = state.children.childActor; - - expect(childActor).toHaveProperty('send'); - } - }, - complete: () => { - expect(service.getSnapshot().matches('success')).toBeTruthy(); - expect(service.getSnapshot().children).not.toHaveProperty( - 'childActor' - ); - done(); - } - }); + ); - service.start(); - }); + const service = createActor(parentMachine); - it('state.children should reference invoked child actors (observable)', (done) => { - const interval$ = interval(10); - const intervalLogic = fromObservable(() => interval$); + service.subscribe({ + next: (state) => { + if (state.matches('active')) { + const childActor = state.children.childActor; - const parentMachine = createMachine( - { - types: {} as { - actors: { - src: 'intervalLogic'; - logic: typeof intervalLogic; - }; + expect(childActor).toHaveProperty('send'); + } }, - initial: 'active', - states: { - active: { - invoke: { - id: 'childActor', - src: 'intervalLogic', - onSnapshot: { - target: 'success', - guard: ({ event }) => { - return event.snapshot.context === 3; + complete: () => { + expect(service.getSnapshot().matches('success')).toBeTruthy(); + expect(service.getSnapshot().children).not.toHaveProperty( + 'childActor' + ); + resolve(); + } + }); + + service.start(); + })); + + it('state.children should reference invoked child actors (observable)', () => + new Promise((resolve) => { + const interval$ = interval(10); + const intervalLogic = fromObservable(() => interval$); + + const parentMachine = createMachine( + { + types: {} as { + actors: { + src: 'intervalLogic'; + logic: typeof intervalLogic; + }; + }, + initial: 'active', + states: { + active: { + invoke: { + id: 'childActor', + src: 'intervalLogic', + onSnapshot: { + target: 'success', + guard: ({ event }) => { + return event.snapshot.context === 3; + } } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' + } + }, + { + actors: { + intervalLogic } } - }, - { - actors: { - intervalLogic + ); + + const service = createActor(parentMachine); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().children).not.toHaveProperty( + 'childActor' + ); + resolve(); } - } - ); - - const service = createActor(parentMachine); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().children).not.toHaveProperty( - 'childActor' - ); - done(); - } - }); + }); - service.subscribe((state) => { - if (state.matches('active')) { - expect(state.children['childActor']).not.toBeUndefined(); - } - }); + service.subscribe((state) => { + if (state.matches('active')) { + expect(state.children['childActor']).not.toBeUndefined(); + } + }); - service.start(); - }); + service.start(); + })); it('state.children should reference spawned actors', () => { const childMachine = createMachine({ @@ -1767,26 +1784,27 @@ describe('interpreter', () => { expect(actor.getSnapshot()).toBe(initialState); }); - it('should call an onDone callback immediately if the service is already done', (done) => { - const machine = createMachine({ - initial: 'a', - states: { - a: { - type: 'final' + it('should call an onDone callback immediately if the service is already done', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + type: 'final' + } } - } - }); + }); - const service = createActor(machine).start(); + const service = createActor(machine).start(); - expect(service.getSnapshot().status).toBe('done'); + expect(service.getSnapshot().status).toBe('done'); - service.subscribe({ - complete: () => { - done(); - } - }); - }); + service.subscribe({ + complete: () => { + resolve(); + } + }); + })); }); it('should throw if an event is received', () => { diff --git a/packages/core/test/invoke.test.ts b/packages/core/test/invoke.test.ts index 959dfd32fb..bfea70f182 100644 --- a/packages/core/test/invoke.test.ts +++ b/packages/core/test/invoke.test.ts @@ -104,303 +104,311 @@ describe('invoke', () => { expect(actorRef.getSnapshot().context).toEqual({ count: -3 }); }); - it('should start services (explicit machine, invoke = config)', (done) => { - const childMachine = createMachine({ - id: 'fetch', - types: {} as { - context: { userId: string | undefined; user?: typeof user | undefined }; - events: { - type: 'RESOLVE'; - user: typeof user; - }; - input: { userId: string }; - }, - context: ({ input }) => ({ - userId: input.userId - }), - initial: 'pending', - states: { - pending: { - entry: raise({ type: 'RESOLVE', user }), - on: { - RESOLVE: { - target: 'success', - guard: ({ context }) => { - return context.userId !== undefined; + it('should start services (explicit machine, invoke = config)', () => + new Promise((resolve) => { + const childMachine = createMachine({ + id: 'fetch', + types: {} as { + context: { + userId: string | undefined; + user?: typeof user | undefined; + }; + events: { + type: 'RESOLVE'; + user: typeof user; + }; + input: { userId: string }; + }, + context: ({ input }) => ({ + userId: input.userId + }), + initial: 'pending', + states: { + pending: { + entry: raise({ type: 'RESOLVE', user }), + on: { + RESOLVE: { + target: 'success', + guard: ({ context }) => { + return context.userId !== undefined; + } } } + }, + success: { + type: 'final', + entry: assign({ + user: ({ event }) => event.user + }) + }, + failure: { + entry: sendParent({ type: 'REJECT' }) } }, - success: { - type: 'final', - entry: assign({ - user: ({ event }) => event.user - }) - }, - failure: { - entry: sendParent({ type: 'REJECT' }) - } - }, - output: ({ context }) => ({ user: context.user }) - }); + output: ({ context }) => ({ user: context.user }) + }); - const machine = createMachine({ - types: {} as { + const machine = createMachine({ + types: {} as { + context: { + selectedUserId: string; + user?: typeof user; + }; + }, + id: 'fetcher', + initial: 'idle', context: { - selectedUserId: string; - user?: typeof user; - }; - }, - id: 'fetcher', - initial: 'idle', - context: { - selectedUserId: '42', - user: undefined - }, - states: { - idle: { - on: { - GO_TO_WAITING: 'waiting' - } + selectedUserId: '42', + user: undefined }, - waiting: { - invoke: { - src: childMachine, - input: ({ context }: any) => ({ - userId: context.selectedUserId - }), - onDone: { - target: 'received', - guard: ({ event }) => { - // Should receive { user: { name: 'David' } } as event data - return (event.output as any).user.name === 'David'; + states: { + idle: { + on: { + GO_TO_WAITING: 'waiting' + } + }, + waiting: { + invoke: { + src: childMachine, + input: ({ context }: any) => ({ + userId: context.selectedUserId + }), + onDone: { + target: 'received', + guard: ({ event }) => { + // Should receive { user: { name: 'David' } } as event data + return (event.output as any).user.name === 'David'; + } } } + }, + received: { + type: 'final' } - }, - received: { - type: 'final' } - } - }); - - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - actor.send({ type: 'GO_TO_WAITING' }); - }); + }); - it('should start services (explicit machine, invoke = machine)', (done) => { - const childMachine = createMachine({ - types: {} as { - events: { type: 'RESOLVE' }; - input: { userId: string }; - }, - initial: 'pending', - states: { - pending: { - entry: raise({ type: 'RESOLVE' }), - on: { - RESOLVE: { - target: 'success' - } - } - }, - success: { - type: 'final' + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + resolve(); } - } - }); + }); + actor.start(); + actor.send({ type: 'GO_TO_WAITING' }); + })); - const machine = createMachine({ - initial: 'idle', - states: { - idle: { - on: { - GO_TO_WAITING: 'waiting' - } + it('should start services (explicit machine, invoke = machine)', () => + new Promise((resolve) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'RESOLVE' }; + input: { userId: string }; }, - waiting: { - invoke: { - src: childMachine, - onDone: 'received' + initial: 'pending', + states: { + pending: { + entry: raise({ type: 'RESOLVE' }), + on: { + RESOLVE: { + target: 'success' + } + } + }, + success: { + type: 'final' } - }, - received: { - type: 'final' } - } - }); - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - actor.send({ type: 'GO_TO_WAITING' }); - }); + }); - it('should start services (machine as invoke config)', (done) => { - const machineInvokeMachine = createMachine({ - types: {} as { - events: { - type: 'SUCCESS'; - data: number; - }; - }, - id: 'machine-invoke', - initial: 'pending', - states: { - pending: { - invoke: { - src: createMachine({ - id: 'child', - initial: 'sending', - states: { - sending: { - entry: sendParent({ type: 'SUCCESS', data: 42 }) - } - } - }) + const machine = createMachine({ + initial: 'idle', + states: { + idle: { + on: { + GO_TO_WAITING: 'waiting' + } }, - on: { - SUCCESS: { - target: 'success', - guard: ({ event }) => { - return event.data === 42; - } + waiting: { + invoke: { + src: childMachine, + onDone: 'received' } + }, + received: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - const actor = createActor(machineInvokeMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + }); + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + actor.send({ type: 'GO_TO_WAITING' }); + })); - it('should start deeply nested service (machine as invoke config)', (done) => { - const machineInvokeMachine = createMachine({ - types: {} as { - events: { - type: 'SUCCESS'; - data: number; - }; - }, - id: 'parent', - initial: 'a', - states: { - a: { - initial: 'b', - states: { - b: { - invoke: { - src: createMachine({ - id: 'child', - initial: 'sending', - states: { - sending: { - entry: sendParent({ type: 'SUCCESS', data: 42 }) - } + it('should start services (machine as invoke config)', () => + new Promise((resolve) => { + const machineInvokeMachine = createMachine({ + types: {} as { + events: { + type: 'SUCCESS'; + data: number; + }; + }, + id: 'machine-invoke', + initial: 'pending', + states: { + pending: { + invoke: { + src: createMachine({ + id: 'child', + initial: 'sending', + states: { + sending: { + entry: sendParent({ type: 'SUCCESS', data: 42 }) } - }) + } + }) + }, + on: { + SUCCESS: { + target: 'success', + guard: ({ event }) => { + return event.data === 42; + } } } - } - }, - success: { - id: 'success', - type: 'final' - } - }, - on: { - SUCCESS: { - target: '.success', - guard: ({ event }) => { - return event.data === 42; + }, + success: { + type: 'final' } } - } - }); - const actor = createActor(machineInvokeMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); - - it('should use the service overwritten by .provide(...)', (done) => { - const childMachine = createMachine({ - id: 'child', - initial: 'init', - states: { - init: {} - } - }); + }); + const actor = createActor(machineInvokeMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); - const someParentMachine = createMachine( - { - id: 'parent', + it('should start deeply nested service (machine as invoke config)', () => + new Promise((resolve) => { + const machineInvokeMachine = createMachine({ types: {} as { - context: { count: number }; - actors: { - src: 'child'; - id: 'someService'; - logic: typeof childMachine; + events: { + type: 'SUCCESS'; + data: number; }; }, - context: { count: 0 }, - initial: 'start', + id: 'parent', + initial: 'a', states: { - start: { - invoke: { - src: 'child', - id: 'someService' - }, - on: { - STOP: 'stop' + a: { + initial: 'b', + states: { + b: { + invoke: { + src: createMachine({ + id: 'child', + initial: 'sending', + states: { + sending: { + entry: sendParent({ type: 'SUCCESS', data: 42 }) + } + } + }) + } + } } }, - stop: { + success: { + id: 'success', type: 'final' } + }, + on: { + SUCCESS: { + target: '.success', + guard: ({ event }) => { + return event.data === 42; + } + } } - }, - { - actors: { - child: childMachine + }); + const actor = createActor(machineInvokeMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); + + it('should use the service overwritten by .provide(...)', () => + new Promise((resolve) => { + const childMachine = createMachine({ + id: 'child', + initial: 'init', + states: { + init: {} } - } - ); + }); - const actor = createActor( - someParentMachine.provide({ - actors: { - child: createMachine({ - id: 'child', - initial: 'init', - states: { - init: { - entry: [sendParent({ type: 'STOP' })] + const someParentMachine = createMachine( + { + id: 'parent', + types: {} as { + context: { count: number }; + actors: { + src: 'child'; + id: 'someService'; + logic: typeof childMachine; + }; + }, + context: { count: 0 }, + initial: 'start', + states: { + start: { + invoke: { + src: 'child', + id: 'someService' + }, + on: { + STOP: 'stop' } + }, + stop: { + type: 'final' } - }) + } + }, + { + actors: { + child: childMachine + } } - }) - ); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + ); + + const actor = createActor( + someParentMachine.provide({ + actors: { + child: createMachine({ + id: 'child', + initial: 'init', + states: { + init: { + entry: [sendParent({ type: 'STOP' })] + } + } + }) + } + }) + ); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); describe('parent to child', () => { const subMachine = createMachine({ @@ -416,61 +424,63 @@ describe('invoke', () => { } }); - it('should communicate with the child machine (invoke on machine)', (done) => { - const mainMachine = createMachine({ - id: 'parent', - initial: 'one', - invoke: { - id: 'foo-child', - src: subMachine - }, - states: { - one: { - entry: sendTo('foo-child', { type: 'NEXT' }), - on: { NEXT: 'two' } + it('should communicate with the child machine (invoke on machine)', () => + new Promise((resolve) => { + const mainMachine = createMachine({ + id: 'parent', + initial: 'one', + invoke: { + id: 'foo-child', + src: subMachine }, - two: { - type: 'final' + states: { + one: { + entry: sendTo('foo-child', { type: 'NEXT' }), + on: { NEXT: 'two' } + }, + two: { + type: 'final' + } } - } - }); + }); - const actor = createActor(mainMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actor = createActor(mainMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); - it('should communicate with the child machine (invoke on state)', (done) => { - const mainMachine = createMachine({ - id: 'parent', - initial: 'one', - states: { - one: { - invoke: { - id: 'foo-child', - src: subMachine + it('should communicate with the child machine (invoke on state)', () => + new Promise((resolve) => { + const mainMachine = createMachine({ + id: 'parent', + initial: 'one', + states: { + one: { + invoke: { + id: 'foo-child', + src: subMachine + }, + entry: sendTo('foo-child', { type: 'NEXT' }), + on: { NEXT: 'two' } }, - entry: sendTo('foo-child', { type: 'NEXT' }), - on: { NEXT: 'two' } - }, - two: { - type: 'final' + two: { + type: 'final' + } } - } - }); + }); - const actor = createActor(mainMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actor = createActor(mainMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); it('should transition correctly if child invocation causes it to directly go to final state', () => { const doneSubMachine = createMachine({ @@ -512,51 +522,52 @@ describe('invoke', () => { expect(actor.getSnapshot().value).toBe('two'); }); - it('should work with invocations defined in orthogonal state nodes', (done) => { - const pongMachine = createMachine({ - id: 'pong', - initial: 'active', - states: { - active: { - type: 'final' - } - }, - output: { secret: 'pingpong' } - }); + it('should work with invocations defined in orthogonal state nodes', () => + new Promise((resolve) => { + const pongMachine = createMachine({ + id: 'pong', + initial: 'active', + states: { + active: { + type: 'final' + } + }, + output: { secret: 'pingpong' } + }); - const pingMachine = createMachine({ - id: 'ping', - type: 'parallel', - states: { - one: { - initial: 'active', - states: { - active: { - invoke: { - id: 'pong', - src: pongMachine, - onDone: { - target: 'success', - guard: ({ event }) => event.output.secret === 'pingpong' + const pingMachine = createMachine({ + id: 'ping', + type: 'parallel', + states: { + one: { + initial: 'active', + states: { + active: { + invoke: { + id: 'pong', + src: pongMachine, + onDone: { + target: 'success', + guard: ({ event }) => event.output.secret === 'pingpong' + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } } } - } - }); + }); - const actor = createActor(pingMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actor = createActor(pingMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); it('should not reinvoke root-level invocations on root non-reentering transitions', () => { // https://github.com/statelyai/xstate/issues/2147 @@ -633,79 +644,80 @@ describe('invoke', () => { expect(actorStopped).toBe(true); }); - it('child should not invoke an actor when it transitions to an invoking state when it gets stopped by its parent', (done) => { - let invokeCount = 0; + it('child should not invoke an actor when it transitions to an invoking state when it gets stopped by its parent', () => + new Promise((resolve) => { + let invokeCount = 0; - const child = createMachine({ - id: 'child', - initial: 'idle', - states: { - idle: { - invoke: { - src: fromCallback(({ sendBack }) => { - invokeCount++; + const child = createMachine({ + id: 'child', + initial: 'idle', + states: { + idle: { + invoke: { + src: fromCallback(({ sendBack }) => { + invokeCount++; - if (invokeCount > 1) { - // prevent a potential infinite loop - throw new Error('This should be impossible.'); - } + if (invokeCount > 1) { + // prevent a potential infinite loop + throw new Error('This should be impossible.'); + } - // it's important for this test to send the event back when the parent is *not* currently processing an event - // this ensures that the parent can process the received event immediately and can stop the child immediately - setTimeout(() => sendBack({ type: 'STARTED' })); - }) - }, - on: { - STARTED: 'active' - } - }, - active: { - invoke: { - src: fromCallback(({ sendBack }) => { - sendBack({ type: 'STOPPED' }); - }) + // it's important for this test to send the event back when the parent is *not* currently processing an event + // this ensures that the parent can process the received event immediately and can stop the child immediately + setTimeout(() => sendBack({ type: 'STARTED' })); + }) + }, + on: { + STARTED: 'active' + } }, - on: { - STOPPED: { - target: 'idle', - actions: forwardTo(SpecialTargets.Parent) + active: { + invoke: { + src: fromCallback(({ sendBack }) => { + sendBack({ type: 'STOPPED' }); + }) + }, + on: { + STOPPED: { + target: 'idle', + actions: forwardTo(SpecialTargets.Parent) + } } } } - } - }); - const parent = createMachine({ - id: 'parent', - initial: 'idle', - states: { - idle: { - on: { - START: 'active' - } - }, - active: { - invoke: { src: child }, - on: { - STOPPED: 'done' + }); + const parent = createMachine({ + id: 'parent', + initial: 'idle', + states: { + idle: { + on: { + START: 'active' + } + }, + active: { + invoke: { src: child }, + on: { + STOPPED: 'done' + } + }, + done: { + type: 'final' } - }, - done: { - type: 'final' } - } - }); + }); - const service = createActor(parent); - service.subscribe({ - complete: () => { - expect(invokeCount).toBe(1); - done(); - } - }); - service.start(); + const service = createActor(parent); + service.subscribe({ + complete: () => { + expect(invokeCount).toBe(1); + resolve(); + } + }); + service.start(); - service.send({ type: 'START' }); - }); + service.send({ type: 'START' }); + })); }); type PromiseExecutor = ( @@ -782,146 +794,120 @@ describe('invoke', () => { } }); - it('should be invoked with a promise factory and resolve through onDone', (done) => { - const machine = createMachine({ - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise((resolve) => { - resolve(); - }) - ), - onDone: 'success' + it('should be invoked with a promise factory and resolve through onDone', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise((resolve) => { + resolve(); + }) + ), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - const service = createActor(machine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); - }); - - it('should be invoked with a promise factory and reject with ErrorExecution', (done) => { - const actor = createActor(invokePromiseMachine, { - input: { id: 31, succeed: false } - }); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + }); + const service = createActor(machine); + service.subscribe({ + complete: () => { + resolve(); + } + }); + service.start(); + })); - it('should be invoked with a promise factory and surface any unhandled errors', (done) => { - const promiseMachine = createMachine({ - id: 'invokePromise', - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise(() => { - throw new Error('test'); - }) - ), - onDone: 'success' + it('should be invoked with a promise factory and reject with ErrorExecution', () => + new Promise((resolve) => { + const actor = createActor(invokePromiseMachine, { + input: { id: 31, succeed: false } + }); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); + + it('should be invoked with a promise factory and surface any unhandled errors', () => + new Promise((resolve) => { + const promiseMachine = createMachine({ + id: 'invokePromise', + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise(() => { + throw new Error('test'); + }) + ), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const service = createActor(promiseMachine); - service.subscribe({ - error(err) { - expect((err as any).message).toEqual(expect.stringMatching(/test/)); - done(); - } - }); + const service = createActor(promiseMachine); + service.subscribe({ + error(err) { + expect((err as any).message).toEqual( + expect.stringMatching(/test/) + ); + resolve(); + } + }); - service.start(); - }); + service.start(); + })); - it('should be invoked with a promise factory and stop on unhandled onError target', (done) => { - const completeSpy = vi.fn(); + it('should be invoked with a promise factory and stop on unhandled onError target', () => + new Promise((resolve) => { + const completeSpy = vi.fn(); - const promiseMachine = createMachine({ - id: 'invokePromise', - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise(() => { - throw new Error('test'); - }) - ), - onDone: 'success' + const promiseMachine = createMachine({ + id: 'invokePromise', + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise(() => { + throw new Error('test'); + }) + ), + onDone: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - - const actor = createActor(promiseMachine); + }); - actor.subscribe({ - error: (err) => { - expect(err).toBeInstanceOf(Error); - expect((err as any).message).toBe('test'); - expect(completeSpy).not.toHaveBeenCalled(); - done(); - }, - complete: completeSpy - }); - actor.start(); - }); + const actor = createActor(promiseMachine); - it('should be invoked with a promise factory and resolve through onDone for compound state nodes', (done) => { - const promiseMachine = createMachine({ - id: 'promise', - initial: 'parent', - states: { - parent: { - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise((resolve) => resolve()) - ), - onDone: 'success' - } - }, - success: { - type: 'final' - } - }, - onDone: 'success' + actor.subscribe({ + error: (err) => { + expect(err).toBeInstanceOf(Error); + expect((err as any).message).toBe('test'); + expect(completeSpy).not.toHaveBeenCalled(); + resolve(); }, - success: { - type: 'final' - } - } - }); - const actor = createActor(promiseMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + complete: completeSpy + }); + actor.start(); + })); - it('should be invoked with a promise service and resolve through onDone for compound state nodes', (done) => { - const promiseMachine = createMachine( - { + it('should be invoked with a promise factory and resolve through onDone for compound state nodes', () => + new Promise((resolve) => { + const promiseMachine = createMachine({ id: 'promise', initial: 'parent', states: { @@ -930,7 +916,9 @@ describe('invoke', () => { states: { pending: { invoke: { - src: 'somePromise', + src: fromPromise(() => + createPromise((resolve) => resolve()) + ), onDone: 'success' } }, @@ -944,151 +932,69 @@ describe('invoke', () => { type: 'final' } } - }, - { - actors: { - somePromise: fromPromise(() => - createPromise((resolve) => resolve()) - ) - } - } - ); - const actor = createActor(promiseMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); - it('should assign the resolved data when invoked with a promise factory', (done) => { - const promiseMachine = createMachine({ - types: {} as { context: { count: number } }, - id: 'promise', - context: { count: 0 }, - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise((resolve) => resolve({ count: 1 })) - ), - onDone: { - target: 'success', - actions: assign({ - count: ({ event }) => event.output.count - }) + }); + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); + + it('should be invoked with a promise service and resolve through onDone for compound state nodes', () => + new Promise((resolve) => { + const promiseMachine = createMachine( + { + id: 'promise', + initial: 'parent', + states: { + parent: { + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: 'success' + } + }, + success: { + type: 'final' + } + }, + onDone: 'success' + }, + success: { + type: 'final' } } }, - success: { - type: 'final' - } - } - }); - - const actor = createActor(promiseMachine); - actor.subscribe({ - complete: () => { - expect(actor.getSnapshot().context.count).toEqual(1); - done(); - } - }); - actor.start(); - }); - - it('should assign the resolved data when invoked with a promise service', (done) => { - const promiseMachine = createMachine( - { - types: {} as { context: { count: number } }, - id: 'promise', - context: { count: 0 }, - initial: 'pending', - states: { - pending: { - invoke: { - src: 'somePromise', - onDone: { - target: 'success', - actions: assign({ - count: ({ event }) => event.output.count - }) - } - } - }, - success: { - type: 'final' - } - } - }, - { - actors: { - somePromise: fromPromise(() => - createPromise((resolve) => resolve({ count: 1 })) - ) - } - } - ); - - const actor = createActor(promiseMachine); - actor.subscribe({ - complete: () => { - expect(actor.getSnapshot().context.count).toEqual(1); - done(); - } - }); - actor.start(); - }); - - it('should provide the resolved data when invoked with a promise factory', (done) => { - let count = 0; - - const promiseMachine = createMachine({ - id: 'promise', - context: { count: 0 }, - initial: 'pending', - states: { - pending: { - invoke: { - src: fromPromise(() => - createPromise((resolve) => resolve({ count: 1 })) - ), - onDone: { - target: 'success', - actions: ({ event }) => { - count = (event.output as any).count; - } - } + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve()) + ) } - }, - success: { - type: 'final' } - } - }); - - const actor = createActor(promiseMachine); - actor.subscribe({ - complete: () => { - expect(count).toEqual(1); - done(); - } - }); - actor.start(); - }); - - it('should provide the resolved data when invoked with a promise service', (done) => { - let count = 0; - - const promiseMachine = createMachine( - { + ); + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); + it('should assign the resolved data when invoked with a promise factory', () => + new Promise((resolve) => { + const promiseMachine = createMachine({ + types: {} as { context: { count: number } }, id: 'promise', + context: { count: 0 }, initial: 'pending', states: { pending: { invoke: { - src: 'somePromise', + src: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), onDone: { target: 'success', - actions: ({ event }) => { - count = event.output.count; - } + actions: assign({ + count: ({ event }) => event.output.count + }) } } }, @@ -1096,321 +1002,449 @@ describe('invoke', () => { type: 'final' } } - }, - { - actors: { - somePromise: fromPromise(() => - createPromise((resolve) => resolve({ count: 1 })) - ) - } - } - ); + }); - const actor = createActor(promiseMachine); - actor.subscribe({ - complete: () => { - expect(count).toEqual(1); - done(); - } - }); - actor.start(); - }); + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(actor.getSnapshot().context.count).toEqual(1); + resolve(); + } + }); + actor.start(); + })); + + it('should assign the resolved data when invoked with a promise service', () => + new Promise((resolve) => { + const promiseMachine = createMachine( + { + types: {} as { context: { count: number } }, + id: 'promise', + context: { count: 0 }, + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: { + target: 'success', + actions: assign({ + count: ({ event }) => event.output.count + }) + } + } + }, + success: { + type: 'final' + } + } + }, + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ) + } + } + ); - it('should be able to specify a Promise as a service', (done) => { - interface BeginEvent { - type: 'BEGIN'; - payload: boolean; - } + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(actor.getSnapshot().context.count).toEqual(1); + resolve(); + } + }); + actor.start(); + })); - const promiseActor = fromPromise( - ({ input }: { input: { foo: boolean; event: { payload: any } } }) => { - return createPromise((resolve, reject) => { - input.foo && input.event.payload ? resolve() : reject(); - }); - } - ); + it('should provide the resolved data when invoked with a promise factory', () => + new Promise((resolve) => { + let count = 0; - const promiseMachine = createMachine( - { + const promiseMachine = createMachine({ id: 'promise', - types: {} as { - context: { foo: boolean }; - events: BeginEvent; - actors: { - src: 'somePromise'; - logic: typeof promiseActor; - }; - }, + context: { count: 0 }, initial: 'pending', - context: { - foo: true - }, states: { pending: { - on: { - BEGIN: 'first' - } - }, - first: { invoke: { - src: 'somePromise', - input: ({ context, event }) => ({ - foo: context.foo, - event: event - }), - onDone: 'last' + src: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), + onDone: { + target: 'success', + actions: ({ event }) => { + count = (event.output as any).count; + } + } } }, - last: { + success: { type: 'final' } } - }, - { - actors: { - somePromise: promiseActor + }); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(count).toEqual(1); + resolve(); } - } - ); + }); + actor.start(); + })); - const actor = createActor(promiseMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - actor.send({ - type: 'BEGIN', - payload: true - }); - }); + it('should provide the resolved data when invoked with a promise service', () => + new Promise((resolve) => { + let count = 0; - it('should be able to reuse the same promise logic multiple times and create unique promise for each created actor', (done) => { - const machine = createMachine( - { - types: {} as { - context: { - result1: number | null; - result2: number | null; - }; - actors: { - src: 'getRandomNumber'; - logic: PromiseActorLogic<{ result: number }>; - }; + const promiseMachine = createMachine( + { + id: 'promise', + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: { + target: 'success', + actions: ({ event }) => { + count = event.output.count; + } + } + } + }, + success: { + type: 'final' + } + } }, - context: { - result1: null, - result2: null + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ) + } + } + ); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(count).toEqual(1); + resolve(); + } + }); + actor.start(); + })); + + it('should be able to specify a Promise as a service', () => + new Promise((resolve) => { + interface BeginEvent { + type: 'BEGIN'; + payload: boolean; + } + + const promiseActor = fromPromise( + ({ + input + }: { + input: { foo: boolean; event: { payload: any } }; + }) => { + return createPromise((resolve, reject) => { + input.foo && input.event.payload ? resolve() : reject(); + }); + } + ); + + const promiseMachine = createMachine( + { + id: 'promise', + types: {} as { + context: { foo: boolean }; + events: BeginEvent; + actors: { + src: 'somePromise'; + logic: typeof promiseActor; + }; + }, + initial: 'pending', + context: { + foo: true + }, + states: { + pending: { + on: { + BEGIN: 'first' + } + }, + first: { + invoke: { + src: 'somePromise', + input: ({ context, event }) => ({ + foo: context.foo, + event: event + }), + onDone: 'last' + } + }, + last: { + type: 'final' + } + } }, - initial: 'pending', - states: { - pending: { - type: 'parallel', - states: { - state1: { - initial: 'active', - states: { - active: { - invoke: { - src: 'getRandomNumber', - onDone: { - target: 'success', - // TODO: we get DoneInvokeEvent here, this gets fixed with https://github.com/microsoft/TypeScript/pull/48838 - actions: assign(({ event }) => ({ - result1: event.output.result - })) + { + actors: { + somePromise: promiseActor + } + } + ); + + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + actor.send({ + type: 'BEGIN', + payload: true + }); + })); + + it('should be able to reuse the same promise logic multiple times and create unique promise for each created actor', () => + new Promise((resolve) => { + const machine = createMachine( + { + types: {} as { + context: { + result1: number | null; + result2: number | null; + }; + actors: { + src: 'getRandomNumber'; + logic: PromiseActorLogic<{ result: number }>; + }; + }, + context: { + result1: null, + result2: null + }, + initial: 'pending', + states: { + pending: { + type: 'parallel', + states: { + state1: { + initial: 'active', + states: { + active: { + invoke: { + src: 'getRandomNumber', + onDone: { + target: 'success', + // TODO: we get DoneInvokeEvent here, this gets fixed with https://github.com/microsoft/TypeScript/pull/48838 + actions: assign(({ event }) => ({ + result1: event.output.result + })) + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }, - state2: { - initial: 'active', - states: { - active: { - invoke: { - src: 'getRandomNumber', - onDone: { - target: 'success', - actions: assign(({ event }) => ({ - result2: event.output.result - })) + }, + state2: { + initial: 'active', + states: { + active: { + invoke: { + src: 'getRandomNumber', + onDone: { + target: 'success', + actions: assign(({ event }) => ({ + result2: event.output.result + })) + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } } - } + }, + onDone: 'done' }, - onDone: 'done' - }, - done: { - type: 'final' + done: { + type: 'final' + } + } + }, + { + actors: { + // it's important for this actor to be reused, this test shouldn't use a factory or anything like that + getRandomNumber: fromPromise(() => { + return createPromise((resolve) => + resolve({ result: Math.random() }) + ); + }) } } - }, - { - actors: { - // it's important for this actor to be reused, this test shouldn't use a factory or anything like that - getRandomNumber: fromPromise(() => { - return createPromise((resolve) => - resolve({ result: Math.random() }) - ); - }) - } - } - ); + ); - const service = createActor(machine); - service.subscribe({ - complete: () => { - const snapshot = service.getSnapshot(); - expect(typeof snapshot.context.result1).toBe('number'); - expect(typeof snapshot.context.result2).toBe('number'); - expect(snapshot.context.result1).not.toBe(snapshot.context.result2); - done(); - } - }); - service.start(); - }); + const service = createActor(machine); + service.subscribe({ + complete: () => { + const snapshot = service.getSnapshot(); + expect(typeof snapshot.context.result1).toBe('number'); + expect(typeof snapshot.context.result2).toBe('number'); + expect(snapshot.context.result1).not.toBe( + snapshot.context.result2 + ); + resolve(); + } + }); + service.start(); + })); - it('should not emit onSnapshot if stopped', (done) => { - const machine = createMachine({ - initial: 'active', - states: { - active: { - invoke: { - src: fromPromise(() => - createPromise((res) => { - setTimeout(() => res(42), 5); - }) - ), - onSnapshot: {} + it('should not emit onSnapshot if stopped', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'active', + states: { + active: { + invoke: { + src: fromPromise(() => + createPromise((res) => { + setTimeout(() => res(42), 5); + }) + ), + onSnapshot: {} + }, + on: { + deactivate: 'inactive' + } }, - on: { - deactivate: 'inactive' - } - }, - inactive: { - on: { - '*': { - actions: ({ event }) => { - if (event.snapshot) { - throw new Error( - `Received unexpected event: ${event.type}` - ); + inactive: { + on: { + '*': { + actions: ({ event }) => { + if (event.snapshot) { + throw new Error( + `Received unexpected event: ${event.type}` + ); + } } } } } } - } - }); + }); - const actor = createActor(machine).start(); - actor.send({ type: 'deactivate' }); + const actor = createActor(machine).start(); + actor.send({ type: 'deactivate' }); - setTimeout(() => { - done(); - }, 10); - }); + setTimeout(() => { + resolve(); + }, 10); + })); }); }); describe('with callbacks', () => { - it('should be able to specify a callback as a service', (done) => { - interface BeginEvent { - type: 'BEGIN'; - payload: boolean; - } - interface CallbackEvent { - type: 'CALLBACK'; - data: number; - } - - const someCallback = fromCallback( - ({ - sendBack, - input - }: { - sendBack: (event: BeginEvent | CallbackEvent) => void; - input: { foo: boolean; event: BeginEvent | CallbackEvent }; - }) => { - if (input.foo && input.event.type === 'BEGIN') { - sendBack({ - type: 'CALLBACK', - data: 40 - }); - sendBack({ - type: 'CALLBACK', - data: 41 - }); - sendBack({ - type: 'CALLBACK', - data: 42 - }); - } + it('should be able to specify a callback as a service', () => + new Promise((resolve) => { + interface BeginEvent { + type: 'BEGIN'; + payload: boolean; + } + interface CallbackEvent { + type: 'CALLBACK'; + data: number; } - ); - const callbackMachine = createMachine( - { - id: 'callback', - types: {} as { - context: { foo: boolean }; - events: BeginEvent | CallbackEvent; - actors: { - src: 'someCallback'; - logic: typeof someCallback; - }; - }, - initial: 'pending', - context: { - foo: true - }, - states: { - pending: { - on: { - BEGIN: 'first' - } + const someCallback = fromCallback( + ({ + sendBack, + input + }: { + sendBack: (event: BeginEvent | CallbackEvent) => void; + input: { foo: boolean; event: BeginEvent | CallbackEvent }; + }) => { + if (input.foo && input.event.type === 'BEGIN') { + sendBack({ + type: 'CALLBACK', + data: 40 + }); + sendBack({ + type: 'CALLBACK', + data: 41 + }); + sendBack({ + type: 'CALLBACK', + data: 42 + }); + } + } + ); + + const callbackMachine = createMachine( + { + id: 'callback', + types: {} as { + context: { foo: boolean }; + events: BeginEvent | CallbackEvent; + actors: { + src: 'someCallback'; + logic: typeof someCallback; + }; }, - first: { - invoke: { - src: 'someCallback', - input: ({ context, event }) => ({ - foo: context.foo, - event: event - }) + initial: 'pending', + context: { + foo: true + }, + states: { + pending: { + on: { + BEGIN: 'first' + } }, - on: { - CALLBACK: { - target: 'last', - guard: ({ event }) => event.data === 42 + first: { + invoke: { + src: 'someCallback', + input: ({ context, event }) => ({ + foo: context.foo, + event: event + }) + }, + on: { + CALLBACK: { + target: 'last', + guard: ({ event }) => event.data === 42 + } } + }, + last: { + type: 'final' } - }, - last: { - type: 'final' + } + }, + { + actors: { + someCallback } } - }, - { - actors: { - someCallback - } - } - ); + ); - const actor = createActor(callbackMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - actor.send({ - type: 'BEGIN', - payload: true - }); - }); + const actor = createActor(callbackMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + actor.send({ + type: 'BEGIN', + payload: true + }); + })); it('should transition correctly if callback function sends an event', () => { const callbackMachine = createMachine( @@ -1545,45 +1579,46 @@ describe('invoke', () => { } }); - it('should treat a callback source as an event stream', (done) => { - const intervalMachine = createMachine({ - types: {} as { context: { count: number } }, - id: 'interval', - initial: 'counting', - context: { - count: 0 - }, - states: { - counting: { - invoke: { - id: 'intervalService', - src: fromCallback(({ sendBack }) => { - const ivl = setInterval(() => { - sendBack({ type: 'INC' }); - }, 10); + it('should treat a callback source as an event stream', () => + new Promise((resolve) => { + const intervalMachine = createMachine({ + types: {} as { context: { count: number } }, + id: 'interval', + initial: 'counting', + context: { + count: 0 + }, + states: { + counting: { + invoke: { + id: 'intervalService', + src: fromCallback(({ sendBack }) => { + const ivl = setInterval(() => { + sendBack({ type: 'INC' }); + }, 10); - return () => clearInterval(ivl); - }) - }, - always: { - target: 'finished', - guard: ({ context }) => context.count === 3 - }, - on: { - INC: { - actions: assign({ count: ({ context }) => context.count + 1 }) + return () => clearInterval(ivl); + }) + }, + always: { + target: 'finished', + guard: ({ context }) => context.count === 3 + }, + on: { + INC: { + actions: assign({ count: ({ context }) => context.count + 1 }) + } } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } - } - }); - const actor = createActor(intervalMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + }); + const actor = createActor(intervalMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); it('should dispose of the callback (if disposal function provided)', () => { const spy = vi.fn(); @@ -1610,67 +1645,69 @@ describe('invoke', () => { expect(spy).toHaveBeenCalled(); }); - it('callback should be able to receive messages from parent', (done) => { - const pingPongMachine = createMachine({ - id: 'ping-pong', - initial: 'active', - states: { - active: { - invoke: { - id: 'child', - src: fromCallback(({ sendBack, receive }) => { - receive((e) => { - if (e.type === 'PING') { - sendBack({ type: 'PONG' }); - } - }); - }) + it('callback should be able to receive messages from parent', () => + new Promise((resolve) => { + const pingPongMachine = createMachine({ + id: 'ping-pong', + initial: 'active', + states: { + active: { + invoke: { + id: 'child', + src: fromCallback(({ sendBack, receive }) => { + receive((e) => { + if (e.type === 'PING') { + sendBack({ type: 'PONG' }); + } + }); + }) + }, + entry: sendTo('child', { type: 'PING' }), + on: { + PONG: 'done' + } }, - entry: sendTo('child', { type: 'PING' }), - on: { - PONG: 'done' + done: { + type: 'final' } - }, - done: { - type: 'final' } - } - }); - const actor = createActor(pingPongMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + }); + const actor = createActor(pingPongMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); - it('should call onError upon error (sync)', (done) => { - const errorMachine = createMachine({ - id: 'error', - initial: 'safe', - states: { - safe: { - invoke: { - src: fromCallback(() => { - throw new Error('test'); - }), - onError: { - target: 'failed', - guard: ({ event }) => { - return ( - event.error instanceof Error && - event.error.message === 'test' - ); + it('should call onError upon error (sync)', () => + new Promise((resolve) => { + const errorMachine = createMachine({ + id: 'error', + initial: 'safe', + states: { + safe: { + invoke: { + src: fromCallback(() => { + throw new Error('test'); + }), + onError: { + target: 'failed', + guard: ({ event }) => { + return ( + event.error instanceof Error && + event.error.message === 'test' + ); + } } } + }, + failed: { + type: 'final' } - }, - failed: { - type: 'final' } - } - }); - const actor = createActor(errorMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + }); + const actor = createActor(errorMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); it('should transition correctly upon error (sync)', () => { const errorMachine = createMachine({ @@ -1811,28 +1848,29 @@ describe('invoke', () => { `); }); - it('should work with input', (done) => { - const machine = createMachine({ - types: {} as { - context: { foo: string }; - }, - initial: 'start', - context: { foo: 'bar' }, - states: { - start: { - invoke: { - src: fromCallback(({ input }) => { - expect(input).toEqual({ foo: 'bar' }); - done(); - }), - input: ({ context }: any) => context + it('should work with input', () => + new Promise((resolve) => { + const machine = createMachine({ + types: {} as { + context: { foo: string }; + }, + initial: 'start', + context: { foo: 'bar' }, + states: { + start: { + invoke: { + src: fromCallback(({ input }) => { + expect(input).toEqual({ foo: 'bar' }); + resolve(); + }), + input: ({ context }: any) => context + } } } - } - }); + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); it('sub invoke race condition ends on the completed state', () => { const anotherChildMachine = createMachine({ @@ -1853,605 +1891,628 @@ describe('invoke', () => { initial: 'begin', states: { begin: { - invoke: { - src: anotherChildMachine, - id: 'invoked.child', - onDone: 'completed' - }, - on: { - STOPCHILD: { - actions: sendTo('invoked.child', { type: 'STOP' }) - } - } - }, - completed: { - type: 'final' - } - } - }); - - const actorRef = createActor(anotherParentMachine).start(); - actorRef.send({ type: 'STOPCHILD' }); - - expect(actorRef.getSnapshot().value).toEqual('completed'); - }); - }); - - describe('with observables', () => { - it('should work with an infinite observable', (done) => { - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: { count: number | undefined }; events: Events }, - id: 'infiniteObs', - initial: 'counting', - context: { count: undefined }, - states: { - counting: { - invoke: { - src: fromObservable(() => interval(10)), - onSnapshot: { - actions: assign({ - count: ({ event }) => event.snapshot.context - }) - } - }, - always: { - target: 'counted', - guard: ({ context }) => context.count === 5 - } - }, - counted: { - type: 'final' - } - } - }); - - const service = createActor(obsMachine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); - }); - - it('should work with a finite observable', (done) => { - interface Ctx { - count: number | undefined; - } - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: Ctx; events: Events }, - id: 'obs', - initial: 'counting', - context: { - count: undefined - }, - states: { - counting: { - invoke: { - src: fromObservable(() => interval(10).pipe(take(5))), - onSnapshot: { - actions: assign({ - count: ({ event }) => event.snapshot.context - }) - }, - onDone: { - target: 'counted', - guard: ({ context }) => context.count === 4 - } - } - }, - counted: { - type: 'final' - } - } - }); - - const actor = createActor(obsMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); - - it('should receive an emitted error', (done) => { - interface Ctx { - count: number | undefined; - } - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: Ctx; events: Events }, - id: 'obs', - initial: 'counting', - context: { count: undefined }, - states: { - counting: { - invoke: { - src: fromObservable(() => - interval(10).pipe( - map((value) => { - if (value === 5) { - throw new Error('some error'); - } - - return value; - }) - ) - ), - onSnapshot: { - actions: assign({ - count: ({ event }) => event.snapshot.context - }) - }, - onError: { - target: 'success', - guard: ({ context, event }) => { - expect((event.error as any).message).toEqual('some error'); - return ( - context.count === 4 && - (event.error as any).message === 'some error' - ); - } + invoke: { + src: anotherChildMachine, + id: 'invoked.child', + onDone: 'completed' + }, + on: { + STOPCHILD: { + actions: sendTo('invoked.child', { type: 'STOP' }) } } }, - success: { + completed: { type: 'final' } } }); - const actor = createActor(obsMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actorRef = createActor(anotherParentMachine).start(); + actorRef.send({ type: 'STOPCHILD' }); - it('should work with input', (done) => { - const childLogic = fromObservable(({ input }: { input: number }) => - of(input) - ); + expect(actorRef.getSnapshot().value).toEqual('completed'); + }); + }); - const machine = createMachine( - { + describe('with observables', () => { + it('should work with an infinite observable', () => + new Promise((resolve) => { + interface Events { + type: 'COUNT'; + value: number; + } + const obsMachine = createMachine({ types: {} as { - actors: { - src: 'childLogic'; - logic: typeof childLogic; - }; + context: { count: number | undefined }; + events: Events; }, - context: { received: undefined }, - invoke: { - src: 'childLogic', - input: 42, - onSnapshot: { - actions: ({ event }) => { - if ( - event.snapshot.status === 'active' && - event.snapshot.context === 42 - ) { - done(); + id: 'infiniteObs', + initial: 'counting', + context: { count: undefined }, + states: { + counting: { + invoke: { + src: fromObservable(() => interval(10)), + onSnapshot: { + actions: assign({ + count: ({ event }) => event.snapshot.context + }) } + }, + always: { + target: 'counted', + guard: ({ context }) => context.count === 5 } + }, + counted: { + type: 'final' } } - }, - { - actors: { - childLogic + }); + + const service = createActor(obsMachine); + service.subscribe({ + complete: () => { + resolve(); } + }); + service.start(); + })); + + it('should work with a finite observable', () => + new Promise((resolve) => { + interface Ctx { + count: number | undefined; } - ); + interface Events { + type: 'COUNT'; + value: number; + } + const obsMachine = createMachine({ + types: {} as { context: Ctx; events: Events }, + id: 'obs', + initial: 'counting', + context: { + count: undefined + }, + states: { + counting: { + invoke: { + src: fromObservable(() => interval(10).pipe(take(5))), + onSnapshot: { + actions: assign({ + count: ({ event }) => event.snapshot.context + }) + }, + onDone: { + target: 'counted', + guard: ({ context }) => context.count === 4 + } + } + }, + counted: { + type: 'final' + } + } + }); - createActor(machine).start(); - }); - }); + const actor = createActor(obsMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); + + it('should receive an emitted error', () => + new Promise((resolve) => { + interface Ctx { + count: number | undefined; + } + interface Events { + type: 'COUNT'; + value: number; + } + const obsMachine = createMachine({ + types: {} as { context: Ctx; events: Events }, + id: 'obs', + initial: 'counting', + context: { count: undefined }, + states: { + counting: { + invoke: { + src: fromObservable(() => + interval(10).pipe( + map((value) => { + if (value === 5) { + throw new Error('some error'); + } - describe('with event observables', () => { - it('should work with an infinite event observable', (done) => { - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: { count: number | undefined }; events: Events }, - id: 'obs', - initial: 'counting', - context: { count: undefined }, - states: { - counting: { - invoke: { - src: fromEventObservable(() => - interval(10).pipe(map((value) => ({ type: 'COUNT', value }))) - ) - }, - on: { - COUNT: { - actions: assign({ count: ({ event }) => event.value }) + return value; + }) + ) + ), + onSnapshot: { + actions: assign({ + count: ({ event }) => event.snapshot.context + }) + }, + onError: { + target: 'success', + guard: ({ context, event }) => { + expect((event.error as any).message).toEqual('some error'); + return ( + context.count === 4 && + (event.error as any).message === 'some error' + ); + } + } } }, - always: { - target: 'counted', - guard: ({ context }) => context.count === 5 + success: { + type: 'final' } - }, - counted: { - type: 'final' } - } - }); + }); - const service = createActor(obsMachine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); - }); + const actor = createActor(obsMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); - it('should work with a finite event observable', (done) => { - interface Ctx { - count: number | undefined; - } - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: Ctx; events: Events }, - id: 'obs', - initial: 'counting', - context: { - count: undefined - }, - states: { - counting: { - invoke: { - src: fromEventObservable(() => - interval(10).pipe( - take(5), - map((value) => ({ type: 'COUNT', value })) - ) - ), - onDone: { - target: 'counted', - guard: ({ context }) => context.count === 4 - } + it('should work with input', () => + new Promise((resolve) => { + const childLogic = fromObservable(({ input }: { input: number }) => + of(input) + ); + + const machine = createMachine( + { + types: {} as { + actors: { + src: 'childLogic'; + logic: typeof childLogic; + }; }, - on: { - COUNT: { - actions: assign({ - count: ({ event }) => event.value - }) + context: { received: undefined }, + invoke: { + src: 'childLogic', + input: 42, + onSnapshot: { + actions: ({ event }) => { + if ( + event.snapshot.status === 'active' && + event.snapshot.context === 42 + ) { + resolve(); + } + } } } }, - counted: { - type: 'final' + { + actors: { + childLogic + } } - } - }); + ); - const actor = createActor(obsMachine); - actor.subscribe({ - complete: () => { - done(); + createActor(machine).start(); + })); + }); + + describe('with event observables', () => { + it('should work with an infinite event observable', () => + new Promise((resolve) => { + interface Events { + type: 'COUNT'; + value: number; } - }); - actor.start(); - }); + const obsMachine = createMachine({ + types: {} as { + context: { count: number | undefined }; + events: Events; + }, + id: 'obs', + initial: 'counting', + context: { count: undefined }, + states: { + counting: { + invoke: { + src: fromEventObservable(() => + interval(10).pipe(map((value) => ({ type: 'COUNT', value }))) + ) + }, + on: { + COUNT: { + actions: assign({ count: ({ event }) => event.value }) + } + }, + always: { + target: 'counted', + guard: ({ context }) => context.count === 5 + } + }, + counted: { + type: 'final' + } + } + }); - it('should receive an emitted error', (done) => { - interface Ctx { - count: number | undefined; - } - interface Events { - type: 'COUNT'; - value: number; - } - const obsMachine = createMachine({ - types: {} as { context: Ctx; events: Events }, - id: 'obs', - initial: 'counting', - context: { count: undefined }, - states: { - counting: { - invoke: { - src: fromEventObservable(() => - interval(10).pipe( - map((value) => { - if (value === 5) { - throw new Error('some error'); - } + const service = createActor(obsMachine); + service.subscribe({ + complete: () => { + resolve(); + } + }); + service.start(); + })); - return { type: 'COUNT', value }; + it('should work with a finite event observable', () => + new Promise((resolve) => { + interface Ctx { + count: number | undefined; + } + interface Events { + type: 'COUNT'; + value: number; + } + const obsMachine = createMachine({ + types: {} as { context: Ctx; events: Events }, + id: 'obs', + initial: 'counting', + context: { + count: undefined + }, + states: { + counting: { + invoke: { + src: fromEventObservable(() => + interval(10).pipe( + take(5), + map((value) => ({ type: 'COUNT', value })) + ) + ), + onDone: { + target: 'counted', + guard: ({ context }) => context.count === 4 + } + }, + on: { + COUNT: { + actions: assign({ + count: ({ event }) => event.value }) - ) - ), - onError: { - target: 'success', - guard: ({ context, event }) => { - expect((event.error as any).message).toEqual('some error'); - return ( - context.count === 4 && - (event.error as any).message === 'some error' - ); } } }, - on: { - COUNT: { - actions: assign({ count: ({ event }) => event.value }) + counted: { + type: 'final' + } + } + }); + + const actor = createActor(obsMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); + + it('should receive an emitted error', () => + new Promise((resolve) => { + interface Ctx { + count: number | undefined; + } + interface Events { + type: 'COUNT'; + value: number; + } + const obsMachine = createMachine({ + types: {} as { context: Ctx; events: Events }, + id: 'obs', + initial: 'counting', + context: { count: undefined }, + states: { + counting: { + invoke: { + src: fromEventObservable(() => + interval(10).pipe( + map((value) => { + if (value === 5) { + throw new Error('some error'); + } + + return { type: 'COUNT', value }; + }) + ) + ), + onError: { + target: 'success', + guard: ({ context, event }) => { + expect((event.error as any).message).toEqual('some error'); + return ( + context.count === 4 && + (event.error as any).message === 'some error' + ); + } + } + }, + on: { + COUNT: { + actions: assign({ count: ({ event }) => event.value }) + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const actor = createActor(obsMachine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actor = createActor(obsMachine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); - it('should work with input', (done) => { - const machine = createMachine({ - invoke: { - src: fromEventObservable(({ input }) => - of({ - type: 'obs.event', - value: input - }) - ), - input: 42 - }, - on: { - 'obs.event': { - actions: ({ event }) => { - expect(event.value).toEqual(42); - done(); + it('should work with input', () => + new Promise((resolve) => { + const machine = createMachine({ + invoke: { + src: fromEventObservable(({ input }) => + of({ + type: 'obs.event', + value: input + }) + ), + input: 42 + }, + on: { + 'obs.event': { + actions: ({ event }) => { + expect(event.value).toEqual(42); + resolve(); + } } } - } - }); + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); }); describe('with logic', () => { - it('should work with actor logic', (done) => { - const countLogic: ActorLogic< - Snapshot & { context: number }, - EventObject - > = { - transition: (state, event) => { - if (event.type === 'INC') { - return { - ...state, - context: state.context + 1 - }; - } else if (event.type === 'DEC') { - return { - ...state, - context: state.context - 1 - }; - } - return state; - }, - getInitialSnapshot: () => ({ - status: 'active', - output: undefined, - error: undefined, - context: 0 - }), - getPersistedSnapshot: (s) => s - }; + it('should work with actor logic', () => + new Promise((resolve) => { + const countLogic: ActorLogic< + Snapshot & { context: number }, + EventObject + > = { + transition: (state, event) => { + if (event.type === 'INC') { + return { + ...state, + context: state.context + 1 + }; + } else if (event.type === 'DEC') { + return { + ...state, + context: state.context - 1 + }; + } + return state; + }, + getInitialSnapshot: () => ({ + status: 'active', + output: undefined, + error: undefined, + context: 0 + }), + getPersistedSnapshot: (s) => s + }; - const countMachine = createMachine({ - invoke: { - id: 'count', - src: countLogic - }, - on: { - INC: { - actions: forwardTo('count') + const countMachine = createMachine({ + invoke: { + id: 'count', + src: countLogic + }, + on: { + INC: { + actions: forwardTo('count') + } } - } - }); + }); - const countService = createActor(countMachine); - countService.subscribe((state) => { - if (state.children['count']?.getSnapshot().context === 2) { - done(); - } - }); - countService.start(); + const countService = createActor(countMachine); + countService.subscribe((state) => { + if (state.children['count']?.getSnapshot().context === 2) { + resolve(); + } + }); + countService.start(); - countService.send({ type: 'INC' }); - countService.send({ type: 'INC' }); - }); + countService.send({ type: 'INC' }); + countService.send({ type: 'INC' }); + })); - it('logic should have reference to the parent', (done) => { - const pongLogic: ActorLogic, EventObject> = { - transition: (state, event, { self }) => { - if (event.type === 'PING') { - self._parent?.send({ type: 'PONG' }); - } + it('logic should have reference to the parent', () => + new Promise((resolve) => { + const pongLogic: ActorLogic, EventObject> = { + transition: (state, event, { self }) => { + if (event.type === 'PING') { + self._parent?.send({ type: 'PONG' }); + } - return state; - }, - getInitialSnapshot: () => ({ - status: 'active', - output: undefined, - error: undefined - }), - getPersistedSnapshot: (s) => s - }; + return state; + }, + getInitialSnapshot: () => ({ + status: 'active', + output: undefined, + error: undefined + }), + getPersistedSnapshot: (s) => s + }; - const pingMachine = createMachine({ - initial: 'waiting', - states: { - waiting: { - entry: sendTo('ponger', { type: 'PING' }), - invoke: { - id: 'ponger', - src: pongLogic + const pingMachine = createMachine({ + initial: 'waiting', + states: { + waiting: { + entry: sendTo('ponger', { type: 'PING' }), + invoke: { + id: 'ponger', + src: pongLogic + }, + on: { + PONG: 'success' + } }, - on: { - PONG: 'success' + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const pingService = createActor(pingMachine); - pingService.subscribe({ - complete: () => { - done(); - } - }); - pingService.start(); - }); + const pingService = createActor(pingMachine); + pingService.subscribe({ + complete: () => { + resolve(); + } + }); + pingService.start(); + })); }); describe('with transition functions', () => { - it('should work with a transition function', (done) => { - const countReducer = ( - count: number, - event: { type: 'INC' } | { type: 'DEC' } - ): number => { - if (event.type === 'INC') { - return count + 1; - } else if (event.type === 'DEC') { - return count - 1; - } - return count; - }; - - const countMachine = createMachine({ - invoke: { - id: 'count', - src: fromTransition(countReducer, 0) - }, - on: { - INC: { - actions: forwardTo('count') + it('should work with a transition function', () => + new Promise((resolve) => { + const countReducer = ( + count: number, + event: { type: 'INC' } | { type: 'DEC' } + ): number => { + if (event.type === 'INC') { + return count + 1; + } else if (event.type === 'DEC') { + return count - 1; } - } - }); + return count; + }; - const countService = createActor(countMachine); - countService.subscribe((state) => { - if (state.children['count']?.getSnapshot().context === 2) { - done(); - } - }); - countService.start(); + const countMachine = createMachine({ + invoke: { + id: 'count', + src: fromTransition(countReducer, 0) + }, + on: { + INC: { + actions: forwardTo('count') + } + } + }); - countService.send({ type: 'INC' }); - countService.send({ type: 'INC' }); - }); + const countService = createActor(countMachine); + countService.subscribe((state) => { + if (state.children['count']?.getSnapshot().context === 2) { + resolve(); + } + }); + countService.start(); - it('should schedule events in a FIFO queue', (done) => { - type CountEvents = { type: 'INC' } | { type: 'DOUBLE' }; + countService.send({ type: 'INC' }); + countService.send({ type: 'INC' }); + })); - const countReducer = ( - count: number, - event: CountEvents, - { self }: ActorScope - ): number => { - if (event.type === 'INC') { - self.send({ type: 'DOUBLE' }); - return count + 1; - } - if (event.type === 'DOUBLE') { - return count * 2; - } + it('should schedule events in a FIFO queue', () => + new Promise((resolve) => { + type CountEvents = { type: 'INC' } | { type: 'DOUBLE' }; - return count; - }; + const countReducer = ( + count: number, + event: CountEvents, + { self }: ActorScope + ): number => { + if (event.type === 'INC') { + self.send({ type: 'DOUBLE' }); + return count + 1; + } + if (event.type === 'DOUBLE') { + return count * 2; + } - const countMachine = createMachine({ - invoke: { - id: 'count', - src: fromTransition(countReducer, 0) - }, - on: { - INC: { - actions: forwardTo('count') + return count; + }; + + const countMachine = createMachine({ + invoke: { + id: 'count', + src: fromTransition(countReducer, 0) + }, + on: { + INC: { + actions: forwardTo('count') + } } - } - }); + }); - const countService = createActor(countMachine); - countService.subscribe((state) => { - if (state.children['count']?.getSnapshot().context === 2) { - done(); - } - }); - countService.start(); + const countService = createActor(countMachine); + countService.subscribe((state) => { + if (state.children['count']?.getSnapshot().context === 2) { + resolve(); + } + }); + countService.start(); - countService.send({ type: 'INC' }); - }); + countService.send({ type: 'INC' }); + })); - it('should emit onSnapshot', (done) => { - const doublerLogic = fromTransition( - (_, event: { type: 'update'; value: number }) => event.value * 2, - 0 - ); - const machine = createMachine( - { - types: {} as { - actors: { src: 'doublerLogic'; logic: typeof doublerLogic }; - }, - invoke: { - id: 'doubler', - src: 'doublerLogic', - onSnapshot: { - actions: ({ event }) => { - if (event.snapshot.context === 42) { - done(); + it('should emit onSnapshot', () => + new Promise((resolve) => { + const doublerLogic = fromTransition( + (_, event: { type: 'update'; value: number }) => event.value * 2, + 0 + ); + const machine = createMachine( + { + types: {} as { + actors: { src: 'doublerLogic'; logic: typeof doublerLogic }; + }, + invoke: { + id: 'doubler', + src: 'doublerLogic', + onSnapshot: { + actions: ({ event }) => { + if (event.snapshot.context === 42) { + resolve(); + } } } - } + }, + entry: sendTo( + 'doubler', + { type: 'update', value: 21 }, + { delay: 10 } + ) }, - entry: sendTo('doubler', { type: 'update', value: 21 }, { delay: 10 }) - }, - { - actors: { - doublerLogic + { + actors: { + doublerLogic + } } - } - ); + ); - createActor(machine).start(); - }); + createActor(machine).start(); + })); }); describe('with machines', () => { @@ -2499,49 +2560,51 @@ describe('invoke', () => { } }); - it('should create invocations from machines in nested states', (done) => { - const actor = createActor(pingMachine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + it('should create invocations from machines in nested states', () => + new Promise((resolve) => { + const actor = createActor(pingMachine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); - it('should emit onSnapshot', (done) => { - const childMachine = createMachine({ - initial: 'a', - states: { - a: { - after: { - 10: 'b' - } - }, - b: {} - } - }); - const machine = createMachine( - { - types: {} as { - actors: { src: 'childMachine'; logic: typeof childMachine }; - }, - invoke: { - src: 'childMachine', - onSnapshot: { - actions: ({ event }) => { - if (event.snapshot.value === 'b') { - done(); + it('should emit onSnapshot', () => + new Promise((resolve) => { + const childMachine = createMachine({ + initial: 'a', + states: { + a: { + after: { + 10: 'b' + } + }, + b: {} + } + }); + const machine = createMachine( + { + types: {} as { + actors: { src: 'childMachine'; logic: typeof childMachine }; + }, + invoke: { + src: 'childMachine', + onSnapshot: { + actions: ({ event }) => { + if (event.snapshot.value === 'b') { + resolve(); + } } } } + }, + { + actors: { + childMachine + } } - }, - { - actors: { - childMachine - } - } - ); + ); - createActor(machine).start(); - }); + createActor(machine).start(); + })); }); describe('multiple simultaneous services', () => { @@ -2591,20 +2654,21 @@ describe('invoke', () => { } }); - it('should start all services at once', (done) => { - const service = createActor(multiple); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().context).toEqual({ - one: 'one', - two: 'two' - }); - done(); - } - }); + it('should start all services at once', () => + new Promise((resolve) => { + const service = createActor(multiple); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().context).toEqual({ + one: 'one', + two: 'two' + }); + resolve(); + } + }); - service.start(); - }); + service.start(); + })); const parallel = createMachine({ types: {} as { context: { one?: string; two?: string } }, @@ -2666,20 +2730,21 @@ describe('invoke', () => { } }); - it('should run services in parallel', (done) => { - const service = createActor(parallel); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().context).toEqual({ - one: 'one', - two: 'two' - }); - done(); - } - }); + it('should run services in parallel', () => + new Promise((resolve) => { + const service = createActor(parallel); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().context).toEqual({ + one: 'one', + two: 'two' + }); + resolve(); + } + }); - service.start(); - }); + service.start(); + })); it('should not invoke an actor if it gets stopped immediately by transitioning away in immediate microstep', () => { // Since an actor will be canceled when the state machine leaves the invoking state @@ -2749,66 +2814,67 @@ describe('invoke', () => { expect(actorStarted).toBe(false); }); - it('should invoke a service if other service gets stopped in subsequent microstep (#1180)', (done) => { - const machine = createMachine({ - initial: 'running', - states: { - running: { - type: 'parallel', - states: { - one: { - initial: 'active', - on: { - STOP_ONE: '.idle' - }, - states: { - idle: {}, - active: { - invoke: { - id: 'active', - src: fromCallback(() => { - /* ... */ - }) - }, - on: { - NEXT: { - actions: raise({ type: 'STOP_ONE' }) + it('should invoke a service if other service gets stopped in subsequent microstep (#1180)', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'running', + states: { + running: { + type: 'parallel', + states: { + one: { + initial: 'active', + on: { + STOP_ONE: '.idle' + }, + states: { + idle: {}, + active: { + invoke: { + id: 'active', + src: fromCallback(() => { + /* ... */ + }) + }, + on: { + NEXT: { + actions: raise({ type: 'STOP_ONE' }) + } } } } - } - }, - two: { - initial: 'idle', - on: { - NEXT: '.active' }, - states: { - idle: {}, - active: { - invoke: { - id: 'post', - src: fromPromise(() => Promise.resolve(42)), - onDone: '#done' + two: { + initial: 'idle', + on: { + NEXT: '.active' + }, + states: { + idle: {}, + active: { + invoke: { + id: 'post', + src: fromPromise(() => Promise.resolve(42)), + onDone: '#done' + } } } } } + }, + done: { + id: 'done', + type: 'final' } - }, - done: { - id: 'done', - type: 'final' } - } - }); + }); - const service = createActor(machine); - service.subscribe({ complete: () => done() }); - service.start(); + const service = createActor(machine); + service.subscribe({ complete: () => resolve() }); + service.start(); - service.send({ type: 'NEXT' }); - }); + service.send({ type: 'NEXT' }); + })); it('should invoke an actor when reentering invoking state within a single macrostep', () => { let actorStartedCount = 0; @@ -2846,50 +2912,51 @@ describe('invoke', () => { }); }); - it('invoke `src` can be used with invoke `input`', (done) => { - const machine = createMachine( - { - types: {} as { - actors: { - src: 'search'; - logic: PromiseActorLogic< - number, - { - endpoint: string; + it('invoke `src` can be used with invoke `input`', () => + new Promise((resolve) => { + const machine = createMachine( + { + types: {} as { + actors: { + src: 'search'; + logic: PromiseActorLogic< + number, + { + endpoint: string; + } + >; + }; + }, + initial: 'searching', + states: { + searching: { + invoke: { + src: 'search', + input: { + endpoint: 'example.com' + }, + onDone: 'success' } - >; - }; - }, - initial: 'searching', - states: { - searching: { - invoke: { - src: 'search', - input: { - endpoint: 'example.com' - }, - onDone: 'success' + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }, - { - actors: { - search: fromPromise(async ({ input }) => { - expect(input.endpoint).toEqual('example.com'); + }, + { + actors: { + search: fromPromise(async ({ input }) => { + expect(input.endpoint).toEqual('example.com'); - return 42; - }) + return 42; + }) + } } - } - ); - const actor = createActor(machine); - actor.subscribe({ complete: () => done() }); - actor.start(); - }); + ); + const actor = createActor(machine); + actor.subscribe({ complete: () => resolve() }); + actor.start(); + })); it('invoke `src` can be used with dynamic invoke `input`', async () => { const machine = createMachine( @@ -2941,45 +3008,46 @@ describe('invoke', () => { }); }); - it('invoke generated ID should be predictable based on the state node where it is defined', (done) => { - const machine = createMachine( - { - initial: 'a', - states: { - a: { - invoke: { - src: 'someSrc', - onDone: { - guard: ({ event }) => { - // invoke ID should not be 'someSrc' - const expectedType = 'xstate.done.actor.0.(machine).a'; - expect(event.type).toEqual(expectedType); - return event.type === expectedType; - }, - target: 'b' + it('invoke generated ID should be predictable based on the state node where it is defined', () => + new Promise((resolve) => { + const machine = createMachine( + { + initial: 'a', + states: { + a: { + invoke: { + src: 'someSrc', + onDone: { + guard: ({ event }) => { + // invoke ID should not be 'someSrc' + const expectedType = 'xstate.done.actor.0.(machine).a'; + expect(event.type).toEqual(expectedType); + return event.type === expectedType; + }, + target: 'b' + } } + }, + b: { + type: 'final' } - }, - b: { - type: 'final' + } + }, + { + actors: { + someSrc: fromPromise(() => Promise.resolve()) } } - }, - { - actors: { - someSrc: fromPromise(() => Promise.resolve()) - } - } - ); + ); - const actor = createActor(machine); - actor.subscribe({ - complete: () => { - done(); - } - }); - actor.start(); - }); + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + resolve(); + } + }); + actor.start(); + })); it.each([ ['src with string reference', { src: 'someSrc' }], @@ -3025,128 +3093,130 @@ describe('invoke', () => { ); // https://github.com/statelyai/xstate/issues/464 - it('xstate.done.actor events should only select onDone transition on the invoking state when invokee is referenced using a string', (done) => { - let counter = 0; - let invoked = false; + it('xstate.done.actor events should only select onDone transition on the invoking state when invokee is referenced using a string', () => + new Promise((resolve) => { + let counter = 0; + let invoked = false; - const createSingleState = (): any => ({ - initial: 'fetch', - states: { - fetch: { - invoke: { - src: 'fetchSmth', - onDone: { - actions: 'handleSuccess' + const createSingleState = (): any => ({ + initial: 'fetch', + states: { + fetch: { + invoke: { + src: 'fetchSmth', + onDone: { + actions: 'handleSuccess' + } } } } - } - }); + }); - const testMachine = createMachine( - { - type: 'parallel', - states: { - first: createSingleState(), - second: createSingleState() - } - }, - { - actions: { - handleSuccess: () => { - ++counter; + const testMachine = createMachine( + { + type: 'parallel', + states: { + first: createSingleState(), + second: createSingleState() } }, - actors: { - fetchSmth: fromPromise(() => { - if (invoked) { - // create a promise that won't ever resolve for the second invoking state - return new Promise(() => { - /* ... */ - }); + { + actions: { + handleSuccess: () => { + ++counter; } - invoked = true; - return Promise.resolve(42); - }) + }, + actors: { + fetchSmth: fromPromise(() => { + if (invoked) { + // create a promise that won't ever resolve for the second invoking state + return new Promise(() => { + /* ... */ + }); + } + invoked = true; + return Promise.resolve(42); + }) + } } - } - ); + ); - createActor(testMachine).start(); + createActor(testMachine).start(); - // check within a macrotask so all promise-induced microtasks have a chance to resolve first - setTimeout(() => { - expect(counter).toEqual(1); - done(); - }, 0); - }); + // check within a macrotask so all promise-induced microtasks have a chance to resolve first + setTimeout(() => { + expect(counter).toEqual(1); + resolve(); + }, 0); + })); - it('xstate.done.actor events should have unique names when invokee is a machine with an id property', (done) => { - const actual: AnyEventObject[] = []; + it('xstate.done.actor events should have unique names when invokee is a machine with an id property', () => + new Promise((resolve) => { + const actual: AnyEventObject[] = []; - const childMachine = createMachine({ - id: 'child', - initial: 'a', - states: { - a: { - invoke: { - src: fromPromise(() => { - return Promise.resolve(42); - }), - onDone: 'b' + const childMachine = createMachine({ + id: 'child', + initial: 'a', + states: { + a: { + invoke: { + src: fromPromise(() => { + return Promise.resolve(42); + }), + onDone: 'b' + } + }, + b: { + type: 'final' } - }, - b: { - type: 'final' } - } - }); + }); - const createSingleState = (): any => ({ - initial: 'fetch', - states: { - fetch: { - invoke: { - src: childMachine + const createSingleState = (): any => ({ + initial: 'fetch', + states: { + fetch: { + invoke: { + src: childMachine + } } } - } - }); + }); - const testMachine = createMachine({ - type: 'parallel', - states: { - first: createSingleState(), - second: createSingleState() - }, - on: { - '*': { - actions: ({ event }) => { - actual.push(event); + const testMachine = createMachine({ + type: 'parallel', + states: { + first: createSingleState(), + second: createSingleState() + }, + on: { + '*': { + actions: ({ event }) => { + actual.push(event); + } } } - } - }); + }); - createActor(testMachine).start(); + createActor(testMachine).start(); - // check within a macrotask so all promise-induced microtasks have a chance to resolve first - setTimeout(() => { - expect(actual).toEqual([ - { - type: 'xstate.done.actor.0.(machine).first.fetch', - output: undefined, - actorId: '0.(machine).first.fetch' - }, - { - type: 'xstate.done.actor.0.(machine).second.fetch', - output: undefined, - actorId: '0.(machine).second.fetch' - } - ]); - done(); - }, 100); - }); + // check within a macrotask so all promise-induced microtasks have a chance to resolve first + setTimeout(() => { + expect(actual).toEqual([ + { + type: 'xstate.done.actor.0.(machine).first.fetch', + output: undefined, + actorId: '0.(machine).first.fetch' + }, + { + type: 'xstate.done.actor.0.(machine).second.fetch', + output: undefined, + actorId: '0.(machine).second.fetch' + } + ]); + resolve(); + }, 100); + })); it('should get reinstantiated after reentering the invoking state in a microstep', () => { let invokeCount = 0; @@ -3364,76 +3434,78 @@ describe('invoke', () => { }); describe('invoke input', () => { - it('should provide input to an actor creator', (done) => { - const machine = createMachine( - { - types: {} as { - context: { count: number }; - actors: { - src: 'stringService'; - logic: PromiseActorLogic< - boolean, - { - staticVal: string; - newCount: number; + it('should provide input to an actor creator', () => + new Promise((resolve) => { + const machine = createMachine( + { + types: {} as { + context: { count: number }; + actors: { + src: 'stringService'; + logic: PromiseActorLogic< + boolean, + { + staticVal: string; + newCount: number; + } + >; + }; + }, + initial: 'pending', + context: { + count: 42 + }, + states: { + pending: { + invoke: { + src: 'stringService', + input: ({ context }) => ({ + staticVal: 'hello', + newCount: context.count * 2 + }), + onDone: 'success' } - >; - }; - }, - initial: 'pending', - context: { - count: 42 - }, - states: { - pending: { - invoke: { - src: 'stringService', - input: ({ context }) => ({ - staticVal: 'hello', - newCount: context.count * 2 - }), - onDone: 'success' + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }, - { - actors: { - stringService: fromPromise(({ input }) => { - expect(input).toEqual({ newCount: 84, staticVal: 'hello' }); + }, + { + actors: { + stringService: fromPromise(({ input }) => { + expect(input).toEqual({ newCount: 84, staticVal: 'hello' }); - return Promise.resolve(true); - }) + return Promise.resolve(true); + }) + } } - } - ); + ); - const service = createActor(machine); - service.subscribe({ - complete: () => { - done(); - } - }); + const service = createActor(machine); + service.subscribe({ + complete: () => { + resolve(); + } + }); - service.start(); - }); + service.start(); + })); - it('should provide self to input mapper', (done) => { - const machine = createMachine({ - invoke: { - src: fromCallback(({ input }) => { - expect(input.responder.send).toBeDefined(); - done(); - }), - input: ({ self }) => ({ - responder: self - }) - } - }); + it('should provide self to input mapper', () => + new Promise((resolve) => { + const machine = createMachine({ + invoke: { + src: fromCallback(({ input }) => { + expect(input.responder.send).toBeDefined(); + resolve(); + }), + input: ({ self }) => ({ + responder: self + }) + } + }); - createActor(machine).start(); - }); + createActor(machine).start(); + })); }); diff --git a/packages/core/test/parallel.test.ts b/packages/core/test/parallel.test.ts index d2d78c2c80..63df06ed1c 100644 --- a/packages/core/test/parallel.test.ts +++ b/packages/core/test/parallel.test.ts @@ -1051,72 +1051,73 @@ describe('parallel states', () => { }); }); - it('should raise a "xstate.done.state.*" event when all child states reach final state', (done) => { - const machine = createMachine({ - id: 'test', - initial: 'p', - states: { - p: { - type: 'parallel', - states: { - a: { - initial: 'idle', - states: { - idle: { - on: { - FINISH: 'finished' + it('should raise a "xstate.done.state.*" event when all child states reach final state', () => + new Promise((resolve) => { + const machine = createMachine({ + id: 'test', + initial: 'p', + states: { + p: { + type: 'parallel', + states: { + a: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished' + } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } - } - }, - b: { - initial: 'idle', - states: { - idle: { - on: { - FINISH: 'finished' + }, + b: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished' + } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } - } - }, - c: { - initial: 'idle', - states: { - idle: { - on: { - FINISH: 'finished' + }, + c: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished' + } + }, + finished: { + type: 'final' } - }, - finished: { - type: 'final' } } - } + }, + onDone: 'success' }, - onDone: 'success' - }, - success: { - type: 'final' + success: { + type: 'final' + } } - } - }); + }); - const service = createActor(machine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); + const service = createActor(machine); + service.subscribe({ + complete: () => { + resolve(); + } + }); + service.start(); - service.send({ type: 'FINISH' }); - }); + service.send({ type: 'FINISH' }); + })); it('should raise a "xstate.done.state.*" event when a pseudostate of a history type is directly on a parallel state', () => { const machine = createMachine({ diff --git a/packages/core/test/predictableExec.test.ts b/packages/core/test/predictableExec.test.ts index 871256b139..e28ffb7fe5 100644 --- a/packages/core/test/predictableExec.test.ts +++ b/packages/core/test/predictableExec.test.ts @@ -270,67 +270,68 @@ describe('predictableExec', () => { expect(actual).toEqual([0, 1, 2]); }); - it('parent should be able to read the updated state of a child when receiving an event from it', (done) => { - const child = createMachine({ - initial: 'a', - states: { - a: { - // we need to clear the call stack before we send the event to the parent - after: { - 1: 'b' + it('parent should be able to read the updated state of a child when receiving an event from it', () => + new Promise((resolve) => { + const child = createMachine({ + initial: 'a', + states: { + a: { + // we need to clear the call stack before we send the event to the parent + after: { + 1: 'b' + } + }, + b: { + entry: sendParent({ type: 'CHILD_UPDATED' }) } - }, - b: { - entry: sendParent({ type: 'CHILD_UPDATED' }) } - } - }); + }); - let service: AnyActor; + let service: AnyActor; - const machine = createMachine({ - invoke: { - id: 'myChild', - src: child - }, - initial: 'initial', - states: { - initial: { - on: { - CHILD_UPDATED: [ - { - guard: () => { - return ( - service.getSnapshot().children.myChild.getSnapshot() - .value === 'b' - ); + const machine = createMachine({ + invoke: { + id: 'myChild', + src: child + }, + initial: 'initial', + states: { + initial: { + on: { + CHILD_UPDATED: [ + { + guard: () => { + return ( + service.getSnapshot().children.myChild.getSnapshot() + .value === 'b' + ); + }, + target: 'success' }, - target: 'success' - }, - { - target: 'fail' - } - ] + { + target: 'fail' + } + ] + } + }, + success: { + type: 'final' + }, + fail: { + type: 'final' } - }, - success: { - type: 'final' - }, - fail: { - type: 'final' } - } - }); + }); - service = createActor(machine); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().value).toBe('success'); - done(); - } - }); - service.start(); - }); + service = createActor(machine); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().value).toBe('success'); + resolve(); + } + }); + service.start(); + })); it('should be possible to send immediate events to initially invoked actors', () => { const child = createMachine({ @@ -365,37 +366,38 @@ describe('predictableExec', () => { expect(service.getSnapshot().value).toBe('done'); }); - it('should create invoke based on context updated by entry actions of the same state', (done) => { - const machine = createMachine({ - context: { - updated: false - }, - initial: 'a', - states: { - a: { - on: { - NEXT: 'b' - } + it('should create invoke based on context updated by entry actions of the same state', () => + new Promise((resolve) => { + const machine = createMachine({ + context: { + updated: false }, - b: { - entry: assign({ updated: true }), - invoke: { - src: fromPromise(({ input }) => { - expect(input.updated).toBe(true); - done(); - return Promise.resolve(); - }), - input: ({ context }: any) => ({ - updated: context.updated - }) + initial: 'a', + states: { + a: { + on: { + NEXT: 'b' + } + }, + b: { + entry: assign({ updated: true }), + invoke: { + src: fromPromise(({ input }) => { + expect(input.updated).toBe(true); + resolve(); + return Promise.resolve(); + }), + input: ({ context }: any) => ({ + updated: context.updated + }) + } } } - } - }); + }); - const actorRef = createActor(machine).start(); - actorRef.send({ type: 'NEXT' }); - }); + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'NEXT' }); + })); it('should deliver events sent from the entry actions to a service invoked in the same state', () => { let received: any; @@ -435,64 +437,65 @@ describe('predictableExec', () => { expect(received).toEqual({ type: 'KNOCK_KNOCK' }); }); - it('parent should be able to read the updated state of a child when receiving an event from it', (done) => { - const child = createMachine({ - initial: 'a', - states: { - a: { - // we need to clear the call stack before we send the event to the parent - after: { - 1: 'b' + it('parent should be able to read the updated state of a child when receiving an event from it', () => + new Promise((resolve) => { + const child = createMachine({ + initial: 'a', + states: { + a: { + // we need to clear the call stack before we send the event to the parent + after: { + 1: 'b' + } + }, + b: { + entry: sendParent({ type: 'CHILD_UPDATED' }) } - }, - b: { - entry: sendParent({ type: 'CHILD_UPDATED' }) } - } - }); + }); - let service: AnyActor; + let service: AnyActor; - const machine = createMachine({ - invoke: { - id: 'myChild', - src: child - }, - initial: 'initial', - states: { - initial: { - on: { - CHILD_UPDATED: [ - { - guard: () => - service.getSnapshot().children.myChild.getSnapshot().value === - 'b', - target: 'success' - }, - { - target: 'fail' - } - ] - } - }, - success: { - type: 'final' + const machine = createMachine({ + invoke: { + id: 'myChild', + src: child }, - fail: { - type: 'final' + initial: 'initial', + states: { + initial: { + on: { + CHILD_UPDATED: [ + { + guard: () => + service.getSnapshot().children.myChild.getSnapshot() + .value === 'b', + target: 'success' + }, + { + target: 'fail' + } + ] + } + }, + success: { + type: 'final' + }, + fail: { + type: 'final' + } } - } - }); + }); - service = createActor(machine); - service.subscribe({ - complete: () => { - expect(service.getSnapshot().value).toBe('success'); - done(); - } - }); - service.start(); - }); + service = createActor(machine); + service.subscribe({ + complete: () => { + expect(service.getSnapshot().value).toBe('success'); + resolve(); + } + }); + service.start(); + })); it('should be possible to send immediate events to initially invoked actors', () => { const child = createMachine({ @@ -528,32 +531,33 @@ describe('predictableExec', () => { }); // https://github.com/statelyai/xstate/issues/3617 - it('should deliver events sent from the exit actions to a service invoked in the same state', (done) => { - const machine = createMachine({ - initial: 'active', - states: { - active: { - invoke: { - id: 'my-service', - src: fromCallback(({ receive }) => { - receive((event) => { - if (event.type === 'MY_EVENT') { - done(); - } - }); - }) + it('should deliver events sent from the exit actions to a service invoked in the same state', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'active', + states: { + active: { + invoke: { + id: 'my-service', + src: fromCallback(({ receive }) => { + receive((event) => { + if (event.type === 'MY_EVENT') { + resolve(); + } + }); + }) + }, + exit: sendTo('my-service', { type: 'MY_EVENT' }), + on: { + TOGGLE: 'inactive' + } }, - exit: sendTo('my-service', { type: 'MY_EVENT' }), - on: { - TOGGLE: 'inactive' - } - }, - inactive: {} - } - }); + inactive: {} + } + }); - const actor = createActor(machine).start(); + const actor = createActor(machine).start(); - actor.send({ type: 'TOGGLE' }); - }); + actor.send({ type: 'TOGGLE' }); + })); }); diff --git a/packages/core/test/spawnChild.test.ts b/packages/core/test/spawnChild.test.ts index db1558a47f..f1b05cf91a 100644 --- a/packages/core/test/spawnChild.test.ts +++ b/packages/core/test/spawnChild.test.ts @@ -48,42 +48,43 @@ describe('spawnChild action', () => { expect(actor.getSnapshot().children.child).toBeDefined(); }); - it('should accept `syncSnapshot` option', (done) => { - const observableLogic = fromObservable(() => interval(10)); - const observableMachine = createMachine({ - id: 'observable', - initial: 'idle', - context: { - observableRef: undefined! as ActorRefFrom - }, - states: { - idle: { - entry: spawnChild(observableLogic, { - id: 'int', - syncSnapshot: true - }), - on: { - 'xstate.snapshot.int': { - target: 'success', - guard: ({ event }) => event.snapshot.context === 5 + it('should accept `syncSnapshot` option', () => + new Promise((resolve) => { + const observableLogic = fromObservable(() => interval(10)); + const observableMachine = createMachine({ + id: 'observable', + initial: 'idle', + context: { + observableRef: undefined! as ActorRefFrom + }, + states: { + idle: { + entry: spawnChild(observableLogic, { + id: 'int', + syncSnapshot: true + }), + on: { + 'xstate.snapshot.int': { + target: 'success', + guard: ({ event }) => event.snapshot.context === 5 + } } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const observableService = createActor(observableMachine); - observableService.subscribe({ - complete: () => { - done(); - } - }); + const observableService = createActor(observableMachine); + observableService.subscribe({ + complete: () => { + resolve(); + } + }); - observableService.start(); - }); + observableService.start(); + })); it('should handle a dynamic id', () => { const spy = vi.fn(); diff --git a/packages/core/test/system.test.ts b/packages/core/test/system.test.ts index d392c725e1..f59b6fb394 100644 --- a/packages/core/test/system.test.ts +++ b/packages/core/test/system.test.ts @@ -21,102 +21,104 @@ import { import { ActorSystem } from '../src/system.ts'; describe('system', () => { - it('should register an invoked actor', (done) => { - type MySystem = ActorSystem<{ - actors: { - receiver: ActorRef, { type: 'HELLO' }>; - }; - }>; - - const machine = createMachine({ - id: 'parent', - initial: 'a', - states: { - a: { - invoke: [ - { - src: fromCallback(({ receive }) => { - receive((event) => { - expect(event.type).toBe('HELLO'); - done(); - }); - }), - systemId: 'receiver' - }, - { - src: createMachine({ - id: 'childmachine', - entry: ({ system }) => { - const receiver = (system as MySystem)?.get('receiver'); - - if (receiver) { - receiver.send({ type: 'HELLO' }); - } - } - }) - } - ] - } - } - }); - - createActor(machine).start(); - }); - - it('should register a spawned actor', (done) => { - type MySystem = ActorSystem<{ - actors: { - receiver: ActorRef, { type: 'HELLO' }>; - }; - }>; - - const machine = createMachine({ - types: {} as { - context: { - ref: CallbackActorRef; - machineRef?: ActorRefFrom; + it('should register an invoked actor', () => + new Promise((resolve) => { + type MySystem = ActorSystem<{ + actors: { + receiver: ActorRef, { type: 'HELLO' }>; }; - }, - id: 'parent', - context: ({ spawn }) => ({ - ref: spawn( - fromCallback(({ receive }) => { - receive((event) => { - expect(event.type).toBe('HELLO'); - done(); - }); - }), - { systemId: 'receiver' } - ) - }), - on: { - toggle: { - actions: assign({ - machineRef: ({ spawn }) => { - return spawn( - createMachine({ + }>; + + const machine = createMachine({ + id: 'parent', + initial: 'a', + states: { + a: { + invoke: [ + { + src: fromCallback(({ receive }) => { + receive((event) => { + expect(event.type).toBe('HELLO'); + resolve(); + }); + }), + systemId: 'receiver' + }, + { + src: createMachine({ id: 'childmachine', entry: ({ system }) => { const receiver = (system as MySystem)?.get('receiver'); if (receiver) { receiver.send({ type: 'HELLO' }); - } else { - throw new Error('no'); } } }) - ); - } - }) + } + ] + } } - } - }); + }); - const actor = createActor(machine).start(); + createActor(machine).start(); + })); - actor.send({ type: 'toggle' }); - }); + it('should register a spawned actor', () => + new Promise((resolve) => { + type MySystem = ActorSystem<{ + actors: { + receiver: ActorRef, { type: 'HELLO' }>; + }; + }>; + + const machine = createMachine({ + types: {} as { + context: { + ref: CallbackActorRef; + machineRef?: ActorRefFrom; + }; + }, + id: 'parent', + context: ({ spawn }) => ({ + ref: spawn( + fromCallback(({ receive }) => { + receive((event) => { + expect(event.type).toBe('HELLO'); + resolve(); + }); + }), + { systemId: 'receiver' } + ) + }), + on: { + toggle: { + actions: assign({ + machineRef: ({ spawn }) => { + return spawn( + createMachine({ + id: 'childmachine', + entry: ({ system }) => { + const receiver = (system as MySystem)?.get('receiver'); + + if (receiver) { + receiver.send({ type: 'HELLO' }); + } else { + throw new Error('no'); + } + } + }) + ); + } + }) + } + } + }); + + const actor = createActor(machine).start(); + + actor.send({ type: 'toggle' }); + })); it('system can be immediately accessed outside the actor', () => { const machine = createMachine({ diff --git a/packages/xstate-graph/test/paths.test.ts b/packages/xstate-graph/test/paths.test.ts index 8dcee07325..c64aa99a65 100644 --- a/packages/xstate-graph/test/paths.test.ts +++ b/packages/xstate-graph/test/paths.test.ts @@ -47,7 +47,7 @@ describe('testModel.testPaths(...)', () => { const events = typeof options.events === 'function' ? options.events(initialState) - : options.events ?? []; + : (options.events ?? []); const nextState = getNextSnapshot(logic, initialState, events[0]); return [ diff --git a/packages/xstate-immer/test/immer.test.ts b/packages/xstate-immer/test/immer.test.ts index 1bfa2d3da8..d529152f60 100644 --- a/packages/xstate-immer/test/immer.test.ts +++ b/packages/xstate-immer/test/immer.test.ts @@ -158,76 +158,78 @@ describe('@xstate/immer', () => { expect(actorRef.getSnapshot().context.foo.bar.baz).toEqual([1, 2, 3, 4]); }); - it('should create updates (form example)', (done) => { - interface FormContext { - name: string; - age: number | undefined; - } - - type NameUpdateEvent = ImmerUpdateEvent<'UPDATE_NAME', string>; - type AgeUpdateEvent = ImmerUpdateEvent<'UPDATE_AGE', number>; - - type FormEvent = - | NameUpdateEvent - | AgeUpdateEvent - | { - type: 'SUBMIT'; - }; - - const nameUpdater = createUpdater( - 'UPDATE_NAME', - ({ context, event }) => { - context.name = event.input; + it('should create updates (form example)', () => + new Promise((resolve) => { + interface FormContext { + name: string; + age: number | undefined; } - ); - const ageUpdater = createUpdater( - 'UPDATE_AGE', - ({ context, event }) => { - context.age = event.input; - } - ); + type NameUpdateEvent = ImmerUpdateEvent<'UPDATE_NAME', string>; + type AgeUpdateEvent = ImmerUpdateEvent<'UPDATE_AGE', number>; + + type FormEvent = + | NameUpdateEvent + | AgeUpdateEvent + | { + type: 'SUBMIT'; + }; + + const nameUpdater = createUpdater< + FormContext, + NameUpdateEvent, + FormEvent + >('UPDATE_NAME', ({ context, event }) => { + context.name = event.input; + }); - const formMachine = createMachine({ - types: {} as { context: FormContext; events: FormEvent }, - initial: 'editing', - context: { - name: '', - age: undefined - }, - states: { - editing: { - on: { - [nameUpdater.type]: { actions: nameUpdater.action }, - [ageUpdater.type]: { actions: ageUpdater.action }, - SUBMIT: 'submitting' - } + const ageUpdater = createUpdater( + 'UPDATE_AGE', + ({ context, event }) => { + context.age = event.input; + } + ); + + const formMachine = createMachine({ + types: {} as { context: FormContext; events: FormEvent }, + initial: 'editing', + context: { + name: '', + age: undefined }, - submitting: { - always: { - target: 'success', - guard: ({ context }) => { - return context.name === 'David' && context.age === 0; + states: { + editing: { + on: { + [nameUpdater.type]: { actions: nameUpdater.action }, + [ageUpdater.type]: { actions: ageUpdater.action }, + SUBMIT: 'submitting' } + }, + submitting: { + always: { + target: 'success', + guard: ({ context }) => { + return context.name === 'David' && context.age === 0; + } + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const service = createActor(formMachine); - service.subscribe({ - complete: () => { - done(); - } - }); - service.start(); + const service = createActor(formMachine); + service.subscribe({ + complete: () => { + resolve(); + } + }); + service.start(); - service.send(nameUpdater.update('David')); - service.send(ageUpdater.update(0)); + service.send(nameUpdater.update('David')); + service.send(ageUpdater.update(0)); - service.send({ type: 'SUBMIT' }); - }); + service.send({ type: 'SUBMIT' }); + })); }); diff --git a/packages/xstate-inspect/test/inspect.test.ts b/packages/xstate-inspect/test/inspect.test.ts index 974dcc8f5d..afbceb39c1 100644 --- a/packages/xstate-inspect/test/inspect.test.ts +++ b/packages/xstate-inspect/test/inspect.test.ts @@ -223,46 +223,48 @@ describe('@xstate/inspect', () => { }); // TODO: the value is still available on `machine.definition.initial` and that is not handled by the serializer - it.skip('should not crash when registering machine with very deep context when serializer manages to replace it', (done) => { - type DeepObject = { nested?: DeepObject }; + it.skip('should not crash when registering machine with very deep context when serializer manages to replace it', async () => { + await new Promise((resolve) => { + type DeepObject = { nested?: DeepObject }; - const deepObj: DeepObject = {}; + const deepObj: DeepObject = {}; - let current = deepObj; - for (let i = 0; i < 20_000; i += 1) { - current.nested = {}; - current = current.nested; - } - - const machine = createMachine({ - initial: 'active', - context: deepObj, - states: { - active: {} + let current = deepObj; + for (let i = 0; i < 20_000; i += 1) { + current.nested = {}; + current = current.nested; } - }); - const devTools = createDevTools(); + const machine = createMachine({ + initial: 'active', + context: deepObj, + states: { + active: {} + } + }); - inspect({ - iframe: false, - devTools, - serialize: (key, value) => { - if (key === 'nested') { - return '[very deep]'; + const devTools = createDevTools(); + + inspect({ + iframe: false, + devTools, + serialize: (key, value) => { + if (key === 'nested') { + return '[very deep]'; + } + + return value; } + }); - return value; - } - }); + const service = createActor(machine).start(); - const service = createActor(machine).start(); + devTools.onRegister(() => { + resolve(); + }); - devTools.onRegister(() => { - done(); + expect(() => devTools.register(service)).not.toThrow(); }); - - expect(() => devTools.register(service)).not.toThrow(); }); it('should successfully serialize value with unsafe toJSON when serializer manages to replace it', () => { diff --git a/packages/xstate-react/test/useActor.test.tsx b/packages/xstate-react/test/useActor.test.tsx index 9993bc35b4..42a2d07843 100644 --- a/packages/xstate-react/test/useActor.test.tsx +++ b/packages/xstate-react/test/useActor.test.tsx @@ -249,187 +249,190 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { await screen.findByTestId('success'); }); - it('actions should not use stale data in a builtin transition action', (done) => { - const toggleMachine = createMachine({ - types: {} as { - context: { latest: number }; - events: { type: 'SET_LATEST' }; - }, - context: { - latest: 0 - }, - on: { - SET_LATEST: { - actions: 'setLatest' + it('actions should not use stale data in a builtin transition action', () => + new Promise((resolve) => { + const toggleMachine = createMachine({ + types: {} as { + context: { latest: number }; + events: { type: 'SET_LATEST' }; + }, + context: { + latest: 0 + }, + on: { + SET_LATEST: { + actions: 'setLatest' + } } - } - }); + }); - const Component = () => { - const [ext, setExt] = useState(1); - - const [, send] = useActor( - toggleMachine.provide({ - actions: { - setLatest: assign({ - latest: () => { - expect(ext).toBe(2); - done(); - return ext; - } - }) - } - }) - ); + const Component = () => { + const [ext, setExt] = useState(1); + + const [, send] = useActor( + toggleMachine.provide({ + actions: { + setLatest: assign({ + latest: () => { + expect(ext).toBe(2); + resolve(); + return ext; + } + }) + } + }) + ); - return ( - <> - - ); - }; + return ( + + ); + }; - render(); - const button = screen.getByTestId('button'); + render(); + const button = screen.getByTestId('button'); - fireEvent.click(button); - }); + fireEvent.click(button); + })); it('actions created by a layout effect should access the latest closure values', () => { const actual: number[] = []; diff --git a/packages/xstate-solid/test/fromActorRef.test.tsx b/packages/xstate-solid/test/fromActorRef.test.tsx index 0ebb9218a9..e8d00e1883 100644 --- a/packages/xstate-solid/test/fromActorRef.test.tsx +++ b/packages/xstate-solid/test/fromActorRef.test.tsx @@ -26,145 +26,148 @@ const createSimpleActor = (value: T) => createActor(fromTransition((s) => s, value)); describe('fromActorRef', () => { - it('initial invoked actor should be immediately available', (done) => { - const childMachine = createMachine({ - id: 'childMachine', - initial: 'active', - states: { - active: {} - } - }); - const machine = createMachine({ - initial: 'active', - invoke: { - id: 'child', - src: childMachine - }, - states: { - active: {} - } - }); + it('initial invoked actor should be immediately available', () => + new Promise((resolve) => { + const childMachine = createMachine({ + id: 'childMachine', + initial: 'active', + states: { + active: {} + } + }); + const machine = createMachine({ + initial: 'active', + invoke: { + id: 'child', + src: childMachine + }, + states: { + active: {} + } + }); - const ChildTest: Component<{ actor: ActorRefFrom }> = ( - props - ) => { - const state = fromActorRef(props.actor); + const ChildTest: Component<{ + actor: ActorRefFrom; + }> = (props) => { + const state = fromActorRef(props.actor); - expect(state().value).toEqual('active'); - done(); + expect(state().value).toEqual('active'); + resolve(); - return null; - }; + return null; + }; - const Test = () => { - const [state] = useActor(machine); - return ( - } - /> - ); - }; + const Test = () => { + const [state] = useActor(machine); + return ( + } + /> + ); + }; - render(() => ); - }); + render(() => ); + })); - it('invoked actor should be able to receive (deferred) events that it replays when active', (done) => { - const childMachine = createMachine({ - id: 'childMachine', - initial: 'active', - states: { - active: { - on: { - FINISH: { actions: sendParent({ type: 'FINISH' }) } + it('invoked actor should be able to receive (deferred) events that it replays when active', () => + new Promise((resolve) => { + const childMachine = createMachine({ + id: 'childMachine', + initial: 'active', + states: { + active: { + on: { + FINISH: { actions: sendParent({ type: 'FINISH' }) } + } } } - } - }); - const machine = createMachine({ - initial: 'active', - invoke: { - id: 'child', - src: childMachine - }, - states: { - active: { - on: { FINISH: 'success' } + }); + const machine = createMachine({ + initial: 'active', + invoke: { + id: 'child', + src: childMachine }, - success: {} - } - }); + states: { + active: { + on: { FINISH: 'success' } + }, + success: {} + } + }); - const ChildTest: Component<{ actor: ActorRefFrom }> = ( - props - ) => { - const state = fromActorRef(props.actor); + const ChildTest: Component<{ + actor: ActorRefFrom; + }> = (props) => { + const state = fromActorRef(props.actor); - onMount(() => { - expect(state().value).toEqual('active'); - props.actor.send({ type: 'FINISH' }); - }); + onMount(() => { + expect(state().value).toEqual('active'); + props.actor.send({ type: 'FINISH' }); + }); - return null; - }; + return null; + }; - const Test = () => { - const [state] = useActor(machine); - createEffect(() => { - if (state.matches('success')) { - done(); - } - }); + const Test = () => { + const [state] = useActor(machine); + createEffect(() => { + if (state.matches('success')) { + resolve(); + } + }); - return ( - } - /> - ); - }; + return ( + } + /> + ); + }; - render(() => ); - }); + render(() => ); + })); - it('send should update synchronously', (done) => { - const machine = createMachine({ - initial: 'start', - states: { - start: { - on: { - done: 'success' + it('send should update synchronously', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'start', + states: { + start: { + on: { + done: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); - - const Spawner = () => { - const [actorRef] = createSignal(createActor(machine).start()); - const snapshot = fromActorRef(actorRef); - - onMount(() => { - expect(snapshot().value).toBe('start'); - actorRef().send({ type: 'done' }); - expect(snapshot().value).toBe('success'); }); - return ( - - - - - - - - - ); - }; + const Spawner = () => { + const [actorRef] = createSignal(createActor(machine).start()); + const snapshot = fromActorRef(actorRef); - render(() => ); - waitFor(() => screen.getByTestId('success')).then(() => done()); - }); + onMount(() => { + expect(snapshot().value).toBe('start'); + actorRef().send({ type: 'done' }); + expect(snapshot().value).toBe('success'); + }); + + return ( + + + + + + + + + ); + }; + + render(() => ); + waitFor(() => screen.getByTestId('success')).then(() => resolve()); + })); it('should only trigger effects once for nested context values', () => { const childMachine = createMachine({ @@ -519,65 +522,68 @@ describe('fromActorRef', () => { expect(canDoSomethingEl.textContent).toEqual('true'); }); - it('spawned actor should be able to receive (deferred) events that it replays when active', (done) => { - const childMachine = createMachine({ - id: 'childMachine', - initial: 'active', - states: { - active: { - on: { - FINISH: { actions: sendParent({ type: 'FINISH' }) } + it('spawned actor should be able to receive (deferred) events that it replays when active', () => + new Promise((resolve) => { + const childMachine = createMachine({ + id: 'childMachine', + initial: 'active', + states: { + active: { + on: { + FINISH: { actions: sendParent({ type: 'FINISH' }) } + } } } - } - }); - const machine = createMachine({ - types: {} as { + }); + const machine = createMachine({ + types: {} as { + context: { + actorRef?: ActorRefFrom; + }; + }, + initial: 'active', context: { - actorRef?: ActorRefFrom; - }; - }, - initial: 'active', - context: { - actorRef: undefined - }, - states: { - active: { - entry: assign({ - actorRef: ({ spawn }) => spawn(childMachine) - }), - on: { FINISH: 'success' } + actorRef: undefined }, - success: {} - } - }); - - const ChildTest = (props: { actor: ActorRefFrom }) => { - const snapshot = fromActorRef(props.actor); - createEffect(() => { - expect(snapshot().value).toEqual('active'); + states: { + active: { + entry: assign({ + actorRef: ({ spawn }) => spawn(childMachine) + }), + on: { FINISH: 'success' } + }, + success: {} + } }); - onMount(() => { - props.actor.send({ type: 'FINISH' }); - }); + const ChildTest = (props: { + actor: ActorRefFrom; + }) => { + const snapshot = fromActorRef(props.actor); + createEffect(() => { + expect(snapshot().value).toEqual('active'); + }); - return null; - }; + onMount(() => { + props.actor.send({ type: 'FINISH' }); + }); - const Test = () => { - const [state] = useActor(machine); - createEffect(() => { - if (state.matches('success')) { - done(); - } - }); + return null; + }; - return ; - }; + const Test = () => { + const [state] = useActor(machine); + createEffect(() => { + if (state.matches('success')) { + resolve(); + } + }); - render(() => ); - }); + return ; + }; + + render(() => ); + })); it('should provide value from `actor.getSnapshot()` immediately', () => { const simpleActor = createActor(fromTransition((s) => s, 42)); @@ -1144,67 +1150,68 @@ describe('fromActorRef', () => { }); }); - it(`actor should not reevaluate a scope depending on state.matches when state.value doesn't change`, (done) => { - vi.useFakeTimers(); + it(`actor should not reevaluate a scope depending on state.matches when state.value doesn't change`, () => + new Promise((resolve) => { + vi.useFakeTimers(); - interface MachineContext { - counter: number; - } + interface MachineContext { + counter: number; + } - const machine = createMachine({ - types: {} as { - context: MachineContext; - }, - context: { - counter: 0 - }, - initial: 'idle', - states: { - idle: { - on: { - INC: { - actions: assign({ - counter: ({ context }) => context.counter + 1 - }) + const machine = createMachine({ + types: {} as { + context: MachineContext; + }, + context: { + counter: 0 + }, + initial: 'idle', + states: { + idle: { + on: { + INC: { + actions: assign({ + counter: ({ context }) => context.counter + 1 + }) + } } } } - } - }); + }); - const counterService = createActor(machine).start(); + const counterService = createActor(machine).start(); - const Comp = () => { - let calls = 0; - const snapshot = fromActorRef(counterService); + const Comp = () => { + let calls = 0; + const snapshot = fromActorRef(counterService); - createEffect(() => { - calls++; - snapshot().matches('foo'); - }); + createEffect(() => { + calls++; + snapshot().matches('foo'); + }); - onMount(() => { - counterService.send({ type: 'INC' }); - counterService.send({ type: 'INC' }); - counterService.send({ type: 'INC' }); - setTimeout(() => { + onMount(() => { + counterService.send({ type: 'INC' }); + counterService.send({ type: 'INC' }); counterService.send({ type: 'INC' }); setTimeout(() => { counterService.send({ type: 'INC' }); setTimeout(() => { - expect(calls).toBe(1); - done(); - }, 100); + counterService.send({ type: 'INC' }); + setTimeout(() => { + expect(calls).toBe(1); + resolve(); + }, 100); + }); }); }); - }); - return null; - }; + return null; + }; - render(() => ); - vi.advanceTimersByTime(110); - }); + render(() => ); + vi.advanceTimersByTime(110); + })); it('actor should be updated when it changes shallow', () => { const counterMachine = createMachine({ diff --git a/packages/xstate-solid/test/useActor.test.tsx b/packages/xstate-solid/test/useActor.test.tsx index 135247c2e2..4f461b88e8 100644 --- a/packages/xstate-solid/test/useActor.test.tsx +++ b/packages/xstate-solid/test/useActor.test.tsx @@ -213,148 +213,151 @@ describe('useActor', () => { render(() => ); }); - it('should not spawn actors until service is started', (done) => { - const spawnMachine = createMachine({ - types: {} as { context: any }, - id: 'spawn', - initial: 'start', - context: { ref: undefined }, - states: { - start: { - entry: assign({ - ref: ({ spawn }) => - spawn( - fromPromise(() => new Promise((res) => res(42))), - { id: 'my-promise' } - ) - }), - on: { - 'xstate.done.actor.my-promise': 'success' + it('should not spawn actors until service is started', () => + new Promise((resolve) => { + const spawnMachine = createMachine({ + types: {} as { context: any }, + id: 'spawn', + initial: 'start', + context: { ref: undefined }, + states: { + start: { + entry: assign({ + ref: ({ spawn }) => + spawn( + fromPromise(() => new Promise((res) => res(42))), + { id: 'my-promise' } + ) + }), + on: { + 'xstate.done.actor.my-promise': 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const Spawner = () => { - const [current] = useActor(spawnMachine); + const Spawner = () => { + const [current] = useActor(spawnMachine); - return ( - - - - - - - - - ); - }; + return ( + + + + + + + + + ); + }; - render(() => ); - waitFor(() => screen.getByTestId('success')).then(() => done()); - }); + render(() => ); + waitFor(() => screen.getByTestId('success')).then(() => resolve()); + })); - it('send should update synchronously', (done) => { - const machine = createMachine({ - initial: 'start', - states: { - start: { - on: { - done: 'success' + it('send should update synchronously', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'start', + states: { + start: { + on: { + done: 'success' + } + }, + success: { + type: 'final' } - }, - success: { - type: 'final' } - } - }); + }); - const Spawner = () => { - const [current, send] = useActor(machine); + const Spawner = () => { + const [current, send] = useActor(machine); - onMount(() => { - expect(current.value).toBe('start'); - send({ type: 'done' }); - expect(current.value).toBe('success'); - }); + onMount(() => { + expect(current.value).toBe('start'); + send({ type: 'done' }); + expect(current.value).toBe('success'); + }); - return ( - - - - - - - - - ); - }; + return ( + + + + + + + + + ); + }; - render(() => ); - waitFor(() => screen.getByTestId('success')).then(() => done()); - }); + render(() => ); + waitFor(() => screen.getByTestId('success')).then(() => resolve()); + })); - it('actions should not have stale data', (done) => { - const toggleMachine = createMachine({ - types: {} as { - events: { type: 'TOGGLE' }; - }, - initial: 'inactive', - states: { - inactive: { - on: { TOGGLE: 'active' } + it('actions should not have stale data', () => + new Promise((resolve) => { + const toggleMachine = createMachine({ + types: {} as { + events: { type: 'TOGGLE' }; }, - active: { - entry: 'doAction' + initial: 'inactive', + states: { + inactive: { + on: { TOGGLE: 'active' } + }, + active: { + entry: 'doAction' + } } - } - }); + }); - const Toggle = () => { - const [ext, setExt] = createSignal(false); + const Toggle = () => { + const [ext, setExt] = createSignal(false); - const doAction = () => { - expect(ext()).toBeTruthy(); - done(); - }; + const doAction = () => { + expect(ext()).toBeTruthy(); + resolve(); + }; - const [, send] = useActor( - toggleMachine.provide({ - actions: { - doAction - } - }) - ); + const [, send] = useActor( + toggleMachine.provide({ + actions: { + doAction + } + }) + ); - return ( -
-
- ); - }; + return ( +
+
+ ); + }; - render(() => ); + render(() => ); - const button = screen.getByTestId('button'); - const extButton = screen.getByTestId('extbutton'); - fireEvent.click(extButton); + const button = screen.getByTestId('button'); + const extButton = screen.getByTestId('extbutton'); + fireEvent.click(extButton); - fireEvent.click(button); - }); + fireEvent.click(button); + })); it('should capture all actions', () => { let count = 0; @@ -893,60 +896,61 @@ describe('useActor', () => { expect(canDoSomethingEl.textContent).toEqual('true'); }); - it(`should not reevaluate a scope depending on state.matches when state.value doesn't change`, (done) => { - interface MachineContext { - counter: number; - } + it(`should not reevaluate a scope depending on state.matches when state.value doesn't change`, () => + new Promise((resolve) => { + interface MachineContext { + counter: number; + } - const machine = createMachine({ - types: {} as { context: MachineContext }, - context: { - counter: 0 - }, - initial: 'idle', - states: { - idle: { - on: { - INC: { - actions: assign({ - counter: ({ context }) => context.counter + 1 - }) + const machine = createMachine({ + types: {} as { context: MachineContext }, + context: { + counter: 0 + }, + initial: 'idle', + states: { + idle: { + on: { + INC: { + actions: assign({ + counter: ({ context }) => context.counter + 1 + }) + } } } } - } - }); + }); - const Comp = () => { - let calls = 0; - const [state, send] = useActor(machine); + const Comp = () => { + let calls = 0; + const [state, send] = useActor(machine); - createEffect(() => { - calls++; - state.matches('foo'); - }); + createEffect(() => { + calls++; + state.matches('foo'); + }); - onMount(() => { - send({ type: 'INC' }); - send({ type: 'INC' }); - send({ type: 'INC' }); - setTimeout(() => { + onMount(() => { + send({ type: 'INC' }); + send({ type: 'INC' }); send({ type: 'INC' }); setTimeout(() => { send({ type: 'INC' }); setTimeout(() => { - expect(calls).toBe(1); - done(); - }, 100); + send({ type: 'INC' }); + setTimeout(() => { + expect(calls).toBe(1); + resolve(); + }, 100); + }); }); }); - }); - return null; - }; + return null; + }; - render(() => ); - }); + render(() => ); + })); it('should successfully spawn actors from the lazily declared context', () => { let childSpawned = false; @@ -1389,39 +1393,40 @@ describe('useActor', () => { expect(machine2Value.textContent).toEqual('101'); }); - it('Service should stop on component cleanup', (done) => { - vi.useFakeTimers(); - const machine = createMachine({ - initial: 'a', - states: { - a: { - on: { - EV: { - target: 'b' + it('Service should stop on component cleanup', () => + new Promise((resolve) => { + vi.useFakeTimers(); + const machine = createMachine({ + initial: 'a', + states: { + a: { + on: { + EV: { + target: 'b' + } } - } - }, - b: {} - } - }); - const Display = () => { - onCleanup(() => { - expect(service.getSnapshot().status).toBe('stopped'); - done(); + }, + b: {} + } }); - const [state, , service] = useActor(machine); - return
{state.toString()}
; - }; - const Counter = () => { - const [show, setShow] = createSignal(true); - setTimeout(() => setShow(false), 100); + const Display = () => { + onCleanup(() => { + expect(service.getSnapshot().status).toBe('stopped'); + resolve(); + }); + const [state, , service] = useActor(machine); + return
{state.toString()}
; + }; + const Counter = () => { + const [show, setShow] = createSignal(true); + setTimeout(() => setShow(false), 100); - return
{show() ? : null}
; - }; + return
{show() ? : null}
; + }; - render(() => ); - vi.advanceTimersByTime(200); - }); + render(() => ); + vi.advanceTimersByTime(200); + })); it('.can should trigger on context change', () => { const machine = createMachine( @@ -1541,42 +1546,43 @@ describe('useActor', () => { expect(activatedCount).toEqual(1); }); - it('child component should be able to send an event to a parent immediately in an effect', (done) => { - const machine = createMachine({ - types: {} as { - events: { type: 'FINISH' }; - }, - initial: 'active', - states: { - active: { - on: { FINISH: 'success' } + it('child component should be able to send an event to a parent immediately in an effect', () => + new Promise((resolve) => { + const machine = createMachine({ + types: {} as { + events: { type: 'FINISH' }; }, - success: {} - } - }); - - const ChildTest = (props: { send: any }) => { - // This will send an event to the parent service - // BEFORE the service is ready. - onMount(() => { - props.send({ type: 'FINISH' }); + initial: 'active', + states: { + active: { + on: { FINISH: 'success' } + }, + success: {} + } }); - return null; - }; + const ChildTest = (props: { send: any }) => { + // This will send an event to the parent service + // BEFORE the service is ready. + onMount(() => { + props.send({ type: 'FINISH' }); + }); - const Test = () => { - const [state, send] = useActor(machine); - createEffect(() => { - if (state.matches('success')) { - done(); - } - }); - return ; - }; + return null; + }; - render(() => ); - }); + const Test = () => { + const [state, send] = useActor(machine); + createEffect(() => { + if (state.matches('success')) { + resolve(); + } + }); + return ; + }; + + render(() => ); + })); it('custom data should be available right away for the invoked actor', () => { const childMachine = createMachine({ diff --git a/packages/xstate-solid/test/useActorRef.test.tsx b/packages/xstate-solid/test/useActorRef.test.tsx index 3cb819a81e..aede63635a 100644 --- a/packages/xstate-solid/test/useActorRef.test.tsx +++ b/packages/xstate-solid/test/useActorRef.test.tsx @@ -5,83 +5,85 @@ import { useActorRef } from '../src/index.ts'; import { createEffect } from 'solid-js'; describe('useActorRef', () => { - it('observer should be called with next state', (done) => { - const machine = createMachine({ - initial: 'inactive', - states: { - inactive: { - on: { - ACTIVATE: 'active' - } - }, - active: {} - } - }); + it('observer should be called with next state', () => + new Promise((resolve) => { + const machine = createMachine({ + initial: 'inactive', + states: { + inactive: { + on: { + ACTIVATE: 'active' + } + }, + active: {} + } + }); - const App = () => { - const service = useActorRef(machine); + const App = () => { + const service = useActorRef(machine); - createEffect(() => { - service.subscribe((state) => { - if (state.matches('active')) { - done(); - } + createEffect(() => { + service.subscribe((state) => { + if (state.matches('active')) { + resolve(); + } + }); }); - }); - return ( -