Introduce Vitest (#478)

Export-Source-Commit: 6242977d8081c01f4f88446ea7e74101a89fb8cf
This commit is contained in:
Ryan Clark
2026-08-25 11:35:10 -05:00
committed by Gus Rivera
parent 700bdada2a
commit 7cee3c41ac
29 changed files with 838 additions and 139 deletions
+76
View File
@@ -252,6 +252,82 @@
"jest-dom/prefer-to-have-value": "error"
}
},
{
"files": ["**/*.vitest.{ts,tsx}"],
"plugins": ["vitest"],
"rules": {
"vitest/expect-expect": "warn",
"vitest/no-alias-methods": "error",
"vitest/no-commented-out-tests": "warn",
"vitest/no-conditional-expect": "error",
"vitest/no-conditional-tests": "error",
"vitest/no-focused-tests": "error",
"vitest/no-identical-title": "error",
"vitest/no-interpolation-in-snapshots": "error",
"vitest/no-mocks-import": "error",
"vitest/no-standalone-expect": "error",
"vitest/no-test-prefixes": "error",
"vitest/prefer-importing-vitest-globals": "error",
"vitest/valid-describe-callback": "error",
"vitest/valid-expect": "error",
"vitest/valid-expect-in-promise": "error",
"vitest/valid-title": "error",
"vitest/no-large-snapshots": ["warn", { "maxSize": 200 }],
"vitest/prefer-comparison-matcher": "error",
"vitest/prefer-each": "error",
"vitest/prefer-expect-type-of": "error",
"vitest/prefer-hooks-in-order": "error",
"vitest/prefer-hooks-on-top": "error",
"vitest/prefer-mock-promise-shorthand": "error",
"vitest/prefer-to-be": "error",
"vitest/prefer-to-be-object": "error",
"vitest/prefer-to-contain": "error",
"vitest/prefer-to-have-length": "error",
"vitest/prefer-todo": "error",
"testing-library/await-async-events": [
"error",
{ "eventModule": "userEvent" }
],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-events": [
"error",
{ "eventModules": ["fire-event"] }
],
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "react"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-manual-cleanup": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-unnecessary-act": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error",
"jest-dom/prefer-checked": "error",
"jest-dom/prefer-empty": "error",
"jest-dom/prefer-enabled-disabled": "error",
"jest-dom/prefer-focus": "error",
"jest-dom/prefer-in-document": "error",
"jest-dom/prefer-required": "error",
"jest-dom/prefer-to-have-attribute": "error",
"jest-dom/prefer-to-have-class": "error",
"jest-dom/prefer-to-have-style": "error",
"jest-dom/prefer-to-have-text-content": "error",
"jest-dom/prefer-to-have-value": "error"
}
},
{
"files": [
"**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)",
+9 -7
View File
@@ -39,19 +39,21 @@
"vite/**/*.{ts,mts,mjs}",
"jest/**/*.{ts,js}",
"storybook.ts",
"**/*.test.{ts,tsx,js,jsx}"
"**/*.{test,vitest}.{ts,tsx,js,jsx}"
],
// These are loaded by name through the SWC plugin config in vite/react.mjs.
"ignoreDependencies": [
"@gravitational/swc-plugin-jsx-marker",
"@gravitational/swc-plugin-styled-components",
"@swc/core"
"@swc/core",
// Named as a string by `environment` in vitest/config.mts, which Vitest resolves at run time.
"happy-dom"
]
},
"web/packages/design": {
"entry": [
"src/**/*.story.{ts,tsx,js,jsx}",
"src/**/*.test.{ts,tsx,js,jsx}"
"src/**/*.{test,vitest}.{ts,tsx,js,jsx}"
],
"project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}", "@types/*.d.ts"],
"paths": {
@@ -62,7 +64,7 @@
"ignore": ["src/**/*.type-test.tsx"]
},
"web/packages/shared": {
"entry": ["**/*.story.{ts,tsx,js,jsx}", "**/*.test.{ts,tsx,js,jsx}"],
"entry": ["**/*.story.{ts,tsx,js,jsx}", "**/*.{test,vitest}.{ts,tsx,js,jsx}"],
"project": ["**/*.{ts,tsx,js,jsx,mts,mjs}", "!libs/ironrdp/pkg/**"],
"paths": {
"design/*": ["../design/src/*"],
@@ -82,7 +84,7 @@
"babel.config.js",
"index.html",
"src/**/*.story.{ts,tsx,js,jsx}",
"src/**/*.test.{ts,tsx,js,jsx}",
"src/**/*.{test,vitest}.{ts,tsx,js,jsx}",
"src/services/audit/gen-event-reference/index.ts"
],
"project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}"],
@@ -119,7 +121,7 @@
"src/sharedProcess/sharedProcess.ts",
"src/agentCleanupDaemon/agentCleanupDaemon.js",
"src/**/*.story.{ts,tsx,js,jsx}",
"src/**/*.test.{ts,tsx,js,jsx}",
"src/**/*.{test,vitest}.{ts,tsx,js,jsx}",
"src/**/*Process.mjs"
],
"project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}", "csp.ts"],
@@ -145,7 +147,7 @@
"babel.config.js",
"index.html",
"src/**/*.story.{ts,tsx,js,jsx}",
"src/**/*.test.{ts,tsx,js,jsx}",
"src/**/*.{test,vitest}.{ts,tsx,js,jsx}",
"src/SessionRecordings/summary/generate-mitre-lookup.ts"
],
"project": ["src/**/*.{ts,tsx,js,jsx,mts,mjs}"],
+4 -4
View File
@@ -13,10 +13,8 @@
"package-term": "pnpm --filter=@gravitational/teleterm package",
"storybook": "./web/scripts/run-storybook.sh",
"storybook-smoke-test": "storybook dev -p 9002 -c web/.storybook --ci --smoke-test",
"test": "jest",
"test": "node web/scripts/run-tests.mjs",
"test-coverage": "jest --coverage && web/scripts/print-coverage-link.sh",
"test-update-snapshot": "pnpm run test --updateSnapshot",
"tdd": "jest --watch",
"oxlint": "oxlint --quiet --report-unused-disable-directives-severity error web/ e/ e2e/",
"lint": "pnpm oxlint && pnpm format-check && pnpm knip",
"lint-fix": "pnpm oxlint --fix && pnpm format",
@@ -46,6 +44,7 @@
"@types/react-dom": "^19.2.4",
"@types/react-transition-group": "^4.4.12",
"@types/wicg-file-system-access": "^2023.10.7",
"@vitest/coverage-v8": "^4.1.0",
"eslint": "^10.8.1",
"eslint-plugin-jest-dom": "^5.10.1",
"eslint-plugin-storybook": "^10.5.7",
@@ -62,7 +61,8 @@
"storybook": "^10.5.7",
"svgo": "^4.0.2",
"typescript": "^7.0.2",
"vite": "^8.2.1"
"vite": "^8.2.1",
"vitest": "^4.1.0"
},
"dependencies": {
"@codemirror/autocomplete": "^6.20.3",
+367 -10
View File
@@ -29,13 +29,13 @@ importers:
version: 5.3.0
'@gravitational/design-system':
specifier: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz
version: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/[email protected](@types/[email protected])([email protected])([email protected]))(@fontsource/[email protected])(@sinclair/[email protected])(@tanstack/[email protected]([email protected]))([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
version: https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/[email protected](@types/[email protected])([email protected])([email protected]))(@fontsource/[email protected])(@sinclair/[email protected])(@standard-schema/[email protected])(@tanstack/[email protected]([email protected]))([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
'@grpc/grpc-js':
specifier: 1.14.4
version: 1.14.4
'@hookform/resolvers':
specifier: ^5.7.1
version: 5.7.1(@sinclair/[email protected])([email protected])([email protected]([email protected]))([email protected])
version: 5.7.1(@sinclair/[email protected])(@standard-schema/[email protected])([email protected])([email protected]([email protected]))([email protected])
'@lezer/highlight':
specifier: ^1.2.3
version: 1.2.3
@@ -129,7 +129,7 @@ importers:
version: link:web/packages/build
'@storybook/addon-vitest':
specifier: ^10.5.7
version: 10.5.7([email protected])([email protected])
version: 10.5.7(@vitest/[email protected])([email protected])([email protected])([email protected])
'@storybook/react-vite':
specifier: ^10.5.7
version: 10.5.7(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -163,6 +163,9 @@ importers:
'@types/wicg-file-system-access':
specifier: ^2023.10.7
version: 2023.10.7
'@vitest/coverage-v8':
specifier: ^4.1.0
version: 4.1.10([email protected])
eslint:
specifier: ^10.8.1
version: 10.8.1([email protected])([email protected])
@@ -214,6 +217,9 @@ importers:
vite:
specifier: ^8.2.1
version: 8.2.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])
vitest:
specifier: ^4.1.0
version: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected]([email protected]))([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
e/web/teleport: {}
@@ -261,12 +267,18 @@ importers:
babel-plugin-styled-components:
specifier: ^2.3.0
version: 2.3.0(@babel/[email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])
happy-dom:
specifier: ^20.10.6
version: 20.11.2
jest-environment-jsdom:
specifier: ^30.4.1
version: 30.4.1([email protected])
jest-fail-on-console:
specifier: ^3.3.4
version: 3.3.4(@jest/[email protected]([email protected]))
jest-styled-components:
specifier: ^7.4.0
version: 7.4.0([email protected]([email protected]([email protected]))([email protected]))
rollup-plugin-visualizer:
specifier: ^7.0.1
version: 7.0.1([email protected])
@@ -276,6 +288,9 @@ importers:
vite-plugin-wasm:
specifier: ^3.6.0
version: 3.6.0([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
vitest-fail-on-console:
specifier: ^0.10.1
version: 0.10.1(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
web/packages/design:
dependencies:
@@ -292,9 +307,6 @@ importers:
'@types/styled-system':
specifier: ^5.1.25
version: 5.1.25
jest-styled-components:
specifier: ^7.4.0
version: 7.4.0([email protected]([email protected]([email protected]))([email protected]))
web/packages/shared:
dependencies:
@@ -1142,6 +1154,10 @@ packages:
'@bcoe/[email protected]':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
'@bcoe/[email protected]':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@cbor-extract/[email protected]':
resolution: {integrity: sha512-ZKZ/F8US7JR92J4DMct6cLW/Y66o2K576+zjlEN/MevH70bFIsB10wkZEQPLzl2oNh2SMGy55xpJ9JoBRl5DOA==}
cpu: [arm64]
@@ -3279,6 +3295,9 @@ packages:
'@so-ric/[email protected]':
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
'@standard-schema/[email protected]':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@standard-schema/[email protected]':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
@@ -3731,12 +3750,18 @@ packages:
'@types/[email protected]':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/[email protected]':
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
'@types/[email protected]':
resolution: {integrity: sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==}
'@types/[email protected]':
resolution: {integrity: sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==}
'@types/[email protected]':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@types/[email protected]':
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
@@ -4052,18 +4077,56 @@ packages:
peerDependencies:
vite: ^4 || ^5 || ^6 || ^7 || ^8
'@vitest/[email protected]':
resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==}
peerDependencies:
'@vitest/browser': 4.1.10
vitest: 4.1.10
peerDependenciesMeta:
'@vitest/browser':
optional: true
'@vitest/[email protected]':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
'@vitest/[email protected]':
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
'@vitest/[email protected]':
resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/[email protected]':
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
'@vitest/[email protected]':
resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
'@vitest/[email protected]':
resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
'@vitest/[email protected]':
resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
'@vitest/[email protected]':
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
'@vitest/[email protected]':
resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
'@vitest/[email protected]':
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
'@vitest/[email protected]':
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
'@webcontainer/[email protected]':
resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==}
@@ -4645,6 +4708,9 @@ packages:
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
engines: {node: '>=4'}
[email protected]:
resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==}
[email protected]:
resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==}
engines: {node: '>=0.12.0'}
@@ -4825,6 +4891,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
[email protected]:
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
engines: {node: '>=4.0'}
[email protected]:
resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==}
engines: {node: '>=12.0.0'}
@@ -4886,10 +4956,18 @@ packages:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
[email protected]:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
[email protected]:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
@@ -5344,6 +5422,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
[email protected]:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'}
[email protected]:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -5484,6 +5566,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
[email protected]:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
[email protected]:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -5503,6 +5588,10 @@ packages:
resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==}
engines: {node: '>= 0.8.0'}
[email protected]:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -5727,6 +5816,10 @@ packages:
resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
[email protected]:
resolution: {integrity: sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==}
engines: {node: '>=20.0.0'}
[email protected]:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -6119,6 +6212,9 @@ packages:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
[email protected]:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -6345,6 +6441,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
[email protected]:
resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==}
[email protected]:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
@@ -6558,6 +6657,10 @@ packages:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
[email protected]:
resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
engines: {node: '>=12.20.0'}
[email protected]:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -6691,6 +6794,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
[email protected]:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
[email protected]:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
@@ -7128,6 +7234,9 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
[email protected]:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
[email protected]:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -7193,6 +7302,9 @@ packages:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'}
[email protected]:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
[email protected]:
resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
engines: {node: '>= 6'}
@@ -7201,6 +7313,9 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
[email protected]:
resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
[email protected]:
resolution: {integrity: sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==}
hasBin: true
@@ -7382,6 +7497,13 @@ packages:
[email protected]:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
[email protected]:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
[email protected]:
resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
@@ -7394,6 +7516,10 @@ packages:
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
[email protected]:
resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
engines: {node: '>=14.0.0'}
[email protected]:
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
engines: {node: '>=14.0.0'}
@@ -7679,6 +7805,54 @@ packages:
yaml:
optional: true
[email protected]:
resolution: {integrity: sha512-Xjy2SpgND547qSy0s0zYVnh1G/WyGtdjAbi4PFV8mkYRmTq+6NzRUJYdc08BHrw7HJLpO2kMxHFB8PWn7FOVsg==}
peerDependencies:
'@vitest/utils': '>=0.26.2'
vite: '>=4.5.2'
vitest: '>=0.26.2'
[email protected]:
resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.10
'@vitest/browser-preview': 4.1.10
'@vitest/browser-webdriverio': 4.1.10
'@vitest/coverage-istanbul': 4.1.10
'@vitest/coverage-v8': 4.1.10
'@vitest/ui': 4.1.10
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
[email protected]:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
@@ -7715,6 +7889,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
[email protected]:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
@@ -7743,6 +7921,11 @@ packages:
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
hasBin: true
[email protected]:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
engines: {node: '>= 12.0.0'}
@@ -8821,6 +9004,8 @@ snapshots:
'@bcoe/[email protected]': {}
'@bcoe/[email protected]': {}
'@cbor-extract/[email protected]':
optional: true
@@ -9472,14 +9657,14 @@ snapshots:
'@fontsource/[email protected]': {}
'@gravitational/design-system@https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/[email protected](@types/[email protected])([email protected])([email protected]))(@fontsource/[email protected])(@sinclair/[email protected])(@tanstack/[email protected]([email protected]))([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])':
'@gravitational/design-system@https://github.com/gravitational/design-system/releases/download/v0.2.0/design-system.tgz(@emotion/[email protected](@types/[email protected])([email protected])([email protected]))(@fontsource/[email protected])(@sinclair/[email protected])(@standard-schema/[email protected])(@tanstack/[email protected]([email protected]))([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])':
dependencies:
'@ark-ui/react': 5.37.1([email protected]([email protected]))([email protected])
'@chakra-ui/cli': 3.35.0(@chakra-ui/[email protected](@emotion/[email protected](@types/[email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected]))([email protected])([email protected])
'@chakra-ui/react': 3.35.0(@emotion/[email protected](@types/[email protected])([email protected])([email protected]))([email protected]([email protected]))([email protected])
'@emotion/react': 11.14.0(@types/[email protected])([email protected])([email protected])
'@fontsource/ubuntu': 5.3.0
'@hookform/resolvers': 5.7.1(@sinclair/[email protected])([email protected])([email protected]([email protected]))([email protected])
'@hookform/resolvers': 5.7.1(@sinclair/[email protected])(@standard-schema/[email protected])([email protected])([email protected]([email protected]))([email protected])
'@phosphor-icons/core': 2.1.1
'@phosphor-icons/react': 2.1.10([email protected]([email protected]))([email protected])
'@tanstack/react-query': 5.101.4([email protected])
@@ -9537,12 +9722,13 @@ snapshots:
protobufjs: 7.6.5
yargs: 17.7.3
'@hookform/[email protected](@sinclair/[email protected])([email protected])([email protected]([email protected]))([email protected])':
'@hookform/[email protected](@sinclair/[email protected])(@standard-schema/[email protected])([email protected])([email protected]([email protected]))([email protected])':
dependencies:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.85.0([email protected])
optionalDependencies:
'@sinclair/typebox': 0.34.49
'@standard-schema/spec': 1.1.0
ajv: 8.20.0
zod: 4.4.3
@@ -10690,13 +10876,18 @@ snapshots:
color: 5.0.3
text-hex: 1.0.0
'@standard-schema/[email protected]': {}
'@standard-schema/[email protected]': {}
'@storybook/[email protected]([email protected])([email protected])':
'@storybook/[email protected](@vitest/[email protected])([email protected])([email protected])([email protected])':
dependencies:
'@storybook/global': 5.0.0
'@storybook/icons': 2.1.0([email protected])
storybook: 10.5.7(@storybook/[email protected])(@types/[email protected])([email protected])([email protected])
optionalDependencies:
'@vitest/runner': 4.1.10
vitest: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected]([email protected]))([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
transitivePeerDependencies:
- react
@@ -11128,10 +11319,16 @@ snapshots:
'@types/[email protected]': {}
'@types/[email protected]': {}
'@types/[email protected]': {}
'@types/[email protected]': {}
'@types/[email protected]':
dependencies:
'@types/node': 24.13.3
'@types/[email protected]': {}
'@types/[email protected]':
@@ -11361,6 +11558,20 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
'@vitest/[email protected]([email protected])':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.1.10
ast-v8-to-istanbul: 1.0.5
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-reports: 3.2.0
magicast: 0.5.4
obug: 2.1.4
std-env: 4.2.0
tinyrainbow: 3.1.1
vitest: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected]([email protected]))([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
'@vitest/[email protected]':
dependencies:
'@types/chai': 5.2.3
@@ -11369,20 +11580,62 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/[email protected]':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.10
'@vitest/utils': 4.1.10
chai: 6.2.2
tinyrainbow: 3.1.1
'@vitest/[email protected]([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))':
dependencies:
'@vitest/spy': 4.1.10
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
msw: 2.15.0(@types/[email protected])([email protected])
vite: 8.2.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])
'@vitest/[email protected]':
dependencies:
tinyrainbow: 2.0.0
'@vitest/[email protected]':
dependencies:
tinyrainbow: 3.1.1
'@vitest/[email protected]':
dependencies:
'@vitest/utils': 4.1.10
pathe: 2.0.3
'@vitest/[email protected]':
dependencies:
'@vitest/pretty-format': 4.1.10
'@vitest/utils': 4.1.10
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/[email protected]':
dependencies:
tinyspy: 4.0.4
'@vitest/[email protected]': {}
'@vitest/[email protected]':
dependencies:
'@vitest/pretty-format': 3.2.4
loupe: 3.2.1
tinyrainbow: 2.0.0
'@vitest/[email protected]':
dependencies:
'@vitest/pretty-format': 4.1.10
convert-source-map: 2.0.0
tinyrainbow: 3.1.1
'@webcontainer/[email protected]': {}
'@xmldom/[email protected]': {}
@@ -12651,6 +12904,12 @@ snapshots:
dependencies:
tslib: 2.8.1
[email protected]:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
estree-walker: 3.0.3
js-tokens: 10.0.0
[email protected]: {}
[email protected]: {}
@@ -12853,6 +13112,10 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
'@types/node': 24.13.3
[email protected]([email protected]):
dependencies:
debug: 4.4.3([email protected])
@@ -12938,11 +13201,15 @@ snapshots:
loupe: 3.2.1
pathval: 2.0.1
[email protected]: {}
[email protected]:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -13400,6 +13667,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -13621,6 +13890,10 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
'@types/estree': 1.0.9
[email protected]: {}
[email protected]:
@@ -13645,6 +13918,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
'@jest/expect-utils': 30.4.1
@@ -13909,6 +14184,19 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
'@types/node': 24.13.3
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
buffer-image-size: 0.6.4
entities: 7.0.1
whatwg-mimetype: 3.0.0
ws: 8.21.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
[email protected]: {}
[email protected]:
@@ -14464,6 +14752,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -14675,6 +14965,12 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
[email protected]:
dependencies:
'@babel/parser': 7.29.8
'@babel/types': 7.29.8
source-map-js: 1.2.1
[email protected]:
dependencies:
semver: 7.8.5
@@ -14863,6 +15159,8 @@ snapshots:
[email protected]:
optional: true
[email protected]: {}
[email protected]:
dependencies:
wrappy: 1.0.2
@@ -15080,6 +15378,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -15527,6 +15827,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -15574,10 +15876,14 @@ snapshots:
dependencies:
escape-string-regexp: 2.0.0
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected](@storybook/[email protected])(@types/[email protected])([email protected])([email protected]):
dependencies:
'@storybook/global': 5.0.0
@@ -15809,6 +16115,10 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
fdir: 6.5.0([email protected])
@@ -15818,6 +16128,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -16074,6 +16386,44 @@ snapshots:
tsx: 4.23.11
yaml: 2.9.0
[email protected](@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))([email protected]):
dependencies:
'@vitest/utils': 4.1.10
chalk: 5.6.2
vite: 8.2.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])
vitest: 4.1.10(@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected]([email protected]))([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
[email protected](@opentelemetry/[email protected])(@types/[email protected])(@vitest/[email protected])([email protected])([email protected]([email protected]))([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10([email protected](@types/[email protected])([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected]))
'@vitest/pretty-format': 4.1.10
'@vitest/runner': 4.1.10
'@vitest/snapshot': 4.1.10
'@vitest/spy': 4.1.10
'@vitest/utils': 4.1.10
es-module-lexer: 2.3.1
expect-type: 1.4.0
magic-string: 0.30.21
obug: 2.1.4
pathe: 2.0.3
picomatch: 4.0.5
std-env: 4.2.0
tinybench: 2.9.0
tinyexec: 1.3.0
tinyglobby: 0.2.17
tinyrainbow: 3.1.1
vite: 8.2.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
'@types/node': 24.13.3
'@vitest/coverage-v8': 4.1.10([email protected])
happy-dom: 20.11.2
jsdom: 26.1.0([email protected])
transitivePeerDependencies:
- msw
[email protected]: {}
[email protected]:
@@ -16106,6 +16456,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -16129,6 +16481,11 @@ snapshots:
dependencies:
isexe: 4.0.0
[email protected]:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
[email protected]:
dependencies:
logform: 2.7.0
+32
View File
@@ -0,0 +1,32 @@
/**
* Teleport
* Copyright (C) 2026 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import fs from 'node:fs';
import { createVitestConfig } from './web/packages/build/vitest/config.mts';
// Gradual migration: Vitest runs only *.vitest.{ts,tsx}; everything still on
// *.test.{ts,tsx} keeps running under Jest.
const include = ['web/**/*.vitest.{ts,tsx}'];
// Include enterprise tests when the e/ directory is present.
if (fs.existsSync('e/web')) {
include.push('e/web/**/*.vitest.{ts,tsx}');
}
export default createVitestConfig(include);
+8 -4
View File
@@ -128,18 +128,22 @@ requests to the given target.
### Unit-Tests
We use [jest](https://jestjs.io/) as our testing framework.
We are migrating from [jest](https://jestjs.io/) to [vitest](https://vitest.dev/). Vitest owns
`*.vitest.{ts,tsx}` files. Everything still on `*.test.{ts,tsx}` runs under jest.
To run all jest unit-tests:
To run all unit-tests under both runners:
```
pnpm test
```
To run jest in watch-mode
`pnpm test` also takes paths and file names, routing each to the runner that owns it.
To drive a runner directly, including watch-mode:
```
pnpm tdd
pnpm jest --watch
pnpm vitest
```
### Interactive Testing
+24
View File
@@ -511,6 +511,30 @@ function installCanvasMock() {
const blob = new window.Blob([new Uint8Array(length)], { type: mime });
setTimeout(() => callback(blob), 0);
};
// happy-dom's OffscreenCanvas.getContext returns null; patch in the mock 2d
// context for renderers that draw offscreen (SessionRecordings timeline).
const offscreenProto = (
globalThis as { OffscreenCanvas?: { prototype: any } }
).OffscreenCanvas?.prototype;
if (offscreenProto) {
Object.defineProperty(offscreenProto, 'getContext', {
configurable: true,
writable: true,
value: function getContext(
this: HTMLCanvasElement,
type: string
): MockCanvasRenderingContext2D | null {
if (type !== '2d') return null;
let ctx = contexts.get(this);
if (!ctx) {
ctx = new MockCanvasRenderingContext2D(this);
contexts.set(this, ctx);
}
return ctx;
},
});
}
}
installCanvasMock();
+2
View File
@@ -19,6 +19,8 @@
import crypto from 'node:crypto';
import path from 'node:path';
import '@testing-library/jest-dom';
import 'jest-styled-components';
import { configure as configureTestingLibrary } from '@testing-library/react';
import failOnConsole from 'jest-fail-on-console';
import { configMocks } from 'jsdom-testing-mocks';
+4 -1
View File
@@ -18,10 +18,13 @@
"@swc/core": "^1.15.47",
"@vitejs/plugin-react-swc": "^4.3.3",
"babel-plugin-styled-components": "^2.3.0",
"happy-dom": "^20.10.6",
"jest-environment-jsdom": "^30.4.1",
"jest-fail-on-console": "^3.3.4",
"jest-styled-components": "^7.4.0",
"rollup-plugin-visualizer": "^7.0.1",
"vite-plugin-compression2": "^2.5.3",
"vite-plugin-wasm": "^3.6.0"
"vite-plugin-wasm": "^3.6.0",
"vitest-fail-on-console": "^0.10.1"
}
}
+60
View File
@@ -0,0 +1,60 @@
/**
* Teleport
* Copyright (C) 2026 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Must precede the imports: vmThreads VM contexts capture TZ at creation time.
process.env.TZ = 'UTC';
import { type Plugin, type ViteUserConfig, defineConfig } from 'vitest/config';
import { reactPlugin } from '../vite/react.mjs';
// Stubs assets that aren't real modules under test (images, YAML, and the
// ironrdp ?inline wasm, whose client is mocked by the tests that use it).
function assetStubPlugin(): Plugin {
return {
name: 'vitest-asset-stub',
transform(_code, id) {
if (/\.wasm(\?.*)?$/.test(id)) {
return { code: `export default new Uint8Array();`, map: null };
}
if (/\.(png|svg|yaml)(\?.*)?$/.test(id)) {
return { code: `export default 'file_stub';`, map: null };
}
},
};
}
const standardExclude = ['**/e2e/**', '**/node_modules/**', '**/dist/**'];
export function createVitestConfig(testInclude: string[]): ViteUserConfig {
return defineConfig({
plugins: [reactPlugin('test'), assetStubPlugin()],
resolve: { tsconfigPaths: true },
test: {
include: testInclude,
exclude: standardExclude,
// happy-dom resolves the design system's modern CSS that jsdom's parser drops (breaking toHaveStyle).
environment: 'happy-dom',
environmentOptions: {
happyDOM: { url: 'http://localhost' },
},
setupFiles: ['./web/packages/build/vitest/setup.ts'],
css: false,
},
});
}
+57
View File
@@ -0,0 +1,57 @@
/**
* Teleport
* Copyright (C) 2026 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// The '@testing-library/jest-dom/vitest' entry self-imports 'vitest', which
// pnpm's isolated store can't resolve from inside the jest-dom package, so
// extend expect with the matchers directly.
import * as jestDomMatchers from '@testing-library/jest-dom/matchers';
import { cleanup } from '@testing-library/react';
import '../jest/canvasMock';
import { afterAll, afterEach, beforeAll, expect } from 'vitest';
import failOnConsole from 'vitest-fail-on-console';
import { server } from 'design/utils/testing';
// happy-dom doesn't implement requestIdleCallback (SessionRecordings timeline).
if (typeof globalThis.requestIdleCallback === 'undefined') {
globalThis.requestIdleCallback = ((cb: IdleRequestCallback) =>
setTimeout(
() => cb({ didTimeout: false, timeRemaining: () => 50 } as IdleDeadline),
0
)) as unknown as typeof globalThis.requestIdleCallback;
globalThis.cancelIdleCallback = ((id: number) =>
clearTimeout(id)) as unknown as typeof globalThis.cancelIdleCallback;
}
expect.extend(jestDomMatchers);
failOnConsole();
// @testing-library/react only self-registers this when a global afterEach exists.
afterEach(cleanup);
// React reads this to decide whether act() is supported. RTL only sets it around its own act() calls, so without
// it React warns on every state update and vitest-fail-on-console turns that into a failure.
(
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
+1 -2
View File
@@ -14,7 +14,6 @@
"styled-system": "^5.1.5"
},
"devDependencies": {
"@types/styled-system": "^5.1.25",
"jest-styled-components": "^7.4.0"
"@types/styled-system": "^5.1.25"
}
}
+1 -5
View File
@@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import '@testing-library/jest-dom';
import {
createThemeSystem,
TELEPORT_THEME,
@@ -33,7 +32,6 @@ import {
waitForElementToBeRemoved,
within,
} from '@testing-library/react';
import 'jest-styled-components';
import userEvent from '@testing-library/user-event';
import { HttpResponse, JsonBodyType } from 'msw';
import { setupServer } from 'msw/node';
@@ -79,9 +77,7 @@ function render(
updates / timeouts to finish.
*/
function tick() {
return new Promise<void>(res =>
jest.requireActual('timers').setImmediate(res)
);
return new Promise<void>(res => setTimeout(res, 0));
}
screen.debug = () => {
@@ -18,10 +18,10 @@
import { http, HttpResponse } from 'msw';
import { generatePath, MemoryRouter } from 'react-router';
import { vi, test, afterEach, expect, beforeEach } from 'vitest';
import {
createDeferredResponse,
enableMswServer,
render,
screen,
server,
@@ -37,8 +37,6 @@ import { createTeleportContext } from 'teleport/mocks/contexts';
import { ListSessionRecordingsRoute } from './ListSessionRecordingsRoute';
import { getThumbnail, MOCK_EVENTS, MOCK_THUMBNAIL } from './mock';
enableMswServer();
beforeEach(() => {
server.use(
getThumbnail(MOCK_THUMBNAIL),
@@ -98,7 +96,7 @@ test('displays loading indicator while fetching data', async () => {
});
test('displays error message when request fails', async () => {
jest.spyOn(console, 'error').mockImplementation();
vi.spyOn(console, 'error').mockImplementation(() => {});
const errorMessage = 'Failed to fetch recordings';
@@ -123,7 +121,7 @@ test('displays error message when request fails', async () => {
});
test('retries the request on retry button click', async () => {
jest.spyOn(console, 'error').mockImplementation();
vi.spyOn(console, 'error').mockImplementation(() => {});
const errorMessage = 'Failed to fetch recordings';
@@ -169,7 +167,7 @@ test('retries the request on retry button click', async () => {
});
test('displays an error message on connection failure', async () => {
jest.spyOn(console, 'error').mockImplementation();
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(
http.get(listRecordingsUrl, () => {
@@ -19,9 +19,9 @@
import { format } from 'date-fns';
import { act } from 'react';
import { MemoryRouter } from 'react-router';
import { vi, test, afterEach, expect, beforeEach } from 'vitest';
import {
enableMswServer,
render,
screen,
server,
@@ -38,8 +38,6 @@ import { getThumbnail, MOCK_THUMBNAIL, thumbnailError } from './mock';
import { RecordingItem, type RecordingItemProps } from './RecordingItem';
import { Density, ViewMode } from './ViewSwitcher';
enableMswServer();
const mockRecording: Recording = {
sid: 'test-session',
user: 'alice',
@@ -235,7 +233,7 @@ test('renders all view modes with all densities', async () => {
});
test('shows thumbnail not available if it fails to load', async () => {
jest.spyOn(console, 'error').mockImplementation();
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(thumbnailError());
@@ -263,7 +261,7 @@ test('shows non-interactive session message for non-playable recordings', async
});
test('allows custom action slot', async () => {
const actionComponent = jest
const actionComponent = vi
.fn()
.mockReturnValue(
<button data-testid="custom-action">Custom Action</button>
@@ -16,19 +16,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
enableMswServer,
render,
screen,
server,
testQueryClient,
} from 'design/utils/testing';
import { expect, test, afterEach } from 'vitest';
import { render, screen, server, testQueryClient } from 'design/utils/testing';
import { getThumbnail, MOCK_THUMBNAIL } from './mock';
import { RecordingThumbnail } from './RecordingThumbnail';
enableMswServer();
afterEach(() => {
testQueryClient.clear();
});
@@ -18,9 +18,9 @@
import { http, HttpResponse } from 'msw';
import { generatePath, MemoryRouter } from 'react-router';
import { vi, beforeEach, afterEach, describe, expect, it } from 'vitest';
import {
enableMswServer,
render,
screen,
server,
@@ -48,8 +48,6 @@ import {
import type { RecordingsListState } from './state';
import { Density, ViewMode } from './ViewSwitcher';
enableMswServer();
beforeEach(() => {
server.use(
getThumbnail(MOCK_THUMBNAIL),
@@ -90,10 +88,10 @@ const defaultState: RecordingsListState = {
};
const mockHandlers = {
onFilterChange: jest.fn(),
onPageChange: jest.fn(),
onSearchChange: jest.fn(),
onSortChange: jest.fn(),
onFilterChange: vi.fn(),
onPageChange: vi.fn(),
onSearchChange: vi.fn(),
onSortChange: vi.fn(),
};
const listRecordingsUrl = generatePath(cfg.api.clusterEventsRecordingsPath, {
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { vi, expect, test, afterEach } from 'vitest';
import { render, screen, userEvent } from 'design/utils/testing';
import {
@@ -29,7 +31,7 @@ const defaultProps: RecordingsPaginationProps = {
to: 9,
page: 0,
pageSize: 10,
onPageChange: jest.fn(),
onPageChange: vi.fn(),
};
function setupTest(props: Partial<RecordingsPaginationProps> = {}) {
@@ -37,7 +39,7 @@ function setupTest(props: Partial<RecordingsPaginationProps> = {}) {
}
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
function getPaginationIndicator() {
@@ -93,7 +95,7 @@ test('enables both buttons on middle page', async () => {
});
test('calls onPageChange with previous page when clicking previous', async () => {
const onPageChange = jest.fn();
const onPageChange = vi.fn();
setupTest({
page: 5,
@@ -109,7 +111,7 @@ test('calls onPageChange with previous page when clicking previous', async () =>
});
test('calls onPageChange with next page when clicking next', async () => {
const onPageChange = jest.fn();
const onPageChange = vi.fn();
setupTest({
page: 5,
@@ -125,7 +127,7 @@ test('calls onPageChange with next page when clicking next', async () => {
});
test('does not call onPageChange when clicking disabled previous button', async () => {
const onPageChange = jest.fn();
const onPageChange = vi.fn();
setupTest({
page: 0,
@@ -140,7 +142,7 @@ test('does not call onPageChange when clicking disabled previous button', async
});
test('does not call onPageChange when clicking disabled next button', async () => {
const onPageChange = jest.fn();
const onPageChange = vi.fn();
setupTest({
page: 9,
@@ -158,7 +160,7 @@ test('does not call onPageChange when clicking disabled next button', async () =
test('shows fetch more button when fetchMoreAvailable is true', async () => {
setupTest({
fetchMoreAvailable: true,
onFetchMore: jest.fn(),
onFetchMore: vi.fn(),
});
expect(screen.getByText('Fetch More')).toBeInTheDocument();
@@ -167,7 +169,7 @@ test('shows fetch more button when fetchMoreAvailable is true', async () => {
test('does not show fetch more button when fetchMoreAvailable is false', async () => {
setupTest({
fetchMoreAvailable: false,
onFetchMore: jest.fn(),
onFetchMore: vi.fn(),
});
expect(screen.queryByText('Fetch More')).not.toBeInTheDocument();
@@ -182,7 +184,7 @@ test('does not show fetch more button when onFetchMore is not provided', async (
});
test('calls onFetchMore when clicking fetch more button', async () => {
const onFetchMore = jest.fn();
const onFetchMore = vi.fn();
setupTest({
fetchMoreAvailable: true,
@@ -199,7 +201,7 @@ test('disables fetch more button when fetchMoreDisabled is true', async () => {
setupTest({
fetchMoreAvailable: true,
fetchMoreDisabled: true,
onFetchMore: jest.fn(),
onFetchMore: vi.fn(),
});
const fetchMoreButton = screen.getByText('Fetch More');
@@ -216,7 +218,7 @@ test('shows error message when fetchMoreError is true', async () => {
});
test('shows retry button instead of fetch more when error occurs', async () => {
const onFetchMore = jest.fn();
const onFetchMore = vi.fn();
setupTest({
fetchMoreAvailable: true,
@@ -229,7 +231,7 @@ test('shows retry button instead of fetch more when error occurs', async () => {
});
test('calls onFetchMore when clicking retry button', async () => {
const onFetchMore = jest.fn();
const onFetchMore = vi.fn();
setupTest({
fetchMoreAvailable: true,
@@ -256,7 +258,7 @@ test('displays correct page indicator for different pages', async () => {
rerender(
<RecordingsPagination
{...defaultProps}
onPageChange={jest.fn()}
onPageChange={vi.fn()}
page={5}
from={50}
to={59}
@@ -269,7 +271,7 @@ test('displays correct page indicator for different pages', async () => {
rerender(
<RecordingsPagination
{...defaultProps}
onPageChange={jest.fn()}
onPageChange={vi.fn()}
page={9}
from={90}
to={99}
@@ -19,6 +19,7 @@
import { act, renderHook } from '@testing-library/react';
import type { PropsWithChildren } from 'react';
import { MemoryRouter, useLocation, useNavigate } from 'react-router';
import { describe, expect, it } from 'vitest';
import { EventRange } from 'teleport/components/EventRangePicker';
@@ -19,6 +19,7 @@
import { EventEmitter } from 'events';
import { act, createRef } from 'react';
import { vi, test, afterEach, expect, beforeEach } from 'vitest';
import { fireEvent, render, screen } from 'design/utils/testing';
@@ -34,11 +35,11 @@ import { DesktopPlayer } from './DesktopPlayer';
class FakePlayerClient {
private events = new EventEmitter();
connect = jest.fn().mockResolvedValue(undefined);
shutdown = jest.fn();
togglePlayPause = jest.fn();
setPlaySpeed = jest.fn();
seekTo = jest.fn((pos: number) => {
connect = vi.fn().mockResolvedValue(undefined);
shutdown = vi.fn();
togglePlayPause = vi.fn();
setPlaySpeed = vi.fn();
seekTo = vi.fn((pos: number) => {
this.emitAnchor({ ms: pos, speed: 1, paused: false });
});
@@ -70,17 +71,19 @@ class FakePlayerClient {
let mockPlayerClient: FakePlayerClient;
jest.mock('teleport/lib/tdp', () => ({
PlayerClient: jest.fn().mockImplementation(() => mockPlayerClient),
vi.mock('teleport/lib/tdp', () => ({
PlayerClient: vi.fn().mockImplementation(function () {
return mockPlayerClient;
}),
}));
beforeEach(() => {
jest.useFakeTimers();
vi.useFakeTimers();
mockPlayerClient = new FakePlayerClient();
});
afterEach(() => {
jest.useRealTimers();
vi.useRealTimers();
});
const INACTIVITY_EVENT: SessionRecordingEvent = {
@@ -110,12 +113,12 @@ function startPlayback() {
function advance(ms: number) {
act(() => {
jest.advanceTimersByTime(ms);
vi.advanceTimersByTime(ms);
});
}
test('emits rAF-smoothed time updates from time anchors', () => {
const onTimeChange = jest.fn();
const onTimeChange = vi.fn();
renderPlayer({ onTimeChange });
startPlayback();
@@ -134,7 +137,7 @@ test('emits rAF-smoothed time updates from time anchors', () => {
});
test('interpolates at the anchor speed', () => {
const onTimeChange = jest.fn();
const onTimeChange = vi.fn();
renderPlayer({ onTimeChange });
startPlayback();
@@ -150,7 +153,7 @@ test('interpolates at the anchor speed', () => {
});
test('holds the time while paused', () => {
const onTimeChange = jest.fn();
const onTimeChange = vi.fn();
renderPlayer({ onTimeChange });
startPlayback();
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { describe, expect, it } from 'vitest';
import { theme } from 'design/utils/testing';
import {
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { vi, test, afterEach, expect, beforeEach } from 'vitest';
import { theme } from 'design/utils/testing';
import { SessionRecordingThumbnail } from 'teleport/services/recordings';
@@ -24,10 +26,10 @@ import type { TimelineRenderContext } from 'teleport/SessionRecordings/view/Time
import { FramesRenderer, type LoadedImageResult } from './FramesRenderer';
// Mock the SVG utilities
jest.mock('teleport/SessionRecordings/svg', () => ({
generateTerminalSVGStyleTag: jest.fn(() => '<style></style>'),
injectSVGStyles: jest.fn(svg => svg),
svgToDataURIBase64: jest.fn(svg => `data:image/svg+xml;base64,${svg}`),
vi.mock('teleport/SessionRecordings/svg', () => ({
generateTerminalSVGStyleTag: vi.fn(() => '<style></style>'),
injectSVGStyles: vi.fn(svg => svg),
svgToDataURIBase64: vi.fn(svg => `data:image/svg+xml;base64,${svg}`),
}));
beforeEach(() => {
@@ -37,7 +39,12 @@ beforeEach(() => {
value: 1,
});
global.OffscreenCanvas = jest.fn().mockImplementation((width, height) => {
// Regular function, not an arrow: it's constructed with `new` and arrows
// aren't constructors.
global.OffscreenCanvas = vi.fn().mockImplementation(function (
width: number,
height: number
) {
const canvas = document.createElement('canvas');
canvas.width = width;
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { expect, test } from 'vitest';
import { theme } from 'design/utils/testing';
import { SessionRecordingMetadata } from 'teleport/services/recordings';
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { describe, expect, it } from 'vitest';
import { theme } from 'design/utils/testing';
import { SessionRecordingMetadata } from 'teleport/services/recordings';
@@ -266,7 +268,7 @@ describe('visibility', () => {
// Should only render markers visible in the viewport (with 100px buffer)
// At this zoom and offset, should see markers starting from 9 seconds
expect(fillTextCalls.length).toBe(8); // 9s, 10s, 11s, 12s, 13s, 14s, 15s, 16s
expect(fillTextCalls).toHaveLength(8); // 9s, 10s, 11s, 12s, 13s, 14s, 15s, 16s
// Check that we're seeing markers in the expected range
const visibleTexts = fillTextCalls.map(call => call.props.text);
@@ -304,7 +306,7 @@ describe('sub-tick rendering', () => {
// At high zoom with 1-second intervals, should have 7 sub-ticks per interval
// 10 intervals × 7 sub-ticks = 70 sub-ticks total
expect(subTickCalls.length).toBe(70);
expect(subTickCalls).toHaveLength(70);
});
});
@@ -333,9 +335,9 @@ describe('label spacing', () => {
// At very low zoom, should show 60-second intervals with label skipping
// Should render 11 tick marks (one every 60s for 10 minutes)
expect(fillRectCalls.length).toBe(11);
expect(fillRectCalls).toHaveLength(11);
// Should show only 4 labels due to label skipping
expect(fillTextCalls.length).toBe(4);
expect(fillTextCalls).toHaveLength(4);
expect(fillTextCalls[0].props.text).toBe('0:00');
expect(fillTextCalls[1].props.text).toBe('3:00');
@@ -366,7 +368,7 @@ describe('label spacing', () => {
);
// When markers are well-spaced, should show all labels
expect(fillTextCalls.length).toBe(fillRectCalls.length);
expect(fillTextCalls).toHaveLength(fillRectCalls.length);
});
it('should render tick marks even when labels are hidden', () => {
@@ -391,7 +393,7 @@ describe('label spacing', () => {
);
// Should render 6 tick marks (one every 60s for 5 minutes)
expect(fillRectCalls.length).toBe(6);
expect(fillRectCalls).toHaveLength(6);
});
it('should use 30-second intervals when very zoomed out', () => {
@@ -415,7 +417,7 @@ describe('label spacing', () => {
// Should use 30-second intervals when pixelsPerSecond < 5
// With label skipping (every 10th marker), should show 7 labels
expect(fillTextCalls.length).toBe(7);
expect(fillTextCalls).toHaveLength(7);
});
it('should use 60-second intervals when extremely zoomed out', () => {
@@ -439,6 +441,6 @@ describe('label spacing', () => {
// Should use 60-second intervals when pixelsPerSecond < 2
// With label skipping (every 10th marker), should show 7 labels
expect(fillTextCalls.length).toBe(7);
expect(fillTextCalls).toHaveLength(7);
});
});
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { vi, beforeEach, afterEach, expect, describe, it } from 'vitest';
import { theme } from 'design/utils/testing';
import {
@@ -127,22 +129,22 @@ function createRenderer(
let animationFrameCallback: FrameRequestCallback | null = null;
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
// Mock requestAnimationFrame to capture the callback
global.requestAnimationFrame = jest.fn(callback => {
global.requestAnimationFrame = vi.fn(callback => {
animationFrameCallback = callback;
return 1;
});
global.cancelAnimationFrame = jest.fn();
global.cancelAnimationFrame = vi.fn();
// Mock performance.now
jest.spyOn(performance, 'now').mockReturnValue(0);
vi.spyOn(performance, 'now').mockReturnValue(0);
});
afterEach(() => {
jest.restoreAllMocks();
vi.restoreAllMocks();
animationFrameCallback = null;
});
@@ -407,7 +409,7 @@ describe('wheel accumulation cleanup', () => {
renderer.accumulatePan(50);
// Simulate time passing
jest.spyOn(performance, 'now').mockReturnValue(50);
vi.spyOn(performance, 'now').mockReturnValue(50);
for (let i = 0; i < 50; i++) {
if (animationFrameCallback) {
@@ -21,17 +21,12 @@ import { screen } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import type { ComponentType } from 'react';
import { MemoryRouter, Route, Routes } from 'react-router';
import { vi, expect, test, afterEach } from 'vitest';
import {
enableMswServer,
render,
server,
testQueryClient,
} from 'design/utils/testing';
import { render, server, testQueryClient } from 'design/utils/testing';
import { ContextProvider } from 'teleport';
import cfg from 'teleport/config';
import { MockAuthenticatedWebSocket } from 'teleport/lib/AuthenticatedWebSocket.mock';
import { createTeleportContext } from 'teleport/mocks/contexts';
import {
type RecordingType,
@@ -42,19 +37,21 @@ import type { RecordingWithSummaryProps } from 'teleport/SessionRecordings/view/
import { createMetadataHandler } from './mock';
import { ViewSessionRecordingRoute } from './ViewSessionRecordingRoute';
jest.spyOn(cfg, 'getSessionRecordingMetadataUrl').mockImplementation(() => {
return 'ws://localhost/v1/webapi/sites/:clusterId/sessionrecording/:sessionId/metadata/ws';
});
vi.spyOn(cfg, 'getSessionRecordingMetadataUrl').mockReturnValue(
'ws://localhost/v1/webapi/sites/:clusterId/sessionrecording/:sessionId/metadata/ws'
);
jest.mock('teleport/lib/AuthenticatedWebSocket', () => ({
AuthenticatedWebSocket: MockAuthenticatedWebSocket,
// Import the mock inside the factory: vi.mock is hoisted above the file's imports, so referencing a top-level
// import binding here risks a before-initialization error.
vi.mock('teleport/lib/AuthenticatedWebSocket', async () => ({
AuthenticatedWebSocket: (
await import('teleport/lib/AuthenticatedWebSocket.mock')
).MockAuthenticatedWebSocket,
}));
enableMswServer();
afterEach(() => {
testQueryClient.clear();
jest.clearAllMocks();
vi.clearAllMocks();
});
const mockMetadata: SessionRecordingMetadata = {
@@ -71,7 +68,7 @@ const mockMetadata: SessionRecordingMetadata = {
};
// mock the RecordingPlayer component
jest.mock('./RecordingPlayer', () => ({
vi.mock('./RecordingPlayer', () => ({
RecordingPlayer: ({ clusterId, sessionId, durationMs, recordingType }) => (
<div data-testid="recording-player">
RecordingPlayer: {clusterId}/{sessionId}/{durationMs}/{recordingType}
@@ -176,7 +173,7 @@ test('renders non-SSH recordings correctly', async () => {
});
test('falls back to the player when the custom summary component fails to load', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(createMetadataHandler({ ...mockMetadata, type: 'desktop' }, []));
@@ -278,7 +275,7 @@ test('falls back to loading the duration if metadata is not available and no URL
});
test('falls back to the session player if metadata is not available', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(
createMetadataHandler(mockMetadata, [], {
@@ -304,7 +301,7 @@ test('falls back to the session player if metadata is not available', async () =
});
test('shows error if metadata and duration are not available', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(
createMetadataHandler(mockMetadata, [], {
@@ -18,6 +18,8 @@
import { EventEmitter } from 'events';
import { vi, expect, describe, it } from 'vitest';
import {
PlayerState,
SessionStream,
@@ -47,7 +49,7 @@ describe('play', () => {
it('should not transition to playing whilst loading', () => {
const { stream } = setup();
const stateListener = jest.fn();
const stateListener = vi.fn();
stream.on('state', stateListener);
stream.play();
@@ -122,7 +124,7 @@ describe('pause', () => {
it('should transition from playing to paused', () => {
const { stream, ws } = setup();
const stateListener = jest.fn();
const stateListener = vi.fn();
stream.on('state', stateListener);
stream.loadInitial();
@@ -158,7 +160,7 @@ describe('pause', () => {
it('should not do anything if already paused', () => {
const { stream, ws } = setup();
const stateListener = jest.fn();
const stateListener = vi.fn();
stream.loadInitial();
ws.receiveMessage(
@@ -177,7 +179,7 @@ describe('seek', () => {
it('should emit time event when seeking', () => {
const { stream, ws } = setup();
const timeListener = jest.fn();
const timeListener = vi.fn();
stream.on('time', timeListener);
stream.loadInitial();
@@ -227,7 +229,7 @@ describe('seek', () => {
describe('event handling', () => {
it('should ignore messages with wrong requestId', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {});
vi.spyOn(console, 'warn').mockImplementation(() => {});
const { stream, ws, player } = setup();
@@ -243,7 +245,7 @@ describe('event handling', () => {
it('should stop when an end event is encountered', () => {
const { stream, ws } = setup();
const stateListener = jest.fn();
const stateListener = vi.fn();
stream.loadInitial();
@@ -255,8 +257,8 @@ describe('event handling', () => {
])
);
jest.spyOn(performance, 'now').mockReturnValueOnce(0);
jest.spyOn(performance, 'now').mockReturnValueOnce(400);
vi.spyOn(performance, 'now').mockReturnValueOnce(0);
vi.spyOn(performance, 'now').mockReturnValueOnce(400);
stream.on('state', stateListener);
stream.play();
@@ -269,7 +271,7 @@ describe('time events', () => {
it('should emit time events during playback', () => {
const { stream, ws } = setup();
const timeListener = jest.fn();
const timeListener = vi.fn();
stream.on('time', timeListener);
stream.loadInitial();
@@ -388,10 +390,10 @@ class MockPlayer extends Player<MockEvent> {
this.events = [];
}
onPlay = jest.fn();
onPause = jest.fn();
onSeek = jest.fn();
onStop = jest.fn();
onPlay = vi.fn();
onPause = vi.fn();
onSeek = vi.fn();
onStop = vi.fn();
}
interface WebSocketEventMap {
@@ -16,12 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { vi, expect, test } from 'vitest';
import api from 'teleport/services/api';
import RecordingsService from './recordings';
test('fetch session recordings, response formatting', async () => {
jest.spyOn(api, 'get').mockResolvedValue(recordingsJSON);
vi.spyOn(api, 'get').mockResolvedValue(recordingsJSON);
const recordings = new RecordingsService();
const response = await recordings.fetchRecordings('im-a-cluster', {
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env node
/**
* Teleport
* Copyright (C) 2026 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { spawnSync } from 'node:child_process';
// Routes `pnpm test <args>` during the jest -> vitest migration. Vitest owns *.vitest.{ts,tsx} files, Jest owns
// *.test.{ts,tsx}. A positional naming neither (a directory or a bare path fragment) goes to both runners, since
// each one's own testMatch/include already narrows the filter to the files it owns. With no args, both full
// suites run.
//
// For advanced flag combos across a mixture (e.g. `-t <pattern>` alongside both file kinds), run `pnpm jest` or
// `pnpm vitest` directly, since a flag can only be forwarded verbatim to each selected runner. Watching a path is
// one such case: the runners are spawned in sequence, so the first watcher holds the terminal and the second
// never starts.
const args = process.argv.slice(2);
const isVitestFile = arg => arg.includes('.vitest.');
const isJestFile = arg => arg.includes('.test.');
const flags = args.filter(arg => arg.startsWith('-'));
const positionals = args.filter(arg => !arg.startsWith('-'));
const vitestFiles = positionals.filter(isVitestFile);
const jestFiles = positionals.filter(isJestFile);
const paths = positionals.filter(arg => !isVitestFile(arg) && !isJestFile(arg));
const commands = [];
if (args.length === 0) {
// Bare `pnpm test`: run both full suites, unchanged from before the wrapper existed.
commands.push(['jest', []], ['vitest', ['run']]);
} else {
// A path filter can legitimately match files for only one runner, so neither may fail on an empty selection.
const passWithNoTests = paths.length > 0 ? ['--passWithNoTests'] : [];
// Run Jest when it owns a positional, or when only flags were passed so `pnpm test --watch` still starts Jest.
if (jestFiles.length > 0 || paths.length > 0 || vitestFiles.length === 0) {
commands.push([
'jest',
[...passWithNoTests, ...flags, ...jestFiles, ...paths],
]);
}
if (vitestFiles.length > 0 || paths.length > 0) {
commands.push([
'vitest',
['run', ...passWithNoTests, ...flags, ...vitestFiles, ...paths],
]);
}
}
let exitCode = 0;
for (const [command, commandArgs] of commands) {
const { status, error } = spawnSync(command, commandArgs, {
stdio: 'inherit',
});
if (error) {
console.error(error);
process.exit(1);
}
if (status !== 0 && exitCode === 0) {
exitCode = status ?? 1;
}
}
process.exit(exitCode);