├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── ci.yml │ ├── issue-close-require.yml │ ├── issue-labeled.yml │ ├── lock-closed-issues.yml │ └── publish.yml ├── .gitignore ├── .npmrc ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTION.md ├── LICENSE ├── README.md ├── debug-shims.d.ts ├── eslint.config.mjs ├── img ├── cover.png ├── icon.png ├── reveal-in-explorer.png ├── reveal-in-picker.png ├── vitest-extension.png └── vitest-test-file.png ├── language-configuration.json ├── package.json ├── pnpm-lock.yaml ├── samples ├── ast-collector │ ├── .vscode │ │ └── settings.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── add.ts │ │ └── should_included_test.ts │ ├── test │ │ └── each.test.ts │ └── vitest.config.ts ├── basic │ ├── .skip │ │ └── vitest.config.ts │ ├── .vscode │ │ └── settings.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── add.ts │ │ └── should_included_test.ts │ ├── test │ │ ├── .gitignore │ │ ├── add.test.ts │ │ ├── bug.test.ts │ │ ├── console.test.ts │ │ ├── deep │ │ │ └── deeper │ │ │ │ └── deep.test.ts │ │ ├── duplicated.test.ts │ │ ├── each.test.ts │ │ ├── env.test.ts │ │ ├── fail_to_run.test.ts │ │ ├── ignored.test.ts │ │ ├── mul.test.ts │ │ ├── snapshot.test.ts │ │ ├── throw.test.ts │ │ └── using.test.ts │ ├── vite.config.ts │ ├── vitest.config.d.ts │ └── vitest.config.ts ├── browser │ ├── .skip │ │ └── vitest.config.ts │ ├── .vscode │ │ └── settings.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── add.ts │ │ └── should_included_test.ts │ ├── test │ │ ├── .gitignore │ │ ├── __screenshots__ │ │ │ ├── each.test.ts │ │ │ │ ├── testing-all-fail--1----0-1.png │ │ │ │ ├── testing-all-fail--2----0-1.png │ │ │ │ ├── testing-all-fail--3----0-1.png │ │ │ │ ├── testing-first-fail--3----1-1.png │ │ │ │ ├── testing-first-pass--2----1-1.png │ │ │ │ ├── testing-first-pass--3----1-1.png │ │ │ │ ├── testing-last-fail--3----1-1.png │ │ │ │ └── testing-last-pass--3----1-1.png │ │ │ └── env.test.ts │ │ │ │ └── process-env-1.png │ │ ├── add.test.ts │ │ ├── console.test.ts │ │ ├── deep │ │ │ └── deeper │ │ │ │ └── deep.test.ts │ │ ├── duplicated.test.ts │ │ ├── each.test.ts │ │ ├── env.test.ts │ │ ├── fail_to_run.test.ts │ │ ├── ignored.test.ts │ │ ├── mul.test.ts │ │ ├── snapshot.test.ts │ │ └── using.test.ts │ └── vitest.config.ts ├── continuous │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ └── calculator.ts │ ├── test │ │ ├── imports-divide.test.ts │ │ ├── imports-multiply.test.ts │ │ └── no-import.test.ts │ └── vitest.config.ts ├── e2e │ ├── package.json │ ├── pnpm-lock.yaml │ ├── test │ │ ├── fail.test.ts │ │ ├── mix.test.ts │ │ └── pass.test.ts │ ├── vite.config.ts │ └── vitest.config.ts ├── imba │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── app.css │ │ ├── assets │ │ │ └── imba.svg │ │ ├── components │ │ │ └── counter.imba │ │ ├── main.imba │ │ ├── main.js │ │ └── utils.imba │ ├── test │ │ ├── basic.test.imba │ │ └── setup.imba │ ├── tsconfig.json │ └── vite.config.js ├── in-source │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ └── add.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── monorepo-no-root │ ├── .vscode │ │ └── settings.json │ ├── CHANGELOG.md │ ├── package.json │ ├── packages │ │ └── react │ │ │ ├── components │ │ │ └── Link.tsx │ │ │ ├── package.json │ │ │ ├── pnpm-lock.yaml │ │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ ├── basic.test.tsx.snap │ │ │ │ └── no-root-react.test.tsx.snap │ │ │ └── no-root-react.test.tsx │ │ │ ├── tsconfig.json │ │ │ └── vitest.config.ts │ ├── pnpm-lock.yaml │ ├── pnpm-workspace.yaml │ └── test │ │ └── no-root-basic.test.tsx ├── monorepo-nx │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── jest.config.ts │ ├── jest.preset.js │ ├── library │ │ ├── .eslintrc.json │ │ ├── README.md │ │ ├── project.json │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── library.spec.ts │ │ │ │ └── library.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── vite.config.ts │ ├── node │ │ ├── .eslintrc.json │ │ ├── project.json │ │ ├── src │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ ├── main.test.ts │ │ │ └── main.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── vite.config.ts │ ├── nx.json │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.base.json │ └── vitest.workspace.ts ├── monorepo-vitest-workspace │ ├── .vscode │ │ └── settings.json │ ├── package.json │ ├── packages │ │ ├── react copy │ │ │ ├── components │ │ │ │ └── Link.tsx │ │ │ ├── package.json │ │ │ ├── pnpm-lock.yaml │ │ │ ├── test │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── basic.test.tsx.snap │ │ │ │ └── basic.test.tsx │ │ │ ├── tsconfig.json │ │ │ └── vitest.config.ts │ │ └── react │ │ │ ├── components │ │ │ └── Link.tsx │ │ │ ├── package.json │ │ │ ├── pnpm-lock.yaml │ │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ └── basic.test.tsx.snap │ │ │ └── basic.test.tsx │ │ │ ├── tsconfig.json │ │ │ └── vitest.config.ts │ ├── pnpm-lock.yaml │ ├── pnpm-workspace.yaml │ ├── test │ │ └── vitest.config.ts │ └── vitest.config.ts ├── monorepo │ ├── .vscode │ │ └── settings.json │ ├── CHANGELOG.md │ ├── package.json │ ├── packages │ │ └── react │ │ │ ├── components │ │ │ └── Link.tsx │ │ │ ├── package.json │ │ │ ├── pnpm-lock.yaml │ │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ └── basic.test.tsx.snap │ │ │ └── basic.test.tsx │ │ │ ├── tsconfig.json │ │ │ └── vitest.config.ts │ ├── pnpm-lock.yaml │ ├── pnpm-workspace.yaml │ └── vitest.config.ts ├── multi-root-workspace │ ├── rust-project │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── sample.code-workspace ├── multiple-configs │ ├── app1 │ │ ├── test-app1.test.ts │ │ └── vitest.config.js │ ├── app2 │ │ ├── test-app2.test.ts │ │ └── vitest.config.js │ ├── package.json │ └── pnpm-lock.yaml ├── no-config │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ └── add.ts │ └── test │ │ └── add.test.ts ├── readme │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ └── add.ts │ ├── test │ │ └── example.test.ts │ └── vitest.config.ts ├── vite-6 │ ├── .skip │ │ └── vitest.config.ts │ ├── .vscode │ │ └── settings.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── test │ │ ├── fail.test.ts │ │ ├── mix.test.ts │ │ └── pass.test.ts │ ├── vite.config.ts │ ├── vitest.config.d.ts │ └── vitest.config.ts └── vue │ ├── components │ ├── AsyncComp.vue │ ├── AsyncWrapper.vue │ └── Hello.vue │ ├── package.json │ ├── pnpm-lock.yaml │ ├── test │ ├── __snapshots__ │ │ └── basic.test.ts.snap │ ├── async.test.ts │ ├── basic.test.ts │ └── imports.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── scripts ├── ecosystem-ci.mts └── release.mts ├── src ├── api.ts ├── api │ ├── child_process.ts │ ├── pkg.ts │ ├── resolve.ts │ ├── rpc.ts │ ├── terminal.ts │ ├── types.ts │ └── ws.ts ├── config.ts ├── constants.ts ├── coverage.ts ├── debug.ts ├── diagnostic.ts ├── extension.ts ├── log.ts ├── polyfills.ts ├── runner.ts ├── tagsManager.ts ├── testTree.ts ├── testTreeData.ts ├── utils.ts ├── watcher.ts └── worker │ ├── collect.ts │ ├── coverage.ts │ ├── emitter.ts │ ├── index.ts │ ├── init.ts │ ├── reporter.ts │ ├── rpc.ts │ ├── setupFile.ts │ ├── types.ts │ ├── utils.ts │ ├── watcher.ts │ └── worker.ts ├── syntaxes ├── LICENSE └── vitest-snapshot.tmLanguage ├── test-e2e ├── README.md ├── assertions.ts ├── basic.test.ts ├── discovery.test.ts ├── downloadSetup.ts ├── fixtures │ └── collect │ │ ├── todo-globals-suite.ts │ │ └── todo-import-suite.ts ├── helper.ts ├── tester.ts ├── tsconfig.json └── vitest.config.ts ├── test ├── TestData.test.ts ├── config.test.ts ├── pkg.test.ts ├── testdata │ └── discover │ │ └── 00_simple.ts └── tsconfig.json ├── tsconfig.base.json ├── tsconfig.json ├── tsup.config.ts └── vitest.workspace.vscode.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [vitest-dev] 2 | open_collective: vitest 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report an issue with Vitest VSCode Extension 3 | labels: [pending triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 14 | placeholder: Bug description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduction 19 | attributes: 20 | label: Reproduction 21 | description: Please provide a link to a github repo (you can use [examples](https://github.com/vitest-dev/vitest/tree/main/examples) as a base) that can [reproduce](https://stackoverflow.com/help/minimal-reproducible-example) the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided after 3 days, it will be auto-closed. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: output 27 | attributes: 28 | label: Output 29 | description: The content of "Output" tab. You can open this tab by typing "> Show Vitest Output Channel" in a search bar or pressing "See error" on an error message if there is one. Your issue **will be closed** if you do not provide the full output (starting with Vitest version) - do not remove any "irrelevant" information - the maintainer will decide if it is valuable or not. 30 | render: shell 31 | validations: 32 | required: true 33 | - type: input 34 | id: extension-version 35 | attributes: 36 | label: Extension Version 37 | description: Specify version of the extension 38 | validations: 39 | required: true 40 | - type: input 41 | id: vitest-version 42 | attributes: 43 | label: Vitest Version 44 | description: Specify version of Vitest 45 | validations: 46 | required: true 47 | - type: checkboxes 48 | id: checkboxes 49 | attributes: 50 | label: Validations 51 | description: Before submitting the issue, please make sure you do the following 52 | options: 53 | - label: Check that you are using the latest version of the extension 54 | required: true 55 | - label: Check that there isn't [already an issue](https://github.com/vitest-dev/vscode/issues) that reports the same bug to avoid creating a duplicate. 56 | required: true 57 | - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/vitest-dev/vscode/discussions) or join our [Discord Chat Server](https://chat.vitest.dev). 58 | required: true 59 | - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. 60 | required: true 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Chat 4 | url: https://chat.vitest.dev 5 | about: Ask questions and discuss with other Vitest users in real time. 6 | - name: Questions & Discussions 7 | url: https://github.com/vitest-dev/vscode/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 New feature proposal 2 | description: Propose a new feature to be added to Vitest VSCode Extension 3 | labels: ['enhancement: pending triage'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: 'As a developer using Vitest I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: We could provide following implementation... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Follow our [Code of Conduct](https://github.com/vitest-dev/vscode/blob/main/CODE_OF_CONDUCT.md) 40 | required: true 41 | - label: Read the [Contributing Guidelines](https://github.com/vitest-dev/vscode/blob/main/CONTRIBUTION.md). 42 | required: true 43 | - label: Read the [docs](https://vitest.dev/guide/). 44 | required: true 45 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 46 | required: true 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | ci: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | os: [macos-14, windows-latest] 16 | node-version: [20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: pnpm/action-setup@v3 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: pnpm 27 | 28 | - run: pnpm install --frozen-lockfile 29 | - run: pnpm build 30 | - run: pnpm typecheck 31 | - run: pnpm lint 32 | 33 | - name: test 34 | run: | 35 | pnpm test 36 | 37 | - name: test-e2e 38 | run: | 39 | pnpm -C samples/e2e i 40 | pnpm -C samples/monorepo-vitest-workspace i 41 | pnpm -C samples/browser i 42 | npm -C samples/imba i 43 | pnpm test-e2e --retry 2 44 | 45 | - uses: actions/upload-artifact@v4 46 | if: always() 47 | with: 48 | name: test-results-${{ matrix.runs-on }} 49 | path: test-results-${{ matrix.runs-on }} 50 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-require.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close Require 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: needs reproduction 12 | uses: actions-cool/issues-helper@v3 13 | with: 14 | actions: close-issues 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | labels: needs reproduction 17 | inactive-day: 3 18 | -------------------------------------------------------------------------------- /.github/workflows/issue-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Issue Labeled 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | reply-labeled: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: needs reproduction 12 | if: github.event.label.name == 'needs reproduction' 13 | uses: actions-cool/issues-helper@v3 14 | with: 15 | actions: create-comment 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}. Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a GitHub repository or [StackBlitz](https://vitest.new) (you can also use [examples](https://github.com/vitest-dev/vitest/tree/main/examples)). Issues marked with `needs reproduction` will be closed if they have no activity within 3 days. 20 | -------------------------------------------------------------------------------- /.github/workflows/lock-closed-issues.yml: -------------------------------------------------------------------------------- 1 | name: Lock Closed Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | permissions: 8 | issues: write 9 | 10 | jobs: 11 | action: 12 | if: github.repository == 'vitest-dev/vscode' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: dessant/lock-threads@v5 16 | with: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | issue-inactive-days: '14' 19 | # issue-comment: | 20 | # This issue has been locked since it has been closed for more than 14 days. 21 | # 22 | # If you have found a concrete bug or regression related to it, please open a new [bug report](https://github.com/vitejs/vite/issues/new/choose) with a reproduction against the latest Vite version. If you have any other comments you should join the chat at [Vite Land](https://chat.vitejs.dev) or create a new [discussion](https://github.com/vitejs/vite/discussions). 23 | issue-lock-reason: '' 24 | process-only: issues 25 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | permissions: 9 | contents: write 10 | id-token: write 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | environment: Release 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - uses: pnpm/action-setup@v3 23 | 24 | - name: Use Node.js 20 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 20 28 | registry-url: https://registry.npmjs.org/ 29 | cache: pnpm 30 | 31 | - run: pnpm install --frozen-lockfile --prefer-offline 32 | 33 | - uses: actions/github-script@v7 34 | id: checkPrerelease 35 | name: Check if prerelease 36 | with: 37 | script: | 38 | const version = context.ref.replace('refs/tags/v', '').split('.'); 39 | core.setOutput('preRelease', String(version[1] % 2 !== 0)) 40 | 41 | - name: Publish to Visual Studio Marketplace 42 | id: publishToVSMarketplace 43 | uses: HaaLeo/publish-vscode-extension@v1 44 | with: 45 | preRelease: ${{ steps.checkPrerelease.outputs.preRelease == 'true' }} 46 | pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} 47 | registryUrl: https://marketplace.visualstudio.com 48 | dependencies: false 49 | 50 | - name: Publish to Open VSX Registry 51 | uses: HaaLeo/publish-vscode-extension@v1 52 | with: 53 | extensionFile: ${{ steps.publishToVSMarketplace.outputs.vsixPath }} 54 | pat: ${{ secrets.OPEN_VSX_TOKEN }} 55 | 56 | - name: Generate Changelog 57 | if: steps.checkPrerelease.outputs.preRelease == 'false' 58 | run: npx changelogithub 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - name: Generate Prerelease Changelog 63 | if: steps.checkPrerelease.outputs.preRelease == 'true' 64 | run: npx changelogithub --prerelease 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | .DS_Store 7 | .eslintcache 8 | test-results 9 | .yarn 10 | .pnp.* 11 | samples/**/coverage 12 | tests-logs-* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shell-emulator=true -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli' 2 | 3 | export default defineConfig({ 4 | files: 'test/**/*.test.ts', 5 | mocha: { 6 | ui: 'bdd', 7 | preload: 'tsx/cjs', 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Enable the ESlint flat config support 4 | "eslint.experimental.useFlatConfig": true, 5 | 6 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 7 | "typescript.tsc.autoDetect": "off", 8 | // Disable the default formatter, use eslint instead 9 | "prettier.enable": false, 10 | "editor.formatOnSave": false, 11 | 12 | // Auto fix 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll.eslint": "explicit", 15 | "source.organizeImports": "never" 16 | }, 17 | 18 | "vitest.workspaceConfig": "./vitest.workspace.vscode.ts", 19 | "testing.openTesting": "neverOpen", 20 | 21 | // Enable eslint for all supported languages 22 | "eslint.validate": [ 23 | "javascript", 24 | "javascriptreact", 25 | "typescript", 26 | "typescriptreact", 27 | "vue", 28 | "html", 29 | "markdown", 30 | "json", 31 | "jsonc", 32 | "yaml" 33 | ], 34 | "testing.automaticallyOpenTestResults": "neverOpen" 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | node_modeuls/** 12 | samples/** 13 | dist/extension.js.map 14 | test/** 15 | test-e2e/** 16 | patches/** 17 | pnpm-lock.yaml 18 | *.vsix 19 | CHANGELOG.md 20 | vite.config.ts 21 | tsconfig.json 22 | tsconfig.base.json 23 | .github/** 24 | .eslintcache 25 | .vscode-test.mjs 26 | eslint.config.mjs 27 | scripts/** 28 | test-results/** -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code Of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team by sending an e-mail to vitest.dev@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | How to build and test the extension: 4 | 5 | - Run `pnpm install` to install deps 6 | - Run `pnpm dev` 7 | - Select an example from the Run And Debug menu 8 | - Press F5 to run extension 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-Present Vitest Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /debug-shims.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable ts/method-signature-style */ 2 | // https://github.com/microsoft/vscode-js-debug/blob/main/src/typings/vscode-js-debug.d.ts 3 | 4 | declare module '@vscode/js-debug' { 5 | import type * as vscode from 'vscode' 6 | 7 | /** @see {IExports.registerDebugTerminalOptionsProvider} */ 8 | export interface IDebugTerminalOptionsProvider { 9 | /** 10 | * Called when the user creates a JavaScript Debug Terminal. It's called 11 | * with the options js-debug wants to use to create the terminal. It should 12 | * modify and return the options to use in the terminal. 13 | * 14 | * In order to avoid conflicting with existing logic, participants should 15 | * try to modify options in a additive way. For example prefer appending 16 | * to rather than reading and overwriting `options.env.PATH`. 17 | */ 18 | provideTerminalOptions(options: vscode.TerminalOptions): vscode.ProviderResult 19 | } 20 | 21 | /** 22 | * Defines the exports of the `js-debug` extension. Once you have this typings 23 | * file, these can be acquired in your extension using the following code: 24 | * 25 | * ``` 26 | * const jsDebugExt = vscode.extensions.getExtension('ms-vscode.js-debug-nightly') 27 | * || vscode.extensions.getExtension('ms-vscode.js-debug'); 28 | * await jsDebugExt.activate() 29 | * const jsDebug: import('@vscode/js-debug').IExports = jsDebug.exports; 30 | * ``` 31 | */ 32 | export interface IExports { 33 | /** 34 | * Registers a participant used when the user creates a JavaScript Debug Terminal. 35 | */ 36 | registerDebugTerminalOptionsProvider(provider: IDebugTerminalOptionsProvider): vscode.Disposable 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import antfu, { GLOB_SRC } from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | // Disable tests rules because we need to test with various setup 6 | test: false, 7 | // This replaces the old `.gitignore` 8 | ignores: [ 9 | '**/coverage', 10 | '**/*.snap', 11 | '**/bench.json', 12 | '**/testdata', 13 | '**/fixtures', 14 | '**/samples', 15 | 'test', 16 | ], 17 | }, 18 | { 19 | rules: { 20 | // prefer global Buffer to not initialize the whole module 21 | 'node/prefer-global/buffer': 'off', 22 | 'node/prefer-global/process': 'off', 23 | 'no-empty-pattern': 'off', 24 | 'antfu/indent-binary-ops': 'off', 25 | 'unused-imports/no-unused-imports': 'error', 26 | 'curly': 'off', 27 | 'style/member-delimiter-style': [ 28 | 'error', 29 | { 30 | multiline: { delimiter: 'none' }, 31 | singleline: { delimiter: 'semi' }, 32 | }, 33 | ], 34 | 35 | 'ts/no-invalid-this': 'off', 36 | 37 | // TODO: migrate and turn it back on 38 | 'ts/ban-types': 'off', 39 | 40 | 'no-restricted-imports': [ 41 | 'error', 42 | { 43 | paths: ['path'], 44 | }, 45 | ], 46 | 47 | 'import/no-named-as-default': 'off', 48 | }, 49 | }, 50 | { 51 | files: [`packages/*/*.{js,mjs,d.ts}`], 52 | rules: { 53 | 'antfu/no-import-dist': 'off', 54 | }, 55 | }, 56 | { 57 | files: [`packages/${GLOB_SRC}`], 58 | rules: { 59 | 'no-restricted-imports': [ 60 | 'error', 61 | { 62 | paths: ['vitest', 'path'], 63 | }, 64 | ], 65 | }, 66 | }, 67 | { 68 | // these files define vitest as peer dependency 69 | files: [`packages/{coverage-*,ui,browser,web-worker}/${GLOB_SRC}`], 70 | rules: { 71 | 'no-restricted-imports': [ 72 | 'error', 73 | { 74 | paths: ['path'], 75 | }, 76 | ], 77 | }, 78 | }, 79 | { 80 | files: [ 81 | `docs/${GLOB_SRC}`, 82 | ], 83 | rules: { 84 | 'style/max-statements-per-line': 'off', 85 | 'import/newline-after-import': 'off', 86 | 'import/first': 'off', 87 | 'unused-imports/no-unused-imports': 'off', 88 | }, 89 | }, 90 | { 91 | files: [ 92 | `docs/${GLOB_SRC}`, 93 | `packages/web-worker/${GLOB_SRC}`, 94 | `test/web-worker/${GLOB_SRC}`, 95 | ], 96 | rules: { 97 | 'no-restricted-globals': 'off', 98 | }, 99 | }, 100 | { 101 | files: [`packages/vite-node/${GLOB_SRC}`], 102 | rules: { 103 | // false positive on "exports" variable 104 | 'antfu/no-cjs-exports': 'off', 105 | }, 106 | }, 107 | ) 108 | -------------------------------------------------------------------------------- /img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/cover.png -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/icon.png -------------------------------------------------------------------------------- /img/reveal-in-explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/reveal-in-explorer.png -------------------------------------------------------------------------------- /img/reveal-in-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/reveal-in-picker.png -------------------------------------------------------------------------------- /img/vitest-extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/vitest-extension.png -------------------------------------------------------------------------------- /img/vitest-test-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/img/vitest-test-file.png -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"], 10 | ["<", ">"] 11 | ], 12 | "autoClosingPairs": [ 13 | { "open": "{", "close": "}", "notIn": ["comment"] }, 14 | { "open": "[", "close": "]", "notIn": ["comment"] }, 15 | { "open": "(", "close": ")", "notIn": ["comment"] }, 16 | { "open": "'", "close": "'", "notIn": ["comment"] }, 17 | { "open": "\"", "close": "\"", "notIn": ["comment"] }, 18 | { "open": "`", "close": "`", "notIn": ["comment"] }, 19 | { "open": "/**", "close": " */", "notIn": ["string"] } 20 | ], 21 | "surroundingPairs": [ 22 | ["{", "}"], 23 | ["[", "]"], 24 | ["(", ")"], 25 | ["'", "'"], 26 | ["\"", "\""], 27 | ["`", "`"], 28 | ["<", ">"] 29 | ], 30 | "colorizedBracketPairs": [ 31 | ["(", ")"], 32 | ["[", "]"], 33 | ["{", "}"] 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /samples/ast-collector/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.nodeEnv": { 3 | "TEST_CUSTOM_ENV": "hello" 4 | }, 5 | "vitest.experimentalStaticAstCollect": true, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 8 | }, 9 | "workbench.sash.hoverDelay": 2000 10 | } 11 | -------------------------------------------------------------------------------- /samples/ast-collector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run" 10 | }, 11 | "dependencies": { 12 | "birpc": "^0.2.2" 13 | }, 14 | "devDependencies": { 15 | "@vitest/coverage-v8": "^3.0.4", 16 | "vite": "^6.0.11", 17 | "vitest": "^3.0.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/ast-collector/src/add.ts: -------------------------------------------------------------------------------- 1 | 2 | export function add(a: number, b: number) { 3 | return a + b 4 | } 5 | 6 | export function sum(from: number, to: number) { 7 | return (from + to) * (to - from + 1) / 2 8 | } 9 | 10 | export function addError(from: number, to: number) { 11 | doSomething() 12 | return add(from, to) 13 | } 14 | 15 | function doSomething() { 16 | throw new Error('Something went wrong'); 17 | } 18 | -------------------------------------------------------------------------------- /samples/ast-collector/src/should_included_test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('should included', () => { 4 | it('is included because of workspace plugin setting', () => {}) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/ast-collector/test/each.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, test, } from 'vitest' 2 | 3 | describe('testing', (a) => { 4 | it.each([ 5 | [1, 1], [2, 2] 6 | ])(`all pass: %i => %i`, (a, b) => { 7 | expect(a).toBe(b) 8 | }) 9 | test.each` 10 | a | b | expected 11 | ${1} | ${1} | ${2} 12 | ${'a'} | ${'b'} | ${'ab'} 13 | `('table1: returns $expected when $a is added $b', ({ a, b, expected }) => { 14 | expect(a + b).toBe(expected) 15 | }) 16 | }) 17 | 18 | describe.each([1, 2])('testing %s', () => { 19 | it('hello world', () => { 20 | expect(true).toBe(true) 21 | }) 22 | 23 | it.each([3, 4])('testing test %s', (a) => { 24 | expect(a).toBe(a) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /samples/ast-collector/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from 'vite' 6 | 7 | export default defineConfig({ 8 | esbuild: { 9 | target: 'es2022', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /samples/basic/.skip/vitest.config.ts: -------------------------------------------------------------------------------- 1 | throw new Error('do not import') -------------------------------------------------------------------------------- /samples/basic/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.nodeEnv": { 3 | "TEST_CUSTOM_ENV": "hello" 4 | }, 5 | "vitest.experimentalStaticAstCollect": true, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run" 10 | }, 11 | "dependencies": { 12 | "birpc": "^0.2.2" 13 | }, 14 | "devDependencies": { 15 | "@vitest/coverage-v8": "^3.0.4", 16 | "vite": "^6.0.11", 17 | "vitest": "^3.0.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/basic/src/add.ts: -------------------------------------------------------------------------------- 1 | 2 | export function add(a: number, b: number) { 3 | return a + b 4 | } 5 | 6 | export function sum(from: number, to: number) { 7 | return (from + to) * (to - from + 1) / 2 8 | } 9 | 10 | export function addError(from: number, to: number) { 11 | doSomething() 12 | return add(from, to) 13 | } 14 | 15 | function doSomething() { 16 | throw new Error('Something went wrong'); 17 | } 18 | -------------------------------------------------------------------------------- /samples/basic/src/should_included_test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('should included', () => { 4 | it('is included because of workspace plugin setting', () => {}) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/basic/test/.gitignore: -------------------------------------------------------------------------------- 1 | __snapshots__ 2 | -------------------------------------------------------------------------------- /samples/basic/test/add.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { add, sum } from '../src/add' 3 | 4 | describe('addition', () => { 5 | it('add', () => { 6 | expect(add(1, 1)).toBe(2) 7 | }) 8 | 9 | it('sum', () => { 10 | expect(sum(0, 10)).toBe(55) 11 | }) 12 | 13 | it.skip('skipped', () => { 14 | expect(1 + 2).toBe(3) 15 | }) 16 | 17 | it.todo('todo') 18 | it('async task', async () => { 19 | await new Promise(resolve => setTimeout(resolve, 100)) 20 | }) 21 | 22 | it('async task 0.5s', async () => { 23 | await new Promise(resolve => setTimeout(resolve, 500)) 24 | }) 25 | 26 | it('async task 1s', async () => { 27 | await new Promise(resolve => setTimeout(resolve, 1000)) 28 | }) 29 | 30 | it('long task', () => { 31 | let sum = 0 32 | for (let i = 0; i < 2e8; i++) 33 | sum += i 34 | 35 | expect(sum).toBeGreaterThan(1) 36 | }) 37 | }) 38 | 39 | describe('testing', () => { 40 | it('run', () => { 41 | const a = 10 42 | expect(a).toBe(10) 43 | }) 44 | 45 | it('mul', () => { 46 | expect(5 * 5).toBe(25) 47 | }) 48 | 49 | it("mul fail", () => { 50 | expect(5 * 5).toBe(25) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /samples/basic/test/bug.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest'; 2 | test('fail', () => { 3 | expect(1).toEqual(2); 4 | }) -------------------------------------------------------------------------------- /samples/basic/test/console.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) 4 | 5 | describe('console', () => { 6 | it('basic', () => { 7 | console.log([ 8 | 'string', 9 | { hello: 'world' }, 10 | 1234, 11 | /regex/g, 12 | true, 13 | false, 14 | null, 15 | ]) 16 | }) 17 | 18 | it('async', async () => { 19 | console.log('1st') 20 | await sleep(200) 21 | console.log('2nd') 22 | await sleep(200) 23 | console.log('3rd') 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /samples/basic/test/deep/deeper/deep.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('test', () => { 4 | expect(1).toBe(1) 5 | }) -------------------------------------------------------------------------------- /samples/basic/test/duplicated.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | 3 | describe("testing", () => { 4 | test("number 1", () => { }) 5 | }); 6 | describe("testing", () => { 7 | test("number 2", () => { }) 8 | }); 9 | -------------------------------------------------------------------------------- /samples/basic/test/each.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, test, } from 'vitest' 2 | 3 | describe('testing', (a) => { 4 | it.each([ 5 | [1, 1], [2, 2], [3, 3] 6 | ])(`all pass: %i => %i`, (a, b) => { 7 | expect(a).toBe(b) 8 | }) 9 | it.each([ 10 | [1, 1], [2, 1], [3, 1] 11 | ])(`first pass: %i => %i`, (a, b) => { 12 | expect(a).toBe(b) 13 | }) 14 | it.each([ 15 | [1, 1], [2, 2], [3, 1] 16 | ])(`last pass: %i => %i`, (a, b) => { 17 | expect(a).toBe(b) 18 | }) 19 | it.each([ 20 | [1, 1], [2, 2], [3, 1] 21 | ])(`first fail: %i => %i`, (a, b) => { 22 | expect(a).toBe(b) 23 | }) 24 | it.each([ 25 | [1, 1], [2, 2], [3, 1] 26 | ])(`last fail: %i => %i`, (a, b) => { 27 | expect(a).toBe(b) 28 | }) 29 | it.each([ 30 | [1, 0], [2, 0], [3, 0] 31 | ])(`all fail: %i => %i`, (a, b) => { 32 | expect(a).toBe(b) 33 | }) 34 | it.each([ 35 | 1, 2, 3 36 | ])('run %i', (a) => { 37 | expect(a).toBe(a) 38 | }) 39 | it.each([ 40 | [1, 1], [2, 4], [3, 9] 41 | ])('run mul %i', (a,b) => { 42 | expect(a * a).toBe(b) 43 | }) 44 | test.each([ 45 | ["test1", 1], 46 | ["test2", 2], 47 | ["test3", 3], 48 | ])(`%s => %i`, (a, b) => { 49 | expect(a.at(-1)).toBe(`${b}`) 50 | }) 51 | test.each` 52 | a | b | expected 53 | ${1} | ${1} | ${2} 54 | ${'a'} | ${'b'} | ${'ab'} 55 | ${[]} | ${'b'} | ${'b'} 56 | ${{}} | ${'b'} | ${'[object Object]b'} 57 | ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 58 | `('table1: returns $expected when $a is added $b', ({ a, b, expected }) => { 59 | expect(a + b).toBe(expected) 60 | }) 61 | test.each` 62 | a | b | expected 63 | ${{v: 1}} | ${{v: 1}} | ${2} 64 | `('table2: returns $expected when $a.v is added $b.v', ({ a, b, expected }) => { 65 | expect(a.v + b.v).toBe(expected) 66 | }) 67 | test.each([ 68 | { input: 1, add: 1, sum: 2 }, 69 | { input: 2, add: 2, sum: 4 }, 70 | ])('$input + $add = $sum', ({ input, add, sum }) => { 71 | expect(input + add).toBe(sum) 72 | }) 73 | }) 74 | 75 | // 'Test result not fourd' error occurs as both .each patterns are matched 76 | // TODO: Fix this 77 | describe("over matched test patterns", () => { 78 | test.each(['1', '2'])('run %s', (a) => { 79 | expect(a).toBe(String(a)) 80 | }) 81 | test.each(['1', '2'])('run for %s', (a) => { 82 | expect(a).toBe(String(a)) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /samples/basic/test/env.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test('process.env', () => { 4 | expect(process.env.TEST).toBe('true'); 5 | expect(process.env.VITEST).toBe('true'); 6 | expect(process.env.NODE_ENV).toBe('test'); 7 | expect(process.env.VITEST_VSCODE).toBe('true'); 8 | expect(process.env.TEST_CUSTOM_ENV).toBe('hello'); 9 | }); 10 | -------------------------------------------------------------------------------- /samples/basic/test/fail_to_run.test.ts: -------------------------------------------------------------------------------- 1 | test('aaaaaa', () => {}) 2 | -------------------------------------------------------------------------------- /samples/basic/test/ignored.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('ignored test', () => { 4 | it('is ignored because of vitest plugin setting', () => {}) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/basic/test/mul.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('mul', () => { 4 | it.skip('run 1', () => {}) 5 | it('run', () => {}) 6 | }) 7 | -------------------------------------------------------------------------------- /samples/basic/test/snapshot.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('snapshots', () => { 4 | it('string', () => { 5 | expect('bc').toMatchSnapshot() 6 | }) 7 | it('async', async () => { 8 | await new Promise(resolve => setTimeout(resolve, 200)) 9 | expect('bc').toMatchSnapshot() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /samples/basic/test/throw.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { addError } from '../src/add'; 3 | 4 | describe('throw error', () => { 5 | it('passes expecting an error to be thrown', () => { 6 | expect(()=>addError(1, 1)).toThrow() 7 | }) 8 | 9 | it('fails with error thrown', () => { 10 | expect(addError(1, 1)).toBe(2) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /samples/basic/test/using.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | (Symbol as any).dispose ??= Symbol('Symbol.dispose'); 4 | (Symbol as any).asyncDispose ??= Symbol('Symbol.asyncDispose') 5 | 6 | describe('using keyword', () => { 7 | it('dispose', () => { 8 | function getDisposableResource() { 9 | using resource = new SomeDisposableResource() 10 | return resource 11 | } 12 | 13 | const resource = getDisposableResource() 14 | expect(resource.isDisposed).toBe(true) 15 | }) 16 | 17 | it('asyncDispose', async () => { 18 | async function getAsyncDisposableResource() { 19 | await using resource = new SomeAsyncDisposableResource() 20 | return resource 21 | } 22 | 23 | const resource = await getAsyncDisposableResource() 24 | expect(resource.isDisposed).toBe(true) 25 | }) 26 | }) 27 | 28 | class SomeDisposableResource implements Disposable { 29 | public isDisposed = false; 30 | 31 | [Symbol.dispose](): void { 32 | this.isDisposed = true 33 | } 34 | } 35 | 36 | class SomeAsyncDisposableResource implements AsyncDisposable { 37 | public isDisposed = false 38 | 39 | async [Symbol.asyncDispose](): Promise { 40 | await new Promise(resolve => setTimeout(resolve, 0)) 41 | this.isDisposed = true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/basic/vite.config.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | throw new Error('This file should not be executed') 3 | } -------------------------------------------------------------------------------- /samples/basic/vitest.config.d.ts: -------------------------------------------------------------------------------- 1 | declare const ts: true 2 | 3 | // @ts-expect-error this is just a test 4 | throw new Error('This file should not be included by default.') 5 | -------------------------------------------------------------------------------- /samples/basic/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from 'vite' 6 | 7 | export default defineConfig({ 8 | esbuild: { 9 | target: 'es2022', 10 | }, 11 | test: { 12 | include: ['src/should_included_test.ts', 'test/**/*.test.ts'], 13 | exclude: ['test/ignored.test.ts'], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /samples/browser/.skip/vitest.config.ts: -------------------------------------------------------------------------------- 1 | throw new Error('do not import') -------------------------------------------------------------------------------- /samples/browser/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.nodeEnv": { 3 | "TEST_CUSTOM_ENV": "hello" 4 | }, 5 | "[typescript]": { 6 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run" 10 | }, 11 | "dependencies": { 12 | "birpc": "^0.2.2" 13 | }, 14 | "devDependencies": { 15 | "@vitest/browser": "^2.1.8", 16 | "@vitest/coverage-v8": "^2.1.8", 17 | "playwright": "^1.47.0", 18 | "vite": "^5.4.3", 19 | "vitest": "^2.1.8" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/browser/src/add.ts: -------------------------------------------------------------------------------- 1 | export function add(a: number, b: number) { 2 | return a + b 3 | } 4 | 5 | export function sum(from: number, to: number) { 6 | return (from + to) * (to - from + 1) / 2 7 | } 8 | -------------------------------------------------------------------------------- /samples/browser/src/should_included_test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('should included', () => { 4 | it('is included because of workspace plugin setting', () => {}) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/browser/test/.gitignore: -------------------------------------------------------------------------------- 1 | __snapshots__ 2 | -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--1----0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--1----0-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--2----0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--2----0-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--3----0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-all-fail--3----0-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-first-fail--3----1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-first-fail--3----1-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-first-pass--2----1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-first-pass--2----1-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-first-pass--3----1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-first-pass--3----1-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-last-fail--3----1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-last-fail--3----1-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/each.test.ts/testing-last-pass--3----1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/each.test.ts/testing-last-pass--3----1-1.png -------------------------------------------------------------------------------- /samples/browser/test/__screenshots__/env.test.ts/process-env-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/browser/test/__screenshots__/env.test.ts/process-env-1.png -------------------------------------------------------------------------------- /samples/browser/test/add.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { add, sum } from '../src/add' 3 | 4 | describe('addition', () => { 5 | it('add', () => { 6 | expect(add(1, 1)).toBe(2) 7 | }) 8 | 9 | it('sum', () => { 10 | expect(sum(0, 10)).toBe(55) 11 | }) 12 | 13 | it.skip('skipped', () => { 14 | expect(1 + 2).toBe(3) 15 | }) 16 | 17 | it.todo('todo') 18 | it('async task', async () => { 19 | await new Promise(resolve => setTimeout(resolve, 100)) 20 | }) 21 | 22 | it('async task 0.5s', async () => { 23 | await new Promise(resolve => setTimeout(resolve, 500)) 24 | }) 25 | 26 | it('async task 1s', async () => { 27 | await new Promise(resolve => setTimeout(resolve, 1000)) 28 | }) 29 | 30 | it('long task', () => { 31 | let sum = 0 32 | for (let i = 0; i < 2e8; i++) 33 | sum += i 34 | 35 | expect(sum).toBeGreaterThan(1) 36 | }) 37 | }) 38 | 39 | describe('testing', () => { 40 | it('run', () => { 41 | const a = 10 42 | expect(a).toBe(10) 43 | }) 44 | 45 | it('mul', () => { 46 | expect(5 * 5).toBe(25) 47 | }) 48 | 49 | it("mul fail", () => { 50 | expect(5 * 5).toBe(25) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /samples/browser/test/console.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) 4 | 5 | describe('console', () => { 6 | it('basic', () => { 7 | console.log([ 8 | 'string', 9 | { hello: 'world' }, 10 | 1234, 11 | /regex/g, 12 | true, 13 | false, 14 | null, 15 | ]) 16 | }) 17 | 18 | it('async', async () => { 19 | console.log('1st') 20 | await sleep(200) 21 | console.log('2nd') 22 | await sleep(200) 23 | console.log('3rd') 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /samples/browser/test/deep/deeper/deep.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('test', () => { 4 | expect(1).toBe(1) 5 | }) -------------------------------------------------------------------------------- /samples/browser/test/duplicated.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | 3 | describe("testing", () => { 4 | test("number 1", () => { }) 5 | }); 6 | describe("testing", () => { 7 | test("number 2", () => { }) 8 | }); 9 | -------------------------------------------------------------------------------- /samples/browser/test/each.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, test, } from 'vitest' 2 | 3 | describe('testing', (a) => { 4 | it.each([ 5 | [1, 1], [2, 2], [3, 3] 6 | ])(`all pass: %i => %i`, (a, b) => { 7 | expect(a).toBe(b) 8 | }) 9 | it.each([ 10 | [1, 1], [2, 1], [3, 1] 11 | ])(`first pass: %i => %i`, (a, b) => { 12 | expect(a).toBe(b) 13 | }) 14 | it.each([ 15 | [1, 1], [2, 2], [3, 1] 16 | ])(`last pass: %i => %i`, (a, b) => { 17 | expect(a).toBe(b) 18 | }) 19 | it.each([ 20 | [1, 1], [2, 2], [3, 1] 21 | ])(`first fail: %i => %i`, (a, b) => { 22 | expect(a).toBe(b) 23 | }) 24 | it.each([ 25 | [1, 1], [2, 2], [3, 1] 26 | ])(`last fail: %i => %i`, (a, b) => { 27 | expect(a).toBe(b) 28 | }) 29 | it.each([ 30 | [1, 0], [2, 0], [3, 0] 31 | ])(`all fail: %i => %i`, (a, b) => { 32 | expect(a).toBe(b) 33 | }) 34 | it.each([ 35 | 1, 2, 3 36 | ])('run %i', (a) => { 37 | expect(a).toBe(a) 38 | }) 39 | it.each([ 40 | [1, 1], [2, 4], [3, 9] 41 | ])('run mul %i', (a,b) => { 42 | expect(a * a).toBe(b) 43 | }) 44 | test.each([ 45 | ["test1", 1], 46 | ["test2", 2], 47 | ["test3", 3], 48 | ])(`%s => %i`, (a, b) => { 49 | expect(a.at(-1)).toBe(`${b}`) 50 | }) 51 | test.each` 52 | a | b | expected 53 | ${1} | ${1} | ${2} 54 | ${'a'} | ${'b'} | ${'ab'} 55 | ${[]} | ${'b'} | ${'b'} 56 | ${{}} | ${'b'} | ${'[object Object]b'} 57 | ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 58 | `('table1: returns $expected when $a is added $b', ({ a, b, expected }) => { 59 | expect(a + b).toBe(expected) 60 | }) 61 | test.each` 62 | a | b | expected 63 | ${{v: 1}} | ${{v: 1}} | ${2} 64 | `('table2: returns $expected when $a.v is added $b.v', ({ a, b, expected }) => { 65 | expect(a.v + b.v).toBe(expected) 66 | }) 67 | test.each([ 68 | { input: 1, add: 1, sum: 2 }, 69 | { input: 2, add: 2, sum: 4 }, 70 | ])('$input + $add = $sum', ({ input, add, sum }) => { 71 | expect(input + add).toBe(sum) 72 | }) 73 | }) 74 | 75 | // 'Test result not fourd' error occurs as both .each patterns are matched 76 | // TODO: Fix this 77 | describe("over matched test patterns", () => { 78 | test.each(['1', '2'])('run %s', (a) => { 79 | expect(a).toBe(String(a)) 80 | }) 81 | test.each(['1', '2'])('run for %s', (a) => { 82 | expect(a).toBe(String(a)) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /samples/browser/test/env.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test('process.env', () => { 4 | expect(process.env.TEST).toBe('true'); 5 | expect(process.env.VITEST).toBe('true'); 6 | expect(process.env.NODE_ENV).toBe('test'); 7 | expect(process.env.VITEST_VSCODE).toBe('true'); 8 | expect(process.env.TEST_CUSTOM_ENV).toBe('hello'); 9 | }); 10 | -------------------------------------------------------------------------------- /samples/browser/test/fail_to_run.test.ts: -------------------------------------------------------------------------------- 1 | test('aaaaaa', () => {}) 2 | -------------------------------------------------------------------------------- /samples/browser/test/ignored.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('ignored test', () => { 4 | it('is ignored because of vitest plugin setting', () => {}) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/browser/test/mul.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('mul', () => { 4 | it.skip('run 1', () => {}) 5 | it('run', () => {}) 6 | }) 7 | -------------------------------------------------------------------------------- /samples/browser/test/snapshot.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('snapshots', () => { 4 | it('string', () => { 5 | expect('bc').toMatchSnapshot() 6 | }) 7 | it('async', async () => { 8 | await new Promise(resolve => setTimeout(resolve, 200)) 9 | expect('bc').toMatchSnapshot() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /samples/browser/test/using.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | (Symbol as any).dispose ??= Symbol('Symbol.dispose'); 4 | (Symbol as any).asyncDispose ??= Symbol('Symbol.asyncDispose') 5 | 6 | describe('using keyword', () => { 7 | it('dispose', () => { 8 | function getDisposableResource() { 9 | using resource = new SomeDisposableResource() 10 | return resource 11 | } 12 | 13 | const resource = getDisposableResource() 14 | expect(resource.isDisposed).toBe(true) 15 | }) 16 | 17 | it('asyncDispose', async () => { 18 | async function getAsyncDisposableResource() { 19 | await using resource = new SomeAsyncDisposableResource() 20 | return resource 21 | } 22 | 23 | const resource = await getAsyncDisposableResource() 24 | expect(resource.isDisposed).toBe(true) 25 | }) 26 | }) 27 | 28 | class SomeDisposableResource implements Disposable { 29 | public isDisposed = false; 30 | 31 | [Symbol.dispose](): void { 32 | this.isDisposed = true 33 | } 34 | } 35 | 36 | class SomeAsyncDisposableResource implements AsyncDisposable { 37 | public isDisposed = false 38 | 39 | async [Symbol.asyncDispose](): Promise { 40 | await new Promise(resolve => setTimeout(resolve, 0)) 41 | this.isDisposed = true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/browser/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from 'vitest/config' 6 | 7 | export default defineConfig({ 8 | esbuild: { 9 | target: 'es2020', 10 | }, 11 | test: { 12 | include: ['test/**/*.test.ts'], 13 | exclude: ['test/ignored.test.ts'], 14 | browser: { 15 | enabled: true, 16 | name: 'chromium', 17 | headless: true, 18 | provider: 'playwright' 19 | } 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /samples/continuous/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "continuous", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "author": "", 7 | "license": "ISC", 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "vitest run" 11 | }, 12 | "devDependencies": { 13 | "vitest": "^1.4.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/continuous/src/calculator.ts: -------------------------------------------------------------------------------- 1 | export function multiply(a: number, b: number) { 2 | return a * b 3 | } 4 | 5 | export function divide(a: number, b: number) { 6 | return a / b 7 | } 8 | -------------------------------------------------------------------------------- /samples/continuous/test/imports-divide.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'vitest' 2 | import { divide } from '../src/calculator' 3 | import { expect } from 'vitest' 4 | 5 | test('divide', () => { 6 | expect(divide(6, 3)).toBe(2) 7 | }) 8 | 9 | -------------------------------------------------------------------------------- /samples/continuous/test/imports-multiply.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'vitest' 2 | import { multiply } from '../src/calculator' 3 | import { expect } from 'vitest' 4 | 5 | test('multiply', () => { 6 | expect(multiply(2, 3)).toBe(6) 7 | }) 8 | -------------------------------------------------------------------------------- /samples/continuous/test/no-import.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from 'vitest' 2 | import { expect } from 'vitest' 3 | 4 | test('multiply', () => { 5 | expect(2 * 3).toBe(6) 6 | }) 7 | 8 | test('divide', () => { 9 | expect(6 / 3).toBe(2) 10 | }) 11 | -------------------------------------------------------------------------------- /samples/continuous/vitest.config.ts: -------------------------------------------------------------------------------- 1 | // Configure Vitest (https://vitest.dev/config) 2 | 3 | import { defineConfig } from 'vitest/config' 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: false, 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /samples/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/vscode-sample-e2e", 3 | "type": "module", 4 | "private": true, 5 | "version": "1.0.0", 6 | "scripts": { 7 | "test": "vitest" 8 | }, 9 | "devDependencies": { 10 | "vitest": "^3.0.5" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/e2e/test/fail.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('all-fail', () => { 4 | expect(0).toBe(1) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/e2e/test/mix.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('mix-pass', () => { 4 | expect(0).toBe(0) 5 | }) 6 | 7 | it('mix-fail', () => { 8 | expect(0).toBe(1) 9 | }) 10 | -------------------------------------------------------------------------------- /samples/e2e/test/pass.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('all-pass', () => { 4 | expect(0).toBe(0) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/e2e/vite.config.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | throw new Error('This file should not be executed') 3 | } -------------------------------------------------------------------------------- /samples/e2e/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({}) 4 | -------------------------------------------------------------------------------- /samples/imba/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | coverage -------------------------------------------------------------------------------- /samples/imba/README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status](https://api.netlify.com/api/v1/badges/5aad6d39-168c-482a-9f52-3d8cd9e1c8d1/deploy-status)](https://app.netlify.com/sites/vite-imba/deploys) 2 | 3 | _Bootstrapped with [imba-vite-template](https://github.com/imba/imba-vite-template)._ 4 | 5 | Welcome to the Imba Vite template! Let's get you set up and ready to code! 6 | 7 | [![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/imba/imba-vite-template) 8 | 9 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fimba%2Fimba-vite-template) 10 | 11 | ## Deploy examples 12 | 13 | To see what it looks like when you use this template and deploy it, check out the following examples: 14 | 15 | - [Netlify](https://github.com/codeluggage/imba-on-netlify) 16 | - [GitHub Pages](https://codeluggage.github.io/imba-on-github-pages/) 17 | 18 | ## Code structure 19 | 20 | ### `main.imba` 21 | 22 | In `src/main.imba` you see how [Imba styles](https://imba.io/docs/css) work. CSS is clearly scoped in Imba, so you can see [global CSS](https://imba.io/docs/css/syntax#selectors-global-selectors), tag level, and element level. 23 | 24 | Both [assets](https://imba.io/docs/assets) and [components](https://imba.io/docs/components/) are imported and used. Finally, the web application is started by [mounting the tag](https://imba.io/docs/tags/mounting). 25 | 26 | ### `counter.imba` 27 | 28 | In `src/components/counter.imba` you see more about how [tags](https://imba.io/docs/tags), [props](https://imba.io/docs/tags#setting-properties), [state management](https://imba.io/docs/state-management) (which is usually a big, complex topic - but is very lightweight in Imba), and inheriting from the web itself (in this case, the HTML button). There's also a [Vitest in-source component test](https://vitest.dev/guide/in-source.html), showing you how this tag is meant to be used. 29 | 30 | ### `app.css` 31 | 32 | You don't need to use CSS files, because of the powerful scoping of [Imba styles](https://imba.io/docs/css), but this file shows how you can get the best of both worlds. It is imported and used in `src/main.imba`. 33 | 34 | ### `utils.imba` 35 | 36 | To showcase logic without any front end interactions, there's a simple example `src/utils.imba` has in-source testing and 37 | 38 | ### `tests/` 39 | 40 | In `test/basic.test.imba` you see how terse and succinct the testing syntax is with Imba, using [Vitest](https://vitest.dev/). This test is in its own file with the `.test.imba` filename ending, but you can also use inline tests like in `src/components/counter.imba`. 41 | 42 | ## Recommended IDE 43 | 44 | - [VS Code](https://code.visualstudio.com/). 45 | - [Imba extension](https://marketplace.visualstudio.com/items?itemName=scrimba.vsimba) - which is automatically recommended if you open this repository in VSCode. 46 | 47 | ## Available Scripts 48 | 49 | In the project directory, you can run: 50 | 51 | ### `npm dev` 52 | 53 | Runs in development mode on `http://localhost:3000` with hot reloading, linting and detailed error output in the console, and source maps. 54 | 55 | ### `npm run build` 56 | 57 | Builds the app for production to the `dist` folder. From here you can [deploy your app](https://imba.io/guides/deployment) to static hosting. 58 | 59 | ### `npm run preview` 60 | 61 | _NOTE: Requires `npm run build` to have been run first._ 62 | 63 | Preview the production application from the `dist/` folder, just as it will be running on static hosting. 64 | 65 | ### `npm test` 66 | 67 | Run and watch the tests. 68 | 69 | ### `npm run test:ui` 70 | 71 | Run and watch the tests - and open the [Vitest UI](https://vitest.dev/guide/ui.html) 72 | 73 | ## Notes 74 | - This app doesn't have a server. If you need a full stack web application with server logic you can use [imba base template](https://github.com/imba/imba-base-template) or check out [Vite's backend integration guide](https://vitejs.dev/guide/backend-integration.html) 75 | - There is a temporary `src/main.js` file that is still necessary for Vite to work correctly. You don't have to do anything with this file. And this will probably be fixed in a future version of Vite. -------------------------------------------------------------------------------- /samples/imba/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Imba 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/imba/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imba", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "start": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "test": "vitest", 11 | "test:ui": "vitest --ui" 12 | }, 13 | "devDependencies": { 14 | "@testing-library/dom": "^9.3.4", 15 | "@testing-library/jest-dom": "^6.4.2", 16 | "imba": "^2.0.0-alpha.235", 17 | "jsdom": "^24.0.0", 18 | "vite": "^5.1.6", 19 | "vite-plugin-imba": "^0.10.3", 20 | "vitest": "^3.0.5", 21 | "vitest-github-actions-reporter-temp": "^0.8.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/imba/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/imba/src/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | .card { 41 | padding: 2em; 42 | } 43 | 44 | #app { 45 | max-width: 1280px; 46 | margin: 0 auto; 47 | padding: 2rem; 48 | text-align: center; 49 | } 50 | 51 | button { 52 | border-radius: 8px; 53 | border: 1px solid transparent; 54 | padding: 0.6em 1.2em; 55 | font-size: 1em; 56 | font-weight: 500; 57 | font-family: inherit; 58 | background-color: #1a1a1a; 59 | cursor: pointer; 60 | transition: border-color 0.25s; 61 | } 62 | button:hover { 63 | border-color: #646cff; 64 | } 65 | button:focus, 66 | button:focus-visible { 67 | outline: 4px auto -webkit-focus-ring-color; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /samples/imba/src/assets/imba.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/imba/src/components/counter.imba: -------------------------------------------------------------------------------- 1 | import {screen, waitFor} from "@testing-library/dom" 2 | 3 | export default tag Counter < button 4 | prop count = 0 5 | `Count is {count}` 6 | 7 | if import.meta.vitest 8 | it "should be a good counter", do 9 | imba.mount 10 | const counter = screen.getByText("Count is 0") 11 | expect(counter).toBeTruthy! 12 | counter.click! 13 | waitFor do expect(screen.getByText("Count is 1")).toBeTruthy! -------------------------------------------------------------------------------- /samples/imba/src/main.imba: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import Counter from './components/counter.imba' 3 | import logo from "./assets/imba.svg" 4 | 5 | global css 6 | @root 7 | fs:16px lh:24px fw:400 c:white/87 8 | color-scheme: light dark 9 | bgc:#242424 10 | 11 | tag app 12 | css .logo h:6em p:1.5em 13 | 14 |
15 | 16 | # use svg as an svg tag 17 | 18 | 19 | 20 | 21 | 22 | 23 | # use svg as an image 24 | 25 | 26 | "Vite + Imba" 27 | 28 | 29 |

"Check out" 30 | " Imba.io" 31 | ", the Imba documentation website" 32 | "Click on the Vite and Imba logos to learn more!!!" 33 | 34 | imba.mount , document.getElementById "app" 35 | -------------------------------------------------------------------------------- /samples/imba/src/main.js: -------------------------------------------------------------------------------- 1 | import "./main.imba" -------------------------------------------------------------------------------- /samples/imba/src/utils.imba: -------------------------------------------------------------------------------- 1 | export def add(...args) 2 | return args.reduce((do(a, b) a + b), 0) 3 | 4 | if import.meta.vitest 5 | const {it, expect} = import.meta.vitest 6 | it "adds from in-source tests", do 7 | expect(add()).toBe 0 8 | expect(add(1)).toBe 1 9 | expect(add(1, 2, 3)).toBe 6 10 | -------------------------------------------------------------------------------- /samples/imba/test/basic.test.imba: -------------------------------------------------------------------------------- 1 | test "Math.sqrt()", do 2 | expect(Math.sqrt(4)).toBe 2 3 | expect(Math.sqrt(144)).toBe 12 4 | expect(Math.sqrt(2)).toBe Math.SQRT2 5 | 6 | test "JSON", do 7 | const input = 8 | foo: "hello" 9 | bar: "world" 10 | const output = JSON.stringify(input) 11 | expect(output).toBe '{"foo":"hello","bar":"world"}' 12 | assert.deepEqual JSON.parse(output), input, "matches original" 13 | -------------------------------------------------------------------------------- /samples/imba/test/setup.imba: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' 2 | import {vi} from "vitest" 3 | # JSDOM doesn't implement pointer event class 4 | # see https://github.com/jsdom/jsdom/issues/2527 5 | class MockPointerEvent 6 | vi.stubGlobal "PointerEvent", MockPointerEvent 7 | -------------------------------------------------------------------------------- /samples/imba/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": [ 4 | "vitest/importMeta" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /samples/imba/vite.config.js: -------------------------------------------------------------------------------- 1 | import imba from 'imba/plugin'; 2 | import { defineConfig } from 'vite'; 3 | import GithubActionsReporter from 'vitest-github-actions-reporter-temp' 4 | 5 | export default defineConfig({ 6 | plugins: [imba()], 7 | define: { 8 | 'import.meta.vitest': 'undefined', 9 | }, 10 | test: { 11 | globals: true, 12 | include: ["**/*.{test,spec}.{imba,js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 13 | includeSource: ['src/**/*.{imba,js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 14 | environment: "jsdom", 15 | setupFiles: ["./test/setup.imba"], 16 | reporters: process.env.GITHUB_ACTIONS 17 | ? new GithubActionsReporter() 18 | : 'default' 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /samples/in-source/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "in-source", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run" 10 | }, 11 | "devDependencies": { 12 | "vite": "^5.1.6", 13 | "vitest": "^1.4.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/in-source/src/add.ts: -------------------------------------------------------------------------------- 1 | // the implementation 2 | export function add(...args: number[]) { 3 | return args.reduce((a, b) => a + b, 0) 4 | } 5 | 6 | // in-source test suites 7 | if (import.meta.vitest) { 8 | const { it, expect } = import.meta.vitest 9 | it('add', () => { 10 | expect(add()).toBe(0) 11 | expect(add(1)).toBe(1) 12 | expect(add(1, 2, 3)).toBe(6) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /samples/in-source/tsconfig.json: -------------------------------------------------------------------------------- 1 | // tsconfig.json 2 | { 3 | "extends": "../../tsconfig.base.json", 4 | "compilerOptions": { 5 | "types": [ 6 | "vitest/importMeta" 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /samples/in-source/vitest.config.ts: -------------------------------------------------------------------------------- 1 | // vite.config.ts 2 | /// 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | test: { 7 | includeSource: ['src/**/*.{js,ts}'], 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See 4 | [standard-version](https://github.com/conventional-changelog/standard-version) 5 | for commit guidelines. 6 | 7 | ### [1.0.1](https://github.com/vitest-dev/vscode/compare/v0.1.13...v1.0.1) (2022-03-30) 8 | 9 | ### Bug Fixes 10 | 11 | - provide better support for monorepo 12 | ([2843c6e](https://github.com/vitest-dev/vscode/commit/2843c6e6fa95c23558893e77fbefba004f0d475f)) 13 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo", 3 | "version": "1.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "mime-types": "^2.1.35" 13 | }, 14 | "devDependencies": { 15 | "happy-dom": "^2.49.0", 16 | "vite": "^5.1.6", 17 | "vitest": "^1.4.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const STATUS = { 4 | HOVERED: 'hovered', 5 | NORMAL: 'normal', 6 | } 7 | 8 | function Link({ page, children }: any) { 9 | const [status, setStatus] = useState(STATUS.NORMAL) 10 | 11 | const onMouseEnter = () => { 12 | setStatus(STATUS.HOVERED) 13 | } 14 | 15 | const onMouseLeave = () => { 16 | setStatus(STATUS.NORMAL) 17 | } 18 | 19 | return ( 20 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | export default Link 32 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/test-react", 3 | "private": true, 4 | "scripts": { 5 | "coverage": "vitest run --coverage", 6 | "test": "vitest", 7 | "test:ui": "vitest --ui" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.2" 11 | }, 12 | "devDependencies": { 13 | "@types/react": "^17.0.41", 14 | "@types/react-test-renderer": "^17.0.1", 15 | "@vitejs/plugin-react": "1.2.0", 16 | "@vitest/ui": "latest", 17 | "happy-dom": "^2.49.0", 18 | "jsdom": "latest", 19 | "react-test-renderer": "17.0.2", 20 | "vitest": "^1.4.0" 21 | }, 22 | "stackblitz": { 23 | "startCommand": "npm run test:ui" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/test/__snapshots__/basic.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Link changes the class when hovered 1`] = ` 4 | 10 | Anthony Fu 11 | 12 | `; 13 | 14 | exports[`Link changes the class when hovered 2`] = ` 15 | 21 | Anthony Fu 22 | 23 | `; 24 | 25 | exports[`Link changes the class when hovered 3`] = ` 26 | 32 | Anthony Fu 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/test/__snapshots__/no-root-react.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Link changes the class when hovered 1`] = ` 4 | 10 | Anthony Fu 11 | 12 | `; 13 | 14 | exports[`Link changes the class when hovered 2`] = ` 15 | 21 | Anthony Fu 22 | 23 | `; 24 | 25 | exports[`Link changes the class when hovered 3`] = ` 26 | 32 | Anthony Fu 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/test/no-root-react.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import Link from '../components/Link' 4 | 5 | function toJson(component: renderer.ReactTestRenderer) { 6 | const result = component.toJSON() 7 | expect(result).toBeDefined() 8 | expect(result).not.toBeInstanceOf(Array) 9 | return result as renderer.ReactTestRendererJSON 10 | } 11 | 12 | test('Link changes the class when hovered', () => { 13 | const component = renderer.create( 14 | Anthony Fu, 15 | ) 16 | let tree = toJson(component) 17 | expect(tree).toMatchSnapshot() 18 | 19 | // manually trigger the callback 20 | tree.props.onMouseEnter() 21 | 22 | // re-rendering 23 | tree = toJson(component) 24 | expect(tree).toMatchSnapshot() 25 | 26 | // manually trigger the callback 27 | tree.props.onMouseLeave() 28 | // re-rendering 29 | tree = toJson(component) 30 | expect(tree).toMatchSnapshot() 31 | }) 32 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/packages/react/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: true, 8 | environment: 'happy-dom', 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | -------------------------------------------------------------------------------- /samples/monorepo-no-root/test/no-root-basic.test.tsx: -------------------------------------------------------------------------------- 1 | test('Basic in root', () => { 2 | expect(1 + 1).toEqual(2) 3 | }) 4 | -------------------------------------------------------------------------------- /samples/monorepo-nx/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /samples/monorepo-nx/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /samples/monorepo-nx/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | }, 34 | { 35 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 36 | "env": { 37 | "jest": true 38 | }, 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /samples/monorepo-nx/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .nx/cache -------------------------------------------------------------------------------- /samples/monorepo-nx/.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache -------------------------------------------------------------------------------- /samples/monorepo-nx/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo-nx/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /samples/monorepo-nx/README.md: -------------------------------------------------------------------------------- 1 | # Nx 2 | 3 | 4 | 5 | ✨ **This workspace has been generated by [Nx, Smart Monorepos · Fast CI.](https://nx.dev)** ✨ 6 | 7 | ## Generate code 8 | 9 | If you happen to use Nx plugins, you can leverage code generators that might come with it. 10 | 11 | Run `nx list` to get a list of available plugins and whether they have generators. Then run `nx list ` to see what generators are available. 12 | 13 | Learn more about [Nx generators on the docs](https://nx.dev/features/generate-code). 14 | 15 | ## Running tasks 16 | 17 | To execute tasks with Nx use the following syntax: 18 | 19 | ``` 20 | nx <...options> 21 | ``` 22 | 23 | You can also run multiple targets: 24 | 25 | ``` 26 | nx run-many -t 27 | ``` 28 | 29 | ..or add `-p` to filter specific projects 30 | 31 | ``` 32 | nx run-many -t -p 33 | ``` 34 | 35 | Targets can be defined in the `package.json` or `projects.json`. Learn more [in the docs](https://nx.dev/features/run-tasks). 36 | 37 | ## Want better Editor Integration? 38 | 39 | Have a look at the [Nx Console extensions](https://nx.dev/nx-console). It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users. 40 | 41 | ## Ready to deploy? 42 | 43 | Just run `nx build demoapp` to build the application. The build artifacts will be stored in the `dist/` directory, ready to be deployed. 44 | 45 | ## Set up CI! 46 | 47 | Nx comes with local caching already built-in (check your `nx.json`). On CI you might want to go a step further. 48 | 49 | - [Set up remote caching](https://nx.dev/features/share-your-cache) 50 | - [Set up task distribution across multiple machines](https://nx.dev/nx-cloud/features/distribute-task-execution) 51 | - [Learn more how to setup CI](https://nx.dev/recipes/ci) 52 | 53 | ## Explore the Project Graph 54 | Run `nx graph` to show the graph of the workspace. 55 | It will show tasks that you can run with Nx. 56 | 57 | - [Learn more about Exploring the Project Graph](https://nx.dev/core-features/explore-graph) 58 | 59 | ## Connect with us! 60 | 61 | - [Join the community](https://nx.dev/community) 62 | - [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools) 63 | - [Follow us on Twitter](https://twitter.com/nxdevtools) 64 | -------------------------------------------------------------------------------- /samples/monorepo-nx/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nx/jest' 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | } 6 | -------------------------------------------------------------------------------- /samples/monorepo-nx/jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default 2 | 3 | module.exports = { ...nxPreset } 4 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/README.md: -------------------------------------------------------------------------------- 1 | # library 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test library` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "library/src", 5 | "projectType": "library", 6 | "targets": {}, 7 | "tags": [] 8 | } 9 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/library' 2 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/src/lib/library.spec.ts: -------------------------------------------------------------------------------- 1 | import { library } from './library' 2 | 3 | describe('library', () => { 4 | it('should work', () => { 5 | expect(library()).toEqual('library') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/src/lib/library.ts: -------------------------------------------------------------------------------- 1 | export function library(): string { 2 | return 'library' 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "references": [ 7 | { 8 | "path": "./tsconfig.lib.json" 9 | }, 10 | { 11 | "path": "./tsconfig.spec.json" 12 | } 13 | ], 14 | "files": [], 15 | "include": [] 16 | } 17 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "types": ["node"], 6 | "declaration": true, 7 | "outDir": "../dist/out-tsc" 8 | }, 9 | "include": ["src/**/*.ts"], 10 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "vitest/globals", 6 | "vitest/importMeta", 7 | "vite/client", 8 | "node", 9 | "vitest" 10 | ], 11 | "outDir": "../dist/out-tsc" 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vitest.config.ts", 16 | "src/**/*.test.ts", 17 | "src/**/*.spec.ts", 18 | "src/**/*.test.tsx", 19 | "src/**/*.spec.tsx", 20 | "src/**/*.test.js", 21 | "src/**/*.spec.js", 22 | "src/**/*.test.jsx", 23 | "src/**/*.spec.jsx", 24 | "src/**/*.d.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /samples/monorepo-nx/library/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite' 3 | 4 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin' 5 | 6 | export default defineConfig({ 7 | root: __dirname, 8 | cacheDir: '../node_modules/.vite/library', 9 | 10 | plugins: [nxViteTsPaths()], 11 | 12 | // Uncomment this if you are using workers. 13 | // worker: { 14 | // plugins: [ nxViteTsPaths() ], 15 | // }, 16 | 17 | test: { 18 | globals: true, 19 | cache: { 20 | dir: '../node_modules/.vitest', 21 | }, 22 | environment: 'jsdom', 23 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 24 | 25 | reporters: ['default'], 26 | coverage: { 27 | reportsDirectory: '../coverage/library', 28 | provider: 'v8', 29 | }, 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "node/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/esbuild:esbuild", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "platform": "node", 13 | "outputPath": "dist/node", 14 | "format": ["cjs"], 15 | "bundle": false, 16 | "main": "node/src/main.ts", 17 | "tsConfig": "node/tsconfig.app.json", 18 | "assets": ["node/src/assets"], 19 | "generatePackageJson": true, 20 | "esbuildOptions": { 21 | "sourcemap": true, 22 | "outExtension": { 23 | ".js": ".js" 24 | } 25 | } 26 | }, 27 | "configurations": { 28 | "development": {}, 29 | "production": { 30 | "esbuildOptions": { 31 | "sourcemap": false, 32 | "outExtension": { 33 | ".js": ".js" 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "serve": { 40 | "executor": "@nx/js:node", 41 | "defaultConfiguration": "development", 42 | "options": { 43 | "buildTarget": "node:build" 44 | }, 45 | "configurations": { 46 | "development": { 47 | "buildTarget": "node:build:development" 48 | }, 49 | "production": { 50 | "buildTarget": "node:build:production" 51 | } 52 | } 53 | } 54 | }, 55 | "tags": [] 56 | } 57 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitest-dev/vscode/f38c616589b0960663af666c927420ce9552b303/samples/monorepo-nx/node/src/assets/.gitkeep -------------------------------------------------------------------------------- /samples/monorepo-nx/node/src/main.test.ts: -------------------------------------------------------------------------------- 1 | import { library } from '@nx/library' 2 | import { mainFunction } from './main' 3 | 4 | describe('library from app', () => { 5 | it('should work', () => { 6 | expect(library()).toEqual('library') 7 | }) 8 | }) 9 | 10 | describe('main', () => { 11 | it('should work', () => { 12 | expect(mainFunction()).toEqual('hello world') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/src/main.ts: -------------------------------------------------------------------------------- 1 | export function mainFunction() { 2 | return 'hello world' 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "types": ["node"], 6 | "outDir": "../dist/out-tsc" 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true 5 | }, 6 | "references": [ 7 | { 8 | "path": "./tsconfig.app.json" 9 | }, 10 | { 11 | "path": "./tsconfig.spec.json" 12 | } 13 | ], 14 | "files": [], 15 | "include": [] 16 | } 17 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "vitest/globals", 6 | "vitest/importMeta", 7 | "vite/client", 8 | "node", 9 | "vitest" 10 | ], 11 | "outDir": "../dist/out-tsc" 12 | }, 13 | "include": [ 14 | "vite.config.ts", 15 | "vitest.config.ts", 16 | "src/**/*.test.ts", 17 | "src/**/*.spec.ts", 18 | "src/**/*.test.tsx", 19 | "src/**/*.spec.tsx", 20 | "src/**/*.test.js", 21 | "src/**/*.spec.js", 22 | "src/**/*.test.jsx", 23 | "src/**/*.spec.jsx", 24 | "src/**/*.d.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /samples/monorepo-nx/node/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite' 3 | 4 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin' 5 | 6 | export default defineConfig({ 7 | root: __dirname, 8 | cacheDir: '../node_modules/.vite/node', 9 | 10 | plugins: [nxViteTsPaths()], 11 | 12 | // Uncomment this if you are using workers. 13 | // worker: { 14 | // plugins: [ nxViteTsPaths() ], 15 | // }, 16 | 17 | test: { 18 | globals: true, 19 | cache: { 20 | dir: '../node_modules/.vitest', 21 | }, 22 | environment: 'jsdom', 23 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 24 | 25 | reporters: ['default'], 26 | coverage: { 27 | reportsDirectory: '../coverage/node', 28 | provider: 'v8', 29 | }, 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /samples/monorepo-nx/nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 5 | "production": [ 6 | "default", 7 | "!{projectRoot}/.eslintrc.json", 8 | "!{projectRoot}/eslint.config.js", 9 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 10 | "!{projectRoot}/tsconfig.spec.json", 11 | "!{projectRoot}/jest.config.[jt]s", 12 | "!{projectRoot}/src/test-setup.[jt]s", 13 | "!{projectRoot}/test-setup.[jt]s" 14 | ], 15 | "sharedGlobals": [] 16 | }, 17 | "targetDefaults": { 18 | "@nx/esbuild:esbuild": { 19 | "cache": true, 20 | "dependsOn": ["^build"], 21 | "inputs": ["production", "^production"] 22 | } 23 | }, 24 | "plugins": [ 25 | { 26 | "plugin": "@nx/eslint/plugin", 27 | "options": { 28 | "targetName": "lint" 29 | } 30 | }, 31 | { 32 | "plugin": "@nx/jest/plugin", 33 | "options": { 34 | "targetName": "test" 35 | } 36 | }, 37 | { 38 | "plugin": "@nx/vite/plugin", 39 | "options": { 40 | "buildTargetName": "build", 41 | "previewTargetName": "preview", 42 | "testTargetName": "test", 43 | "serveTargetName": "serve", 44 | "serveStaticTargetName": "serve-static" 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /samples/monorepo-nx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nx/source", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": {}, 7 | "dependencies": { 8 | "axios": "^1.6.0", 9 | "jsdom": "^24.0.0", 10 | "tslib": "^2.3.0" 11 | }, 12 | "devDependencies": { 13 | "@nx/esbuild": "18.0.4", 14 | "@nx/eslint": "18.0.4", 15 | "@nx/eslint-plugin": "18.0.4", 16 | "@nx/jest": "18.0.4", 17 | "@nx/js": "18.0.4", 18 | "@nx/node": "18.0.4", 19 | "@nx/vite": "^18.0.4", 20 | "@nx/web": "18.0.4", 21 | "@nx/workspace": "18.0.4", 22 | "@swc-node/register": "~1.8.0", 23 | "@swc/core": "~1.3.85", 24 | "@swc/helpers": "~0.5.2", 25 | "@types/jest": "^29.4.0", 26 | "@types/node": "~18.16.9", 27 | "@typescript-eslint/eslint-plugin": "^6.13.2", 28 | "@typescript-eslint/parser": "^6.13.2", 29 | "@vitest/coverage-v8": "^1.4.0", 30 | "@vitest/ui": "^1.4.0", 31 | "esbuild": "^0.19.2", 32 | "eslint": "~8.48.0", 33 | "eslint-config-prettier": "^9.0.0", 34 | "jest": "^29.4.1", 35 | "jest-environment-node": "^29.4.1", 36 | "nx": "18.0.4", 37 | "prettier": "^2.6.2", 38 | "ts-jest": "^29.1.0", 39 | "ts-node": "10.9.1", 40 | "typescript": "~5.3.2", 41 | "vite": "^5.1.6", 42 | "vitest": "^1.4.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/monorepo-nx/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es2015", 5 | "lib": ["es2020", "dom"], 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "baseUrl": ".", 9 | "rootDir": ".", 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "paths": { 13 | "@nx/library": ["library/src/index.ts"] 14 | }, 15 | "declaration": false, 16 | "importHelpers": true, 17 | "sourceMap": true, 18 | "skipDefaultLibCheck": true, 19 | "skipLibCheck": true 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | -------------------------------------------------------------------------------- /samples/monorepo-nx/vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin' 2 | import { defineWorkspace } from 'vitest/config' 3 | 4 | export default defineWorkspace([ 5 | 'node/**', 6 | { 7 | plugins: [nxViteTsPaths()], 8 | test: { 9 | globals: true, 10 | }, 11 | }, 12 | 'library/**', 13 | { 14 | plugins: [nxViteTsPaths()], 15 | test: { 16 | globals: true, 17 | }, 18 | }, 19 | ]) 20 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo", 3 | "version": "1.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest" 10 | }, 11 | "devDependencies": { 12 | "@vitest/coverage-v8": "^2.1.4", 13 | "happy-dom": "^15.7.4", 14 | "vitest": "^3.0.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const STATUS = { 4 | HOVERED: 'hovered', 5 | NORMAL: 'normal', 6 | } 7 | 8 | function Link({ page, children }: any) { 9 | const [status, setStatus] = useState(STATUS.NORMAL) 10 | 11 | const onMouseEnter = () => { 12 | setStatus(STATUS.HOVERED) 13 | } 14 | 15 | const onMouseLeave = () => { 16 | setStatus(STATUS.NORMAL) 17 | } 18 | 19 | return ( 20 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | export default Link 32 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/test-react-copy", 3 | "private": true, 4 | "scripts": { 5 | "coverage": "vitest run --coverage", 6 | "test": "vitest", 7 | "test:ui": "vitest --ui" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.2" 11 | }, 12 | "devDependencies": { 13 | "@types/react": "^17.0.41", 14 | "@types/react-test-renderer": "^17.0.1", 15 | "@vitejs/plugin-react": "1.2.0", 16 | "happy-dom": "^2.49.0", 17 | "jsdom": "latest", 18 | "react-test-renderer": "17.0.2" 19 | }, 20 | "stackblitz": { 21 | "startCommand": "npm run test:ui" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/test/__snapshots__/basic.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Link changes the class when hovered 1`] = ` 4 | 10 | Anthony Fu 11 | 12 | `; 13 | 14 | exports[`Link changes the class when hovered 2`] = ` 15 | 21 | Anthony Fu 22 | 23 | `; 24 | 25 | exports[`Link changes the class when hovered 3`] = ` 26 | 32 | Anthony Fu 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/test/basic.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import { expect, test } from 'vitest' 4 | import Link from '../components/Link' 5 | 6 | function toJson(component: renderer.ReactTestRenderer) { 7 | const result = component.toJSON() 8 | expect(result).toBeDefined() 9 | expect(result).not.toBeInstanceOf(Array) 10 | return result as renderer.ReactTestRendererJSON 11 | } 12 | 13 | test('Link changes the class when hovered', () => { 14 | const component = renderer.create( 15 | Anthony Fu, 16 | ) 17 | let tree = toJson(component) 18 | expect(tree).toMatchSnapshot() 19 | 20 | // manually trigger the callback 21 | tree.props.onMouseEnter() 22 | 23 | // re-rendering 24 | tree = toJson(component) 25 | expect(tree).toMatchSnapshot() 26 | 27 | // manually trigger the callback 28 | tree.props.onMouseLeave() 29 | // re-rendering 30 | tree = toJson(component) 31 | expect(tree).toMatchSnapshot() 32 | }) 33 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react copy/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config' 2 | 3 | export default defineProject({ 4 | 5 | }) 6 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const STATUS = { 4 | HOVERED: 'hovered', 5 | NORMAL: 'normal', 6 | } 7 | 8 | function Link({ page, children }: any) { 9 | const [status, setStatus] = useState(STATUS.NORMAL) 10 | 11 | const onMouseEnter = () => { 12 | setStatus(STATUS.HOVERED) 13 | } 14 | 15 | const onMouseLeave = () => { 16 | setStatus(STATUS.NORMAL) 17 | } 18 | 19 | return ( 20 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | export default Link 32 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/test-react", 3 | "private": true, 4 | "scripts": { 5 | "coverage": "vitest run --coverage", 6 | "test": "vitest", 7 | "test:ui": "vitest --ui" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.2" 11 | }, 12 | "devDependencies": { 13 | "@types/react": "^17.0.41", 14 | "@types/react-test-renderer": "^17.0.1", 15 | "@vitejs/plugin-react": "1.2.0", 16 | "happy-dom": "^2.49.0", 17 | "jsdom": "latest", 18 | "react-test-renderer": "17.0.2" 19 | }, 20 | "stackblitz": { 21 | "startCommand": "npm run test:ui" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/test/__snapshots__/basic.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Link changes the class when hovered 1`] = ` 4 | 10 | Anthony Fu 11 | 12 | `; 13 | 14 | exports[`Link changes the class when hovered 2`] = ` 15 | 21 | Anthony Fu 22 | 23 | `; 24 | 25 | exports[`Link changes the class when hovered 3`] = ` 26 | 32 | Anthony Fu 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/test/basic.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import { expect, test } from 'vitest' 4 | import Link from '../components/Link' 5 | 6 | function toJson(component: renderer.ReactTestRenderer) { 7 | const result = component.toJSON() 8 | expect(result).toBeDefined() 9 | expect(result).not.toBeInstanceOf(Array) 10 | return result as renderer.ReactTestRendererJSON 11 | } 12 | 13 | test('Link changes the class when hovered', () => { 14 | const component = renderer.create( 15 | Anthony Fu, 16 | ) 17 | let tree = toJson(component) 18 | expect(tree).toMatchSnapshot() 19 | 20 | // manually trigger the callback 21 | tree.props.onMouseEnter() 22 | 23 | // re-rendering 24 | tree = toJson(component) 25 | expect(tree).toMatchSnapshot() 26 | 27 | // manually trigger the callback 28 | tree.props.onMouseLeave() 29 | // re-rendering 30 | tree = toJson(component) 31 | expect(tree).toMatchSnapshot() 32 | }) 33 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/packages/react/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config' 2 | 3 | export default defineProject({ 4 | 5 | }) 6 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/test/vitest.config.ts: -------------------------------------------------------------------------------- 1 | throw new Error('should not be called') -------------------------------------------------------------------------------- /samples/monorepo-vitest-workspace/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | workspace: [ 6 | 'packages/*', 7 | { 8 | test: { 9 | environment: 'happy-dom', 10 | }, 11 | }, 12 | ] 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /samples/monorepo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.enable": true 3 | } 4 | -------------------------------------------------------------------------------- /samples/monorepo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See 4 | [standard-version](https://github.com/conventional-changelog/standard-version) 5 | for commit guidelines. 6 | 7 | ### [1.0.1](https://github.com/vitest-dev/vscode/compare/v0.1.13...v1.0.1) (2022-03-30) 8 | 9 | ### Bug Fixes 10 | 11 | - provide better support for monorepo 12 | ([2843c6e](https://github.com/vitest-dev/vscode/commit/2843c6e6fa95c23558893e77fbefba004f0d475f)) 13 | -------------------------------------------------------------------------------- /samples/monorepo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monorepo", 3 | "version": "1.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "devDependencies": { 12 | "happy-dom": "^2.49.0", 13 | "vitest": "^1.4.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const STATUS = { 4 | HOVERED: 'hovered', 5 | NORMAL: 'normal', 6 | } 7 | 8 | function Link({ page, children }: any) { 9 | const [status, setStatus] = useState(STATUS.NORMAL) 10 | 11 | const onMouseEnter = () => { 12 | setStatus(STATUS.HOVERED) 13 | } 14 | 15 | const onMouseLeave = () => { 16 | setStatus(STATUS.NORMAL) 17 | } 18 | 19 | return ( 20 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | export default Link 32 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/test-react", 3 | "private": true, 4 | "scripts": { 5 | "coverage": "vitest run --coverage", 6 | "test": "vitest", 7 | "test:ui": "vitest --ui" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.2" 11 | }, 12 | "devDependencies": { 13 | "@types/react": "^17.0.41", 14 | "@types/react-test-renderer": "^17.0.1", 15 | "@vitejs/plugin-react": "1.2.0", 16 | "@vitest/ui": "latest", 17 | "happy-dom": "^2.49.0", 18 | "jsdom": "latest", 19 | "react-test-renderer": "17.0.2", 20 | "vite": "^5.1.6", 21 | "vitest": "^1.4.0" 22 | }, 23 | "stackblitz": { 24 | "startCommand": "npm run test:ui" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/test/__snapshots__/basic.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Link changes the class when hovered 1`] = ` 4 | 10 | Anthony Fu 11 | 12 | `; 13 | 14 | exports[`Link changes the class when hovered 2`] = ` 15 | 21 | Anthony Fu 22 | 23 | `; 24 | 25 | exports[`Link changes the class when hovered 3`] = ` 26 | 32 | Anthony Fu 33 | 34 | `; 35 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/test/basic.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import Link from '../components/Link' 4 | 5 | function toJson(component: renderer.ReactTestRenderer) { 6 | const result = component.toJSON() 7 | expect(result).toBeDefined() 8 | expect(result).not.toBeInstanceOf(Array) 9 | return result as renderer.ReactTestRendererJSON 10 | } 11 | 12 | test('Link changes the class when hovered', () => { 13 | const component = renderer.create( 14 | Anthony Fu, 15 | ) 16 | let tree = toJson(component) 17 | expect(tree).toMatchSnapshot() 18 | 19 | // manually trigger the callback 20 | tree.props.onMouseEnter() 21 | 22 | // re-rendering 23 | tree = toJson(component) 24 | expect(tree).toMatchSnapshot() 25 | 26 | // manually trigger the callback 27 | tree.props.onMouseLeave() 28 | // re-rendering 29 | tree = toJson(component) 30 | expect(tree).toMatchSnapshot() 31 | }) 32 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /samples/monorepo/packages/react/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: true, 8 | environment: 'happy-dom', 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /samples/monorepo/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | -------------------------------------------------------------------------------- /samples/monorepo/vitest.config.ts: -------------------------------------------------------------------------------- 1 | // /// 2 | 3 | // import { defineConfig } from 'vite' 4 | 5 | // export default defineConfig({ 6 | // test: { 7 | // globals: true, 8 | // environment: 'happy-dom', 9 | // }, 10 | // }) 11 | -------------------------------------------------------------------------------- /samples/multi-root-workspace/rust-project/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /samples/multi-root-workspace/rust-project/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "rust-project" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /samples/multi-root-workspace/rust-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-project" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /samples/multi-root-workspace/rust-project/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /samples/multi-root-workspace/sample.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "rust", 5 | "path": "./rust-project" 6 | }, 7 | { 8 | "name": "basic", 9 | "path": "../basic" 10 | }, 11 | { 12 | "name": "react", 13 | "path": "../monorepo/packages/react" 14 | }, 15 | { 16 | "name": "no-root-config", 17 | "path": "../monorepo-no-root" 18 | }, 19 | { 20 | "name": "react-no-root", 21 | "path": "../monorepo-no-root/packages/react" 22 | } 23 | ], 24 | "settings": { 25 | "vitest.disabledWorkspaceFolders": ["no-root-config"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/multiple-configs/app1/test-app1.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest' 2 | 3 | test('math', () => { 4 | expect(1 + 1).toBe(2) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/multiple-configs/app1/vitest.config.js: -------------------------------------------------------------------------------- 1 | export default {} -------------------------------------------------------------------------------- /samples/multiple-configs/app2/test-app2.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest' 2 | 3 | test('math', () => { 4 | expect(1 + 1).toBe(2) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/multiple-configs/app2/vitest.config.js: -------------------------------------------------------------------------------- 1 | export default {} -------------------------------------------------------------------------------- /samples/multiple-configs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-configs", 3 | "private": true, 4 | "dependencies": { 5 | "vitest": "^2.1.5" 6 | } 7 | } -------------------------------------------------------------------------------- /samples/no-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "no-package", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run --pool=forks" 10 | }, 11 | "devDependencies": { 12 | "vitest": "^1.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/no-config/src/add.ts: -------------------------------------------------------------------------------- 1 | export function add(a: number, b: number) { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /samples/no-config/test/add.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { add } from '../src/add' 3 | 4 | describe('addition', () => { 5 | it('add', () => { 6 | expect(add(1, 1)).toBe(2) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /samples/readme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "readme", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "vitest": "^2.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/readme/src/add.ts: -------------------------------------------------------------------------------- 1 | export function add(a: number, b: number) { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /samples/readme/test/example.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest' 2 | import { add } from '../src/add.js' 3 | 4 | describe('test suite', () => { 5 | test('passing test', () => { 6 | expect(add(1, 1)).toBe(2) 7 | }) 8 | 9 | test('failing test', () => { 10 | expect(add(2, 2)).toBe(5) 11 | }) 12 | 13 | test.skip('skipped test', () => { 14 | expect(add(3, 3)).toBe(6) 15 | }) 16 | 17 | // test('not run', () => { 18 | // expect(add(4, 4)).toBe(8) 19 | // }) 20 | }) -------------------------------------------------------------------------------- /samples/readme/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: false, 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /samples/vite-6/.skip/vitest.config.ts: -------------------------------------------------------------------------------- 1 | throw new Error('do not import') -------------------------------------------------------------------------------- /samples/vite-6/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.nodeEnv": { 3 | "TEST_CUSTOM_ENV": "hello" 4 | }, 5 | "vitest.experimentalStaticAstCollect": true, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/vite-6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-6", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "vitest run" 10 | }, 11 | "devDependencies": { 12 | "vite": "^6.0.0", 13 | "vitest": "^3.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/vite-6/test/fail.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('all-fail', () => { 4 | expect(0).toBe(1) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/vite-6/test/mix.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('mix-pass', () => { 4 | expect(0).toBe(0) 5 | }) 6 | 7 | it('mix-fail', () => { 8 | expect(0).toBe(1) 9 | }) 10 | -------------------------------------------------------------------------------- /samples/vite-6/test/pass.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | 3 | it('all-pass', () => { 4 | expect(0).toBe(0) 5 | }) 6 | -------------------------------------------------------------------------------- /samples/vite-6/vite.config.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | throw new Error('This file should not be executed') 3 | } -------------------------------------------------------------------------------- /samples/vite-6/vitest.config.d.ts: -------------------------------------------------------------------------------- 1 | declare const ts: true 2 | 3 | // @ts-expect-error this is just a test 4 | throw new Error('This file should not be included by default.') 5 | -------------------------------------------------------------------------------- /samples/vite-6/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Configure Vitest (https://vitest.dev/config) 4 | 5 | import { defineConfig } from 'vite' 6 | 7 | export default defineConfig({ 8 | esbuild: { 9 | target: 'es2022', 10 | }, 11 | test: { 12 | include: ['src/should_included_test.ts', 'test/**/*.test.ts'], 13 | exclude: ['test/ignored.test.ts'], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /samples/vue/components/AsyncComp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /samples/vue/components/AsyncWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /samples/vue/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /samples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vitest/test-vue", 3 | "private": true, 4 | "scripts": { 5 | "test": "vitest", 6 | "coverage": "vitest run --coverage" 7 | }, 8 | "dependencies": { 9 | "vue": "^3.4.22" 10 | }, 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^5.0.4", 13 | "@vitest/coverage-v8": "^1.5.0", 14 | "@vue/test-utils": "^2.4.5", 15 | "happy-dom": "14.7.1", 16 | "vite": "^5.2.9", 17 | "vitest": "^1.5.0" 18 | }, 19 | "stackblitz": { 20 | "startCommand": "npm run test" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/vue/test/__snapshots__/basic.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`mount component 1`] = ` 4 | "

4 x 2 = 8
5 | " 6 | `; 7 | -------------------------------------------------------------------------------- /samples/vue/test/async.test.ts: -------------------------------------------------------------------------------- 1 | import { nextTick } from 'vue' 2 | import { flushPromises, mount } from '@vue/test-utils' 3 | import AsyncWrapper from '../components/AsyncWrapper.vue' 4 | 5 | test('async component with suspense', async () => { 6 | expect(AsyncWrapper).toBeTruthy() 7 | 8 | let resolve: Function 9 | // eslint-disable-next-line promise/param-names 10 | const promise = new Promise(_resolve => resolve = _resolve) 11 | const wrapper = mount(AsyncWrapper, { 12 | props: { 13 | promise, 14 | }, 15 | }) 16 | 17 | await nextTick() 18 | 19 | expect(wrapper.text()).toContain('fallback') 20 | 21 | resolve() 22 | 23 | await flushPromises() 24 | await nextTick() 25 | await nextTick() 26 | 27 | const text = wrapper.text() 28 | expect(text).toContain('resolved') 29 | }) 30 | -------------------------------------------------------------------------------- /samples/vue/test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Hello from '../components/Hello.vue' 3 | 4 | test('mount component', async () => { 5 | expect(Hello).toBeTruthy() 6 | 7 | const wrapper = mount(Hello, { 8 | props: { 9 | count: 4, 10 | }, 11 | }) 12 | 13 | expect(wrapper.text()).toContain('4 x 2 = 8') 14 | expect(wrapper.html()).toMatchSnapshot() 15 | 16 | await wrapper.get('button').trigger('click') 17 | 18 | expect(wrapper.text()).toContain('4 x 3 = 12') 19 | 20 | await wrapper.get('button').trigger('click') 21 | 22 | expect(wrapper.text()).toContain('4 x 4 = 16') 23 | }) 24 | -------------------------------------------------------------------------------- /samples/vue/test/imports.test.ts: -------------------------------------------------------------------------------- 1 | describe('import vue components', () => { 2 | test('normal imports as expected', async () => { 3 | const cmp = await import('../components/Hello.vue') 4 | expect(cmp).toBeDefined() 5 | }) 6 | 7 | test('template string imports as expected', async () => { 8 | const cmp = await import(`../components/Hello.vue`) 9 | expect(cmp).toBeDefined() 10 | }) 11 | 12 | test('dynamic imports as expected', async () => { 13 | const name = 'Hello' 14 | const cmp = await import(`../components/${name}.vue`) 15 | expect(cmp).toBeDefined() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /samples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/vue/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vite' 4 | import Vue from '@vitejs/plugin-vue' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | Vue(), 9 | ], 10 | test: { 11 | globals: true, 12 | environment: 'happy-dom', 13 | coverage: { 14 | provider: 'v8', 15 | reporter: ['text', 'json'], 16 | enabled: true, 17 | exclude: [], 18 | include: ['components/**'] 19 | } 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /scripts/ecosystem-ci.mts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { $ as $_ } from 'execa' 3 | 4 | const $ = $_({ stdio: 'inherit', verbose: true }) 5 | 6 | async function main() { 7 | await $`pnpm -C samples/e2e i` 8 | await $`pnpm -C samples/monorepo-vitest-workspace i` 9 | await $`pnpm -C samples/browser i` 10 | await $`pnpm -C samples/imba i` 11 | 12 | // setup pakcage overrides for samples used by test-e2e 13 | if (process.env.CI === 'true' && process.platform === 'linux') { 14 | await $`xvfb-run pnpm test` 15 | await $`xvfb-run pnpm test-e2e --retry 2` 16 | } 17 | else { 18 | await $`pnpm test` 19 | await $`pnpm test-e2e` 20 | } 21 | } 22 | 23 | main() 24 | -------------------------------------------------------------------------------- /scripts/release.mts: -------------------------------------------------------------------------------- 1 | import { SemVer } from 'semver' 2 | import prompts from 'prompts' 3 | import c from 'picocolors' 4 | import versionBump from 'bumpp' 5 | import { version } from '../package.json' 6 | 7 | // vscode support _only_ major.minor.patch, it doesn't support -beta.1 8 | const versionNumbers = version.split('.') 9 | const currentVersion = versionNumbers.slice(0, 3).join('.') 10 | const currentMinor = versionNumbers[1] 11 | const isCurrentlyPreRelease = Number(currentMinor) % 2 !== 0 12 | 13 | const PADDING = 13 14 | 15 | const major = new SemVer(currentVersion).inc('major').version 16 | const semverMinor = new SemVer(currentVersion).inc('minor').version 17 | const semverTwoMinor = new SemVer(currentVersion).inc('minor').inc('minor').version 18 | const patch = new SemVer(currentVersion).inc('patch').version 19 | 20 | const minor = isCurrentlyPreRelease ? semverMinor : semverTwoMinor 21 | const preminor = isCurrentlyPreRelease ? semverTwoMinor : semverMinor 22 | 23 | const result = await prompts([ 24 | { 25 | type: 'autocomplete', 26 | name: 'release', 27 | message: `Current version ${c.green(currentVersion)}`, 28 | initial: 'next', 29 | choices: [ 30 | { value: major, title: `${'major'.padStart(PADDING, ' ')} ${c.bold(major)}` }, 31 | { value: minor, title: `${'minor'.padStart(PADDING, ' ')} ${c.bold(minor)}` }, 32 | { value: preminor, title: `${'pre-minor'.padStart(PADDING, ' ')} ${c.bold(preminor)} (odd number)` }, 33 | { value: patch, title: `${(isCurrentlyPreRelease ? 'pre-patch' : 'patch').padStart(PADDING, ' ')} ${c.bold(patch)}` }, 34 | { value: currentVersion, title: `${'as-is'.padStart(PADDING, ' ')} ${c.bold(currentVersion)}` }, 35 | ], 36 | }, 37 | ]) 38 | 39 | if (!result.release) 40 | process.exit(0) 41 | 42 | await versionBump({ 43 | release: result.release, 44 | commit: true, 45 | push: true, 46 | tag: true, 47 | interface: false, 48 | confirm: true, 49 | }) 50 | -------------------------------------------------------------------------------- /src/api/resolve.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode' 2 | import { dirname, resolve } from 'pathe' 3 | import { findUpSync } from 'find-up' 4 | import { getConfig } from '../config' 5 | import { normalizeDriveLetter } from '../worker/utils' 6 | 7 | const _require = require 8 | 9 | export interface VitestResolution { 10 | vitestPackageJsonPath: string 11 | vitestNodePath: string 12 | pnp?: { 13 | loaderPath: string 14 | pnpPath: string 15 | } 16 | } 17 | 18 | export function resolveVitestPackage(cwd: string, folder: vscode.WorkspaceFolder | undefined): VitestResolution | null { 19 | const vitestPackageJsonPath = !process.versions.pnp && resolveVitestPackagePath(cwd, folder) 20 | if (vitestPackageJsonPath) { 21 | return { 22 | vitestNodePath: resolveVitestNodePath(vitestPackageJsonPath), 23 | vitestPackageJsonPath, 24 | } 25 | } 26 | 27 | const pnp = resolveVitestPnpPackagePath(folder?.uri.fsPath || cwd) 28 | if (!pnp) 29 | return null 30 | return { 31 | vitestNodePath: pnp.vitestNodePath, 32 | vitestPackageJsonPath: 'vitest/package.json', 33 | pnp: { 34 | loaderPath: pnp.pnpLoader, 35 | pnpPath: pnp.pnpPath, 36 | }, 37 | } 38 | } 39 | 40 | export function resolveVitestPackagePath(cwd: string, folder: vscode.WorkspaceFolder | undefined) { 41 | const customPackagePath = getConfig(folder).vitestPackagePath 42 | if (customPackagePath && !customPackagePath.endsWith('package.json')) 43 | throw new Error(`"vitest.vitestPackagePath" must point to a package.json file, instead got: ${customPackagePath}`) 44 | try { 45 | const result = customPackagePath || require.resolve('vitest/package.json', { 46 | paths: [cwd], 47 | }) 48 | delete require.cache['vitest/package.json'] 49 | delete require.cache[result] 50 | return result 51 | } 52 | catch { 53 | return null 54 | } 55 | } 56 | 57 | export function resolveVitestPnpPackagePath(cwd: string) { 58 | try { 59 | const pnpPath = findUpSync(['.pnp.js', '.pnp.cjs'], { cwd }) 60 | if (pnpPath == null) { 61 | return null 62 | } 63 | const pnpApi = _require(pnpPath) 64 | const vitestNodePath = pnpApi.resolveRequest('vitest/node', normalizeDriveLetter(cwd)) 65 | return { 66 | pnpLoader: require.resolve('./.pnp.loader.mjs', { 67 | paths: [dirname(pnpPath)], 68 | }), 69 | pnpPath, 70 | vitestNodePath, 71 | } 72 | } 73 | catch { 74 | return null 75 | } 76 | } 77 | 78 | export function resolveVitestNodePath(vitestPkgPath: string) { 79 | return resolve(dirname(vitestPkgPath), './dist/node.js') 80 | } 81 | -------------------------------------------------------------------------------- /src/api/rpc.ts: -------------------------------------------------------------------------------- 1 | import v8 from 'node:v8' 2 | import { type BirpcReturn, createBirpc } from 'birpc' 3 | import type { RunnerTestFile, TaskResultPack, UserConsoleLog } from 'vitest' 4 | 5 | export type SerializedTestSpecification = [ 6 | project: { name: string | undefined }, 7 | file: string, 8 | ] 9 | 10 | export interface ExtensionWorkerTransport { 11 | getFiles: () => Promise<[project: string, file: string][]> 12 | collectTests: (testFile: [project: string, filepath: string][]) => Promise 13 | cancelRun: () => Promise 14 | // accepts files with the project or folders (project doesn't matter for them) 15 | runTests: (files?: SerializedTestSpecification[] | string[], testNamePattern?: string) => Promise 16 | updateSnapshots: (files?: SerializedTestSpecification[] | string[], testNamePattern?: string) => Promise 17 | 18 | watchTests: (files?: SerializedTestSpecification[] | string[], testNamePattern?: string) => void 19 | unwatchTests: () => void 20 | 21 | invalidateIstanbulTestModules: (modules: string[] | null) => Promise 22 | enableCoverage: () => void 23 | disableCoverage: () => void 24 | waitForCoverageReport: () => Promise 25 | close: () => void 26 | 27 | onFilesCreated: (files: string[]) => void 28 | onFilesChanged: (files: string[]) => void 29 | } 30 | 31 | export interface ExtensionWorkerEvents { 32 | onConsoleLog: (log: UserConsoleLog) => void 33 | onTaskUpdate: (task: TaskResultPack[]) => void 34 | onFinished: (files: RunnerTestFile[], unhandledError: string, collecting?: boolean) => void 35 | onCollected: (files?: RunnerTestFile[], collecting?: boolean) => void 36 | onWatcherStart: (files?: RunnerTestFile[], errors?: unknown[], collecting?: boolean) => void 37 | onWatcherRerun: (files: string[], trigger?: string, collecting?: boolean) => void 38 | } 39 | 40 | export type VitestRPC = BirpcReturn 41 | 42 | function createHandler any>() { 43 | const handlers: T[] = [] 44 | return { 45 | handlers, 46 | register: (listener: any) => handlers.push(listener), 47 | trigger: (...data: any) => handlers.forEach(handler => handler(...data)), 48 | clear: () => handlers.length = 0, 49 | remove: (listener: T) => { 50 | const index = handlers.indexOf(listener) 51 | if (index !== -1) 52 | handlers.splice(index, 1) 53 | }, 54 | } 55 | } 56 | 57 | export function createRpcOptions() { 58 | const handlers = { 59 | onConsoleLog: createHandler(), 60 | onTaskUpdate: createHandler(), 61 | onFinished: createHandler(), 62 | onCollected: createHandler(), 63 | onWatcherRerun: createHandler(), 64 | onWatcherStart: createHandler(), 65 | } 66 | 67 | const events: Omit = { 68 | onConsoleLog: handlers.onConsoleLog.trigger, 69 | onFinished: handlers.onFinished.trigger, 70 | onTaskUpdate: handlers.onTaskUpdate.trigger, 71 | onCollected: handlers.onCollected.trigger, 72 | onWatcherRerun: handlers.onWatcherRerun.trigger, 73 | onWatcherStart: handlers.onWatcherStart.trigger, 74 | } 75 | 76 | return { 77 | events, 78 | handlers: { 79 | onConsoleLog: handlers.onConsoleLog.register, 80 | onTaskUpdate: handlers.onTaskUpdate.register, 81 | onFinished: handlers.onFinished.register, 82 | onCollected: handlers.onCollected.register, 83 | onWatcherRerun: handlers.onWatcherRerun.register, 84 | onWatcherStart: handlers.onWatcherStart.register, 85 | removeListener(name: string, listener: any) { 86 | handlers[name as 'onCollected']?.remove(listener) 87 | }, 88 | clearListeners() { 89 | for (const name in handlers) 90 | handlers[name as 'onCollected']?.clear() 91 | }, 92 | }, 93 | } 94 | } 95 | 96 | export function createVitestRpc(options: { 97 | on: (listener: (message: any) => void) => void 98 | send: (message: any) => void 99 | }) { 100 | const { events, handlers } = createRpcOptions() 101 | 102 | const api = createBirpc( 103 | events, 104 | { 105 | timeout: -1, 106 | bind: 'functions', 107 | on(listener) { 108 | options.on(listener) 109 | }, 110 | post(message) { 111 | options.send(message) 112 | }, 113 | serialize: v8.serialize, 114 | deserialize: v => v8.deserialize(Buffer.from(v) as any), 115 | }, 116 | ) 117 | 118 | return { 119 | api, 120 | handlers, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/api/terminal.ts: -------------------------------------------------------------------------------- 1 | import type { Server } from 'node:http' 2 | import { createServer } from 'node:http' 3 | import * as vscode from 'vscode' 4 | import getPort from 'get-port' 5 | import type { WebSocket } from 'ws' 6 | import { WebSocketServer } from 'ws' 7 | import { getConfig } from '../config' 8 | import { workerPath } from '../constants' 9 | import { createErrorLogger, log } from '../log' 10 | import { formatPkg } from '../utils' 11 | import type { ResolvedMeta } from '../api' 12 | import type { VitestPackage } from './pkg' 13 | import { waitForWsConnection } from './ws' 14 | import type { ExtensionWorkerProcess } from './types' 15 | 16 | export async function createVitestTerminalProcess(pkg: VitestPackage): Promise { 17 | const port = await getPort() 18 | const server = createServer().listen(port).unref() 19 | const wss = new WebSocketServer({ server }) 20 | const wsAddress = `ws://localhost:${port}` 21 | const config = getConfig(pkg.folder) 22 | const env = config.env || {} 23 | const terminal = vscode.window.createTerminal({ 24 | hideFromUser: true, 25 | cwd: pkg.folder.uri, 26 | isTransient: false, 27 | shellArgs: config.terminalShellArgs, 28 | shellPath: config.terminalShellPath, 29 | env: { 30 | ...env, 31 | VITEST_WS_ADDRESS: wsAddress, 32 | VITEST_VSCODE: 'true', 33 | TEST: 'true', 34 | VITEST: 'true', 35 | NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test', 36 | }, 37 | }) 38 | const command = `node ${workerPath}` 39 | log.info('[API]', `Initiated ws connection via ${wsAddress}`) 40 | log.info('[API]', `Starting ${formatPkg(pkg)} in the terminal: ${command}`) 41 | terminal.sendText(command, true) 42 | const meta = await waitForWsConnection(wss, pkg, false, 'terminal') 43 | const processId = (await terminal.processId) ?? Math.random() 44 | log.info('[API]', `${formatPkg(pkg)} terminal process ${processId} created`) 45 | const vitestProcess = new ExtensionTerminalProcess( 46 | processId, 47 | terminal, 48 | server, 49 | meta.ws, 50 | ) 51 | return { 52 | rpc: meta.rpc, 53 | handlers: meta.handlers, 54 | pkg, 55 | workspaceSource: meta.workspaceSource, 56 | process: vitestProcess, 57 | configs: meta.configs, 58 | } 59 | } 60 | 61 | export class ExtensionTerminalProcess implements ExtensionWorkerProcess { 62 | private _onDidExit = new vscode.EventEmitter() 63 | 64 | private stopped: Promise 65 | 66 | constructor( 67 | public readonly id: number, 68 | private readonly terminal: vscode.Terminal, 69 | server: Server, 70 | ws: WebSocket, 71 | ) { 72 | this.stopped = new Promise((resolve) => { 73 | const disposer = vscode.window.onDidCloseTerminal(async (e) => { 74 | if (e === terminal) { 75 | const exitCode = e.exitStatus?.code 76 | this._onDidExit.fire(exitCode ?? null) 77 | this._onDidExit.dispose() 78 | server.close(createErrorLogger('Failed to close server')) 79 | disposer.dispose() 80 | resolve() 81 | } 82 | }) 83 | }) 84 | ws.on('close', () => { 85 | this.close() 86 | }) 87 | } 88 | 89 | show() { 90 | this.terminal.show(false) 91 | } 92 | 93 | get closed() { 94 | return this.terminal.exitStatus !== undefined 95 | } 96 | 97 | close() { 98 | // send ctrl+c to sigint any running processs (vscode/#108289) 99 | this.terminal.sendText('\x03') 100 | // and then destroy it on the next event loop tick 101 | setTimeout(() => this.terminal.dispose(), 1) 102 | return new Promise((resolve, reject) => { 103 | const timer = setTimeout(() => { 104 | reject(new Error('The extension terminal process did not exit in time.')) 105 | }, 5_000) 106 | this.stopped 107 | .finally(() => clearTimeout(timer)) 108 | .then(resolve, reject) 109 | }) 110 | } 111 | 112 | onError() { 113 | // do nothing 114 | return () => { 115 | // do nothing 116 | } 117 | } 118 | 119 | onExit(listener: (code: number | null) => void) { 120 | const disposable = this._onDidExit.event(listener) 121 | return () => { 122 | disposable.dispose() 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/api/types.ts: -------------------------------------------------------------------------------- 1 | export interface ExtensionWorkerProcess { 2 | id: number 3 | closed: boolean 4 | close: () => Promise 5 | onError: (listener: (error: Error) => void) => () => void 6 | onExit: (listener: (code: number | null) => void) => () => void 7 | } 8 | -------------------------------------------------------------------------------- /src/api/ws.ts: -------------------------------------------------------------------------------- 1 | import { pathToFileURL } from 'node:url' 2 | import type { WebSocket, WebSocketServer } from 'ws' 3 | import { gte } from 'semver' 4 | import type { ResolvedMeta } from '../api' 5 | import { log } from '../log' 6 | import type { WorkerEvent, WorkerRunnerOptions } from '../worker/types' 7 | import { getConfig } from '../config' 8 | import { createVitestRpc } from './rpc' 9 | import type { VitestPackage } from './pkg' 10 | 11 | export type WsConnectionMetadata = Omit & { 12 | ws: WebSocket 13 | } 14 | 15 | export function waitForWsConnection( 16 | wss: WebSocketServer, 17 | pkg: VitestPackage, 18 | debug: boolean, 19 | shellType: 'terminal' | 'child_process', 20 | ) { 21 | return new Promise((resolve, reject) => { 22 | wss.once('connection', (ws) => { 23 | function onMessage(_message: any) { 24 | const message = JSON.parse(_message.toString()) as WorkerEvent 25 | 26 | if (message.type === 'debug') 27 | log.worker('info', ...message.args) 28 | 29 | if (message.type === 'ready') { 30 | const { api, handlers } = createVitestRpc({ 31 | on: listener => ws.on('message', listener), 32 | send: message => ws.send(message), 33 | }) 34 | ws.once('close', () => { 35 | log.verbose?.('[API]', 'Vitest WebSocket connection closed, cannot call RPC anymore.') 36 | api.$close() 37 | }) 38 | resolve({ 39 | rpc: api, 40 | workspaceSource: message.workspaceSource, 41 | handlers: { 42 | ...handlers, 43 | onStdout() { 44 | // do nothing by default 45 | }, 46 | }, 47 | configs: message.configs, 48 | ws, 49 | pkg, 50 | }) 51 | } 52 | 53 | if (message.type === 'error') { 54 | const error = new Error(`Vitest failed to start: \n${message.error}`) 55 | reject(error) 56 | } 57 | ws.off('error', onError) 58 | ws.off('message', onMessage) 59 | ws.off('close', onExit) 60 | } 61 | 62 | function onError(err: Error) { 63 | log.error('[API]', err) 64 | reject(err) 65 | ws.off('error', onError) 66 | ws.off('message', onMessage) 67 | ws.off('close', onExit) 68 | } 69 | 70 | function onExit(code: number) { 71 | reject(new Error(`Vitest process exited with code ${code}`)) 72 | } 73 | 74 | wss.off('error', onUnexpectedError) 75 | wss.off('exit', onUnexpectedExit) 76 | 77 | ws.on('error', onError) 78 | ws.on('message', onMessage) 79 | ws.on('close', onExit) 80 | 81 | const pnpLoader = pkg.loader 82 | const pnp = pkg.pnp 83 | 84 | const runnerOptions: WorkerRunnerOptions = { 85 | type: 'init', 86 | meta: { 87 | shellType, 88 | vitestNodePath: pkg.vitestNodePath, 89 | env: getConfig(pkg.folder).env || undefined, 90 | configFile: pkg.configFile, 91 | cwd: pkg.cwd, 92 | arguments: pkg.arguments, 93 | workspaceFile: pkg.workspaceFile, 94 | id: pkg.id, 95 | pnpApi: pnp, 96 | pnpLoader: pnpLoader && gte(process.version, '18.19.0') 97 | ? pathToFileURL(pnpLoader).toString() 98 | : undefined, 99 | }, 100 | debug, 101 | astCollect: getConfig(pkg.folder).experimentalStaticAstCollect, 102 | } 103 | 104 | ws.send(JSON.stringify(runnerOptions)) 105 | }) 106 | 107 | function onUnexpectedExit() { 108 | reject(new Error('Cannot establish connection with Vitest process.')) 109 | } 110 | 111 | function onUnexpectedError(err: Error) { 112 | reject(err) 113 | } 114 | 115 | wss.on('error', onUnexpectedError) 116 | wss.once('close', onUnexpectedExit) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { homedir } from 'node:os' 2 | import { dirname, isAbsolute, resolve } from 'node:path' 3 | import type { WorkspaceConfiguration, WorkspaceFolder } from 'vscode' 4 | import * as vscode from 'vscode' 5 | 6 | export const extensionId = 'vitest.explorer' 7 | export const testControllerId = 'vitest' 8 | 9 | export function getConfigValue( 10 | rootConfig: WorkspaceConfiguration, 11 | folderConfig: WorkspaceConfiguration, 12 | key: string, 13 | defaultValue?: T, 14 | ): T | undefined { 15 | if (typeof defaultValue === 'boolean') { 16 | return folderConfig.get(key) ?? rootConfig.get(key) ?? defaultValue 17 | } 18 | return folderConfig.get(key) || rootConfig.get(key) || defaultValue 19 | } 20 | 21 | export function getConfig(workspaceFolder?: WorkspaceFolder) { 22 | if (!workspaceFolder && vscode.workspace.workspaceFolders?.length === 1) 23 | workspaceFolder = vscode.workspace.workspaceFolders[0] 24 | 25 | const folderConfig = vscode.workspace.getConfiguration('vitest', workspaceFolder) 26 | const rootConfig = vscode.workspace.getConfiguration('vitest') 27 | 28 | const get = (key: string, defaultValue?: T) => getConfigValue( 29 | rootConfig, 30 | folderConfig, 31 | key, 32 | defaultValue, 33 | ) 34 | 35 | const nodeExecutable = get('nodeExecutable') 36 | const workspaceConfig = get('workspaceConfig') 37 | const rootConfigFile = get('rootConfig') 38 | 39 | const configSearchPatternExclude = get( 40 | 'configSearchPatternExclude', 41 | '{**/node_modules/**,**/.*/**,**/*.d.ts}', 42 | )! 43 | 44 | const vitestPackagePath = get('vitestPackagePath') 45 | const resolvedVitestPackagePath = workspaceFolder && vitestPackagePath 46 | ? resolve( 47 | workspaceFolder.uri.fsPath, 48 | // eslint-disable-next-line no-template-curly-in-string 49 | vitestPackagePath.replace('${workspaceFolder}', workspaceFolder.uri.fsPath), 50 | ) 51 | : vitestPackagePath 52 | 53 | const logLevel = get('logLevel', 'info') 54 | 55 | const filesWatcherInclude = get('filesWatcherInclude', '**/*')! 56 | 57 | const terminalShellArgs = get('terminalShellArgs') 58 | const terminalShellPath = get('terminalShellPath') 59 | const shellType = get<'child_process' | 'terminal'>('shellType', 'child_process')! 60 | const nodeExecArgs = get('nodeExecArgs') 61 | 62 | const experimentalStaticAstCollect = get('experimentalStaticAstCollect', false)! 63 | 64 | const cliArguments = get('cliArguments') 65 | 66 | const debugOutFiles = get('debugOutFiles', []) 67 | const applyDiagnostic = get('applyDiagnostic', true) 68 | 69 | return { 70 | env: get>('nodeEnv', null), 71 | debugExclude: get('debugExclude'), 72 | debugOutFiles, 73 | filesWatcherInclude, 74 | terminalShellArgs, 75 | terminalShellPath, 76 | shellType, 77 | applyDiagnostic, 78 | cliArguments, 79 | nodeExecArgs, 80 | experimentalStaticAstCollect, 81 | vitestPackagePath: resolvedVitestPackagePath, 82 | workspaceConfig: resolveConfigPath(workspaceConfig), 83 | rootConfig: resolveConfigPath(rootConfigFile), 84 | configSearchPatternExclude, 85 | maximumConfigs: get('maximumConfigs', 5), 86 | nodeExecutable: resolveConfigPath(nodeExecutable), 87 | disableWorkspaceWarning: get('disableWorkspaceWarning', false), 88 | debuggerPort: get('debuggerPort') || undefined, 89 | debuggerAddress: get('debuggerAddress', undefined) || undefined, 90 | logLevel, 91 | } 92 | } 93 | 94 | export function resolveConfigPath(path: string | undefined) { 95 | if (!path || isAbsolute(path)) 96 | return path 97 | if (path.startsWith('~/')) { 98 | return resolve(homedir(), path.slice(2)) 99 | } 100 | // if there is a workspace file, then it should be relative to it because 101 | // this option cannot be configured on a workspace folder level 102 | if (vscode.workspace.workspaceFile) 103 | return resolve(dirname(vscode.workspace.workspaceFile.fsPath), path) 104 | const workspaceFolders = vscode.workspace.workspaceFolders 105 | // if there is no workspace file, then it's probably a single folder workspace 106 | if (workspaceFolders?.length === 1) 107 | return resolve(workspaceFolders[0].uri.fsPath, path) 108 | // if there are still several folders, then we can't reliably resolve the path 109 | return path 110 | } 111 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'pathe' 2 | 3 | export const minimumVersion = '1.4.0' 4 | // follows minimum Vitest 5 | export const minimumNodeVersion = '18.0.0' 6 | 7 | export const distDir = __dirname 8 | export const workerPath = resolve(__dirname, 'worker.js') 9 | export const setupFilePath = resolve(__dirname, 'setupFile.mjs') 10 | 11 | export const configGlob = '**/*{vite,vitest}*.config*.{ts,js,mjs,cjs,cts,mts}' 12 | export const workspaceGlob = '**/*vitest.{workspace,projects}*.{ts,js,mjs,cjs,cts,mts,json}' 13 | 14 | export const finalCoverageFileName = 'coverage-final.json' 15 | -------------------------------------------------------------------------------- /src/coverage.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import { IstanbulCoverageContext, IstanbulMissingCoverageError } from 'istanbul-to-vscode' 3 | import { join } from 'pathe' 4 | import { finalCoverageFileName } from './constants' 5 | 6 | export const coverageContext = new IstanbulCoverageContext() 7 | 8 | export function readCoverageReport(reportsDirectory: string) { 9 | try { 10 | return JSON.parse(readFileSync(join(reportsDirectory, finalCoverageFileName), 'utf8')) 11 | } 12 | catch (err: any) { 13 | throw new IstanbulMissingCoverageError(reportsDirectory, err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/diagnostic.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | export class ExtensionDiagnostic { 4 | private diagnostic = vscode.languages.createDiagnosticCollection('Vitest') 5 | 6 | addDiagnostic(testFile: vscode.Uri, errors: vscode.TestMessage[]) { 7 | const diagnostics: vscode.Diagnostic[] = [...this.diagnostic.get(testFile) || []] 8 | errors.forEach((error) => { 9 | const range = error.location?.range 10 | if (!range) { 11 | return 12 | } 13 | const diagnostic = new vscode.Diagnostic( 14 | range, 15 | error.message.toString(), 16 | vscode.DiagnosticSeverity.Error, 17 | ) 18 | diagnostics.push(diagnostic) 19 | }) 20 | this.diagnostic.set(testFile, diagnostics) 21 | } 22 | 23 | deleteDiagnostic(testFile: vscode.Uri) { 24 | this.diagnostic.delete(testFile) 25 | } 26 | 27 | clearDiagnostic() { 28 | this.diagnostic.clear() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { appendFileSync, writeFileSync } from 'node:fs' 4 | import { window } from 'vscode' 5 | import { getConfig } from './config' 6 | 7 | const logFile = process.env.VITEST_VSCODE_E2E_LOG_FILE! 8 | const channel = window.createOutputChannel('Vitest') 9 | const callbacks: Set<((message: string) => void)> = new Set() 10 | 11 | function logToCallbacks(message: string) { 12 | for (const callback of callbacks) { 13 | callback(message) 14 | } 15 | } 16 | 17 | export const log = { 18 | onWorkerLog(callback: (message: string) => void) { 19 | callbacks.add(callback) 20 | }, 21 | offWorkerLog(callback: (message: string) => void) { 22 | callbacks.delete(callback) 23 | }, 24 | worker: (type: 'info' | 'error', ...args: any[]) => { 25 | if (typeof args.at(-1) === 'string' && args.at(-1).endsWith('\n')) 26 | args[args.length - 1] = args.at(-1).slice(0, process.platform === 'win32' ? -2 : -1) 27 | 28 | const time = new Date().toLocaleTimeString() 29 | if (process.env.EXTENSION_NODE_ENV === 'dev') { 30 | console[type](`[INFO ${time}]`, '[Worker]', ...args) 31 | } 32 | const message = `[INFO ${time}] [Worker] ${args.join(' ')}` 33 | if (logFile) { 34 | appendFile(message) 35 | } 36 | logToCallbacks(message) 37 | channel.appendLine(message) 38 | }, 39 | info: (...args: any[]) => { 40 | if (process.env.EXTENSION_NODE_ENV === 'dev') { 41 | console.log(...args) 42 | } 43 | const time = new Date().toLocaleTimeString() 44 | const message = `[INFO ${time}] ${args.join(' ')}` 45 | if (logFile) { 46 | appendFile(message) 47 | } 48 | channel.appendLine(message) 49 | }, 50 | error: (...args: any[]) => { 51 | if (process.env.EXTENSION_NODE_ENV === 'dev') { 52 | console.error(...args) 53 | } 54 | const time = new Date().toLocaleTimeString() 55 | for (let i = 0; i < args.length; i++) { 56 | if (args[i] instanceof Error) { 57 | const err = args[i] as Error 58 | args[i] = `[Error ${err.name}] ${err.message}\n${err.stack}` 59 | } 60 | } 61 | const message = `[Error ${time}] ${args.join(' ')}` 62 | if (logFile) { 63 | appendFile(message) 64 | } 65 | channel.appendLine(message) 66 | }, 67 | verbose: getConfig().logLevel === 'verbose' || process.env.VITEST_VSCODE_LOG === 'verbose' 68 | ? (...args: string[]) => { 69 | const time = new Date().toLocaleTimeString() 70 | if (process.env.EXTENSION_NODE_ENV === 'dev') { 71 | console.log(`[${time}]`, ...args) 72 | } 73 | const message = `[${time}] ${args.join(' ')}` 74 | if (logFile) { 75 | appendFile(message) 76 | } 77 | channel.appendLine(message) 78 | } 79 | : undefined, 80 | workspaceInfo: (folder: string, ...args: any[]) => { 81 | log.info(`[Workspace ${folder}]`, ...args) 82 | }, 83 | workspaceError: (folder: string, ...args: any[]) => { 84 | log.error(`[Workspace ${folder}]`, ...args) 85 | }, 86 | openOuput() { 87 | channel.show() 88 | }, 89 | } as const 90 | 91 | let exitsts = false 92 | function appendFile(log: string) { 93 | if (!exitsts) { 94 | writeFileSync(logFile, '') 95 | exitsts = true 96 | } 97 | appendFileSync(logFile, `${log}\n`) 98 | } 99 | 100 | export function createErrorLogger(prefix: string) { 101 | return (...args: any[]) => { 102 | log.error(prefix, ...args) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | if (!Promise.withResolvers) { 2 | Promise.withResolvers = function withResolvers() { 3 | let a: (v: T | PromiseLike) => void 4 | let b: (r?: any) => void 5 | const c = new this((resolve, reject) => { 6 | a = resolve 7 | b = reject 8 | }) 9 | return { resolve: a!, reject: b!, promise: c } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tagsManager.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import type { TestTree } from './testTree' 3 | 4 | export class TagsManager extends vscode.Disposable { 5 | private disposables: vscode.Disposable[] = [] 6 | 7 | private openTestTag = new vscode.TestTag('open') 8 | 9 | constructor( 10 | private testTree: TestTree, 11 | ) { 12 | super(() => { 13 | this.disposables.forEach(d => d.dispose()) 14 | this.disposables = [] 15 | }) 16 | } 17 | 18 | activate() { 19 | this.disposables.push( 20 | vscode.workspace.onDidOpenTextDocument((doc) => { 21 | this.addFileTag(doc.uri, this.openTestTag) 22 | }), 23 | vscode.workspace.onDidCloseTextDocument((doc) => { 24 | this.removeFileTag(doc.uri, this.openTestTag) 25 | }), 26 | ) 27 | 28 | vscode.window.visibleTextEditors.forEach(({ document }) => { 29 | this.addFileTag(document.uri, this.openTestTag) 30 | }) 31 | } 32 | 33 | addItemTag(item: vscode.TestItem, tag: vscode.TestTag) { 34 | if (!item.tags.includes(tag)) 35 | item.tags = [...item.tags, tag] 36 | item.children.forEach((child) => { 37 | this.addItemTag(child, tag) 38 | }) 39 | } 40 | 41 | removeItemTag(item: vscode.TestItem, tag: vscode.TestTag) { 42 | item.tags = item.tags.filter(x => x !== tag) 43 | item.children.forEach((child) => { 44 | this.removeItemTag(child, tag) 45 | }) 46 | } 47 | 48 | removeFileTag(uri: vscode.Uri, tag: vscode.TestTag) { 49 | const fileItems = this.testTree.getFileTestItems(uri.fsPath) 50 | if (!fileItems) 51 | return 52 | fileItems.forEach((item) => { 53 | this.removeItemTag(item, tag) 54 | }) 55 | } 56 | 57 | addFileTag(uri: vscode.Uri, tag: vscode.TestTag) { 58 | const fileItems = this.testTree.getFileTestItems(uri.fsPath) 59 | if (!fileItems) 60 | return 61 | fileItems.forEach((item) => { 62 | this.addItemTag(item, tag) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { spawn } from 'node:child_process' 3 | import * as vscode from 'vscode' 4 | import { dirname, relative } from 'pathe' 5 | import which from 'which' 6 | import type { VitestPackage } from './api/pkg' 7 | import { log } from './log' 8 | import { getConfig } from './config' 9 | 10 | export function noop() {} 11 | 12 | export function formatPkg(pkg: VitestPackage) { 13 | return `Vitest v${pkg.version} (${relative(dirname(pkg.cwd), pkg.id)})` 14 | } 15 | 16 | function _showVitestError(message: string, error?: any) { 17 | if (error) 18 | log.error(error) 19 | 20 | vscode.window.showErrorMessage( 21 | `${message}. Check the output for more details.`, 22 | 'See error', 23 | ).then((result) => { 24 | if (result === 'See error') 25 | vscode.commands.executeCommand('vitest.openOutput') 26 | }) 27 | } 28 | 29 | export const showVitestError = debounce(_showVitestError, 100) 30 | 31 | export function pluralize(count: number, singular: string) { 32 | return `${count} ${singular}${count === 1 ? '' : 's'}` 33 | } 34 | 35 | export function debounce void>(cb: T, wait = 20) { 36 | let h: NodeJS.Timeout | undefined 37 | const callable = (...args: any) => { 38 | if (h) 39 | clearTimeout(h) 40 | h = setTimeout(() => cb(...args), wait) 41 | } 42 | return (callable) 43 | } 44 | 45 | // port from nanoid 46 | // https://github.com/ai/nanoid 47 | const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' 48 | export function nanoid(size = 21) { 49 | let id = '' 50 | let i = size 51 | while (i--) 52 | id += urlAlphabet[(Math.random() * 64) | 0] 53 | return id 54 | } 55 | 56 | export function waitUntilExists(file: string, timeoutMs = 5000) { 57 | return new Promise((resolve, reject) => { 58 | const timeout = setTimeout(() => { 59 | reject(new Error(`File ${file} did not appear in time`)) 60 | }, timeoutMs) 61 | const interval = setInterval(() => { 62 | if (fs.existsSync(file)) { 63 | clearInterval(interval) 64 | clearTimeout(timeout) 65 | resolve() 66 | } 67 | }, 50) 68 | }) 69 | } 70 | 71 | let pathToNodeJS: string | undefined 72 | 73 | // based on https://github.com/microsoft/playwright-vscode/blob/main/src/utils.ts#L144 74 | export async function findNode(cwd: string): Promise { 75 | if (getConfig().nodeExecutable) 76 | // if empty string, keep as undefined 77 | pathToNodeJS = getConfig().nodeExecutable || undefined 78 | 79 | if (pathToNodeJS) 80 | return pathToNodeJS 81 | 82 | // Stage 1: Try to find Node.js via process.env.PATH 83 | let node: string | null = await which('node', { nothrow: true }) 84 | // Stage 2: When extension host boots, it does not have the right env set, so we might need to wait. 85 | for (let i = 0; i < 5 && !node; ++i) { 86 | await new Promise(f => setTimeout(f, 200)) 87 | node = await which('node', { nothrow: true }) 88 | } 89 | // Stage 3: If we still haven't found Node.js, try to find it via a subprocess. 90 | // This evaluates shell rc/profile files and makes nvm work. 91 | node ??= await findNodeViaShell(cwd) 92 | 93 | if (!node) { 94 | const msg = `Unable to find 'node' executable.\nMake sure to have Node.js installed and available in your PATH.\nCurrent PATH: '${process.env.PATH}'.` 95 | log.error(msg) 96 | throw new Error(msg) 97 | } 98 | pathToNodeJS = node 99 | return node 100 | } 101 | 102 | async function findNodeViaShell(cwd: string): Promise { 103 | if (process.platform === 'win32') 104 | return null 105 | return new Promise((resolve) => { 106 | const startToken = '___START_SHELL__' 107 | const endToken = '___END_SHELL__' 108 | try { 109 | const childProcess = spawn(`${vscode.env.shell} -i -c 'if [[ $(type node 2>/dev/null) == *function* ]]; then node --version; fi; echo ${startToken} && which node && echo ${endToken}'`, { 110 | stdio: 'pipe', 111 | shell: true, 112 | cwd, 113 | }) 114 | let output = '' 115 | childProcess.stdout.on('data', data => output += data.toString()) 116 | childProcess.on('error', () => resolve(null)) 117 | childProcess.on('exit', (exitCode) => { 118 | if (exitCode !== 0) 119 | return resolve(null) 120 | const start = output.indexOf(startToken) 121 | const end = output.indexOf(endToken) 122 | if (start === -1 || end === -1) 123 | return resolve(null) 124 | return resolve(output.substring(start + startToken.length, end).trim()) 125 | }) 126 | } 127 | catch (e) { 128 | log.error('[SPAWN]', vscode.env.shell, e) 129 | resolve(null) 130 | } 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /src/watcher.ts: -------------------------------------------------------------------------------- 1 | import { relative } from 'node:path' 2 | import * as vscode from 'vscode' 3 | import { normalize } from 'pathe' 4 | import type { TestTree } from './testTree' 5 | import { getConfig } from './config' 6 | import type { VitestFolderAPI } from './api' 7 | import { log } from './log' 8 | 9 | export class ExtensionWatcher extends vscode.Disposable { 10 | private watcherByFolder = new Map() 11 | private apisByFolder = new WeakMap() 12 | 13 | constructor(private readonly testTree: TestTree) { 14 | super(() => { 15 | this.reset() 16 | log.verbose?.('[VSCODE] Watcher disposed') 17 | }) 18 | } 19 | 20 | reset() { 21 | this.watcherByFolder.forEach(x => x.dispose()) 22 | this.watcherByFolder.clear() 23 | } 24 | 25 | watchTestFilesInWorkspace(api: VitestFolderAPI) { 26 | const folder = api.workspaceFolder 27 | const apis = this.apisByFolder.get(folder) ?? [] 28 | if (!apis.includes(api)) { 29 | log.info('[API] Watching', relative(folder.uri.fsPath, api.id)) 30 | apis.push(api) 31 | } 32 | this.apisByFolder.set(folder, apis) 33 | if (this.watcherByFolder.has(folder)) { 34 | return 35 | } 36 | 37 | const pattern = new vscode.RelativePattern(folder, getConfig(folder).filesWatcherInclude) 38 | log.info('[VSCODE] Watching', folder.name, 'with pattern', pattern.pattern) 39 | const watcher = vscode.workspace.createFileSystemWatcher(pattern) 40 | this.watcherByFolder.set(folder, watcher) 41 | 42 | watcher.onDidDelete((uri) => { 43 | log.verbose?.('[VSCODE] File deleted:', this.relative(api, uri)) 44 | this.testTree.removeFile(normalize(uri.fsPath)) 45 | }) 46 | 47 | watcher.onDidChange(async (uri) => { 48 | const path = normalize(uri.fsPath) 49 | if (await this.shouldIgnoreFile(api, path, uri)) { 50 | return 51 | } 52 | log.verbose?.('[VSCODE] File changed:', this.relative(api, uri)) 53 | const apis = this.apisByFolder.get(folder) || [] 54 | apis.forEach(api => api.onFileChanged(path)) 55 | }) 56 | 57 | watcher.onDidCreate(async (uri) => { 58 | const path = normalize(uri.fsPath) 59 | if (await this.shouldIgnoreFile(api, path, uri)) { 60 | return 61 | } 62 | log.verbose?.('[VSCODE] File created:', this.relative(api, uri)) 63 | const apis = this.apisByFolder.get(folder) || [] 64 | apis.forEach(api => api.onFileCreated(path)) 65 | }) 66 | } 67 | 68 | private relative(api: VitestFolderAPI, uri: vscode.Uri) { 69 | return relative(api.workspaceFolder.uri.fsPath, uri.fsPath) 70 | } 71 | 72 | private async shouldIgnoreFile(api: VitestFolderAPI, path: string, uri: vscode.Uri) { 73 | if ( 74 | path.includes('/node_modules/') 75 | || path.includes('/.git/') 76 | || path.endsWith('.git') 77 | ) { 78 | log.verbose?.('[VSCODE] Ignoring file:', this.relative(api, uri)) 79 | return true 80 | } 81 | try { 82 | const stats = await vscode.workspace.fs.stat(uri) 83 | if ( 84 | // if not a file 85 | stats.type !== vscode.FileType.File 86 | // if not a symlinked file 87 | && stats.type !== (vscode.FileType.File | vscode.FileType.SymbolicLink) 88 | ) { 89 | log.verbose?.('[VSCODE]', this.relative(api, uri), 'is not a file. Ignoring.') 90 | return true 91 | } 92 | return false 93 | } 94 | catch (err: unknown) { 95 | log.verbose?.('[VSCODE] Error checking file stats:', this.relative(api, uri), err as string) 96 | return true 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/worker/coverage.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto' 2 | import { existsSync } from 'node:fs' 3 | import { tmpdir } from 'node:os' 4 | import { join } from 'pathe' 5 | import type { CoverageProvider, ResolvedCoverageOptions } from 'vitest/node' 6 | import { finalCoverageFileName } from '../constants' 7 | import type { ExtensionWorker } from './worker' 8 | 9 | export class ExtensionCoverageManager { 10 | private _enabled = false 11 | private _provider: CoverageProvider | null | undefined = undefined 12 | 13 | private _config: ResolvedCoverageOptions 14 | 15 | constructor( 16 | private vitest: ExtensionWorker, 17 | ) { 18 | this._config = vitest.ctx.config.coverage 19 | const projects = new Set([...vitest.ctx.projects, vitest.getRootTestProject()]) 20 | projects.forEach((project) => { 21 | Object.defineProperty(project.config, 'coverage', { 22 | get: () => { 23 | return this.config 24 | }, 25 | set: (coverage: ResolvedCoverageOptions) => { 26 | this._config = coverage 27 | }, 28 | }) 29 | }) 30 | Object.defineProperty(vitest.ctx, 'coverageProvider', { 31 | get: () => { 32 | if (this.enabled) 33 | return this._provider 34 | 35 | return null 36 | }, 37 | set: (provider: CoverageProvider | null) => { 38 | this._provider = provider 39 | }, 40 | }) 41 | } 42 | 43 | public get config(): ResolvedCoverageOptions { 44 | return { 45 | ...this._config, 46 | enabled: this.enabled, 47 | } 48 | } 49 | 50 | public get enabled() { 51 | return this._enabled && !this.vitest.collecting 52 | } 53 | 54 | public get resolved() { 55 | return !!this._provider 56 | } 57 | 58 | public async enable() { 59 | const vitest = this.vitest.ctx 60 | this._enabled = true 61 | 62 | const jsonReporter = this._config.reporter.find(([name]) => name === 'json') 63 | this._config.reporter = [ 64 | ['json', { 65 | ...jsonReporter?.[1], 66 | file: finalCoverageFileName, 67 | }], 68 | ] 69 | this._config.reportOnFailure = true 70 | this._config.reportsDirectory = join(tmpdir(), `vitest-coverage-${randomUUID()}`) 71 | 72 | this.vitest.ctx.logger.log('Running coverage with configuration:', this.config) 73 | 74 | if (!this._provider) { 75 | // @ts-expect-error private method 76 | await vitest.initCoverageProvider() 77 | await vitest.coverageProvider?.clean(this._config.clean) 78 | } 79 | else { 80 | await this._provider.clean(this._config.clean) 81 | } 82 | } 83 | 84 | public disable() { 85 | this._enabled = false 86 | } 87 | 88 | async waitForReport() { 89 | if (!this.enabled) 90 | return null 91 | const ctx = this.vitest.ctx 92 | const coverage = ctx.config.coverage 93 | if (!coverage.enabled || !ctx.coverageProvider) 94 | return null 95 | ctx.logger.error(`Waiting for the coverage report to generate: ${coverage.reportsDirectory}`) 96 | await ctx.runningPromise 97 | if (existsSync(coverage.reportsDirectory)) { 98 | ctx.logger.error(`Coverage reports retrieved: ${coverage.reportsDirectory}`) 99 | return coverage.reportsDirectory 100 | } 101 | ctx.logger.error(`Coverage reports directory not found: ${coverage.reportsDirectory}`) 102 | return null 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/worker/emitter.ts: -------------------------------------------------------------------------------- 1 | import type WebSocket from 'ws' 2 | import type { WorkerEvent } from './types' 3 | 4 | abstract class WorkerEventEmitter { 5 | abstract name: string 6 | 7 | abstract send(event: any): void 8 | abstract on(event: string, listener: (...args: any[]) => void): void 9 | abstract off(event: string, listener: (...args: any[]) => void): void 10 | 11 | ready(configs: string[], workspaceSource: string | false) { 12 | this.sendWorkerEvent({ type: 'ready', configs, workspaceSource }) 13 | } 14 | 15 | error(err: any) { 16 | this.sendWorkerEvent({ type: 'error', error: String(err.stack) }) 17 | } 18 | 19 | debug(...args: string[]) { 20 | this.sendWorkerEvent({ type: 'debug', args }) 21 | } 22 | 23 | protected sendWorkerEvent(event: WorkerEvent) { 24 | this.send(event) 25 | } 26 | } 27 | 28 | export class WorkerWSEventEmitter extends WorkerEventEmitter { 29 | name = 'ws' 30 | 31 | constructor(private ws: WebSocket) { 32 | super() 33 | } 34 | 35 | protected override sendWorkerEvent(event: WorkerEvent): void { 36 | this.ws.send(JSON.stringify(event)) 37 | } 38 | 39 | override send(event: any) { 40 | this.ws.send(event) 41 | } 42 | 43 | override on(event: string, listener: (...args: any[]) => void) { 44 | this.ws.on(event, listener) 45 | } 46 | 47 | override off(event: string, listener: (...args: any[]) => void) { 48 | this.ws.off(event, listener) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/worker/index.ts: -------------------------------------------------------------------------------- 1 | import v8 from 'node:v8' 2 | import { WebSocket } from 'ws' 3 | import { createWorkerRPC } from './rpc' 4 | import type { WorkerRunnerOptions } from './types' 5 | import { initVitest } from './init' 6 | import { WorkerWSEventEmitter } from './emitter' 7 | import { ExtensionWorker } from './worker' 8 | 9 | // this is the file that will be executed with "node " 10 | 11 | const emitter = new WorkerWSEventEmitter( 12 | new WebSocket(process.env.VITEST_WS_ADDRESS!), 13 | ) 14 | 15 | emitter.on('message', async function onMessage(message: any) { 16 | if (emitter.name === 'ws') { 17 | message = JSON.parse(message.toString()) 18 | } 19 | 20 | if (message.type === 'init') { 21 | emitter.off('message', onMessage) 22 | const data = message as WorkerRunnerOptions 23 | 24 | try { 25 | const { reporter, vitest, configs, workspaceSource } = await initVitest( 26 | data.meta, 27 | data.debug 28 | ? { 29 | disableConsoleIntercept: true, 30 | fileParallelism: false, 31 | testTimeout: 0, 32 | hookTimeout: 0, 33 | } 34 | : {}, 35 | ) 36 | 37 | const rpc = createWorkerRPC( 38 | new ExtensionWorker(vitest, data.debug, data.astCollect), 39 | { 40 | on(listener) { 41 | emitter.on('message', listener) 42 | }, 43 | post(message) { 44 | emitter.send(message) 45 | }, 46 | serialize: v8.serialize, 47 | deserialize: v => v8.deserialize(Buffer.from(v) as any), 48 | }, 49 | ) 50 | reporter.initRpc(rpc) 51 | emitter.ready(configs, workspaceSource) 52 | } 53 | catch (err: any) { 54 | emitter.error(err) 55 | } 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /src/worker/init.ts: -------------------------------------------------------------------------------- 1 | import { pathToFileURL } from 'node:url' 2 | import type { UserConfig, WorkspaceProject } from 'vitest/node' 3 | import { VSCodeReporter } from './reporter' 4 | import type { WorkerInitMetadata } from './types' 5 | import { normalizeDriveLetter } from './utils' 6 | 7 | export async function initVitest(meta: WorkerInitMetadata, options?: UserConfig) { 8 | const vitestModule = await import( 9 | pathToFileURL(normalizeDriveLetter(meta.vitestNodePath)).toString() 10 | ) as typeof import('vitest/node') 11 | const reporter = new VSCodeReporter() 12 | const pnpExecArgv = meta.pnpApi && meta.pnpLoader 13 | ? [ 14 | '--require', 15 | meta.pnpApi, 16 | '--experimental-loader', 17 | meta.pnpLoader, 18 | ] 19 | : undefined 20 | const args = meta.arguments 21 | ? vitestModule.parseCLI(meta.arguments, { 22 | allowUnknownOptions: false, 23 | }).options 24 | : {} 25 | const vitest = await vitestModule.createVitest( 26 | 'test', 27 | { 28 | config: meta.configFile, 29 | ...(meta.workspaceFile ? { workspace: meta.workspaceFile } : {}), 30 | ...args, 31 | ...options, 32 | watch: true, 33 | api: false, 34 | // @ts-expect-error private property 35 | reporter: undefined, 36 | reporters: meta.shellType === 'terminal' 37 | ? [reporter, ['default', { isTTY: false }]] 38 | : [reporter], 39 | ui: false, 40 | includeTaskLocation: true, 41 | poolOptions: meta.pnpApi && meta.pnpLoader 42 | ? { 43 | threads: { 44 | execArgv: pnpExecArgv, 45 | }, 46 | forks: { 47 | execArgv: pnpExecArgv, 48 | }, 49 | vmForks: { 50 | execArgv: pnpExecArgv, 51 | }, 52 | vmThreads: { 53 | execArgv: pnpExecArgv, 54 | }, 55 | } 56 | : {}, 57 | }, 58 | { 59 | server: { 60 | middlewareMode: true, 61 | // when support for Vite 4 is dropped, set to `null` 62 | watch: { 63 | usePolling: true, 64 | ignored: ['**/*'], 65 | depth: 0, 66 | followSymlinks: false, 67 | }, 68 | }, 69 | plugins: [ 70 | { 71 | name: 'vitest:vscode-extension', 72 | configureServer(server) { 73 | server.watcher.close() 74 | }, 75 | configResolved(config) { 76 | // stub a server so Vite doesn't start a websocket connection, 77 | // because we don't need it in the extension and it messes up Vite dev command 78 | config.server.hmr = { 79 | server: { 80 | on: () => {}, 81 | off: () => {}, 82 | } as any, 83 | } 84 | }, 85 | }, 86 | ], 87 | }, 88 | ) 89 | await vitest.report('onInit', vitest) 90 | const configs = ([ 91 | // @ts-expect-error -- getRootProject in Vitest 3.0 92 | 'getRootProject' in vitest ? vitest.getRootProject() : vitest.getCoreWorkspaceProject(), 93 | ...vitest.projects, 94 | ] as WorkspaceProject[]).map(p => p.server.config.configFile).filter(c => c != null) 95 | const workspaceSource: string | false = meta.workspaceFile 96 | ? meta.workspaceFile 97 | : vitest.config.workspace != null 98 | ? vitest.server.config.configFile || false 99 | : false 100 | return { 101 | vitest, 102 | reporter, 103 | workspaceSource, 104 | configs: Array.from(new Set(configs)), 105 | meta, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/worker/rpc.ts: -------------------------------------------------------------------------------- 1 | import type { ChannelOptions } from 'birpc' 2 | import { createBirpc } from 'birpc' 3 | import type { ExtensionWorkerEvents, ExtensionWorkerTransport } from '../api/rpc' 4 | import type { ExtensionWorker } from './worker' 5 | 6 | export function createWorkerRPC(vitest: ExtensionWorker, channel: ChannelOptions) { 7 | const rpc = createBirpc(vitest, { 8 | timeout: -1, 9 | bind: 'functions', 10 | eventNames: [ 11 | 'onConsoleLog', 12 | 'onTaskUpdate', 13 | 'onFinished', 14 | 'onCollected', 15 | 'onWatcherRerun', 16 | 'onWatcherStart', 17 | ], 18 | ...channel, 19 | }) 20 | return rpc 21 | } 22 | -------------------------------------------------------------------------------- /src/worker/setupFile.ts: -------------------------------------------------------------------------------- 1 | import type { WorkerGlobalState } from 'vitest' 2 | import { inject } from 'vitest' 3 | import { assert } from './utils' 4 | 5 | const { watchEveryFile, continuousFiles, rerunTriggered } = inject('__vscode') 6 | // @ts-expect-error injected global 7 | const workerState = globalThis.__vitest_worker__ as WorkerGlobalState 8 | 9 | const testFile = workerState.filepath! 10 | 11 | assert(testFile, 'Expected workerState.filepath to be set') 12 | 13 | // don't run tests that are not watched if rerun was triggered - only collect those tests 14 | if (rerunTriggered) { 15 | if (!watchEveryFile && !testFileWatched()) 16 | // eslint-disable-next-line regexp/no-useless-assertions 17 | workerState.config.testNamePattern = /$a/ 18 | } 19 | 20 | function testFileWatched() { 21 | return continuousFiles.some((file) => { 22 | if (file === testFile) 23 | return true 24 | if (file[file.length - 1] === '/') 25 | return testFile.startsWith(file) 26 | return false 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/worker/types.ts: -------------------------------------------------------------------------------- 1 | export interface WorkerInitMetadata { 2 | vitestNodePath: string 3 | id: string 4 | cwd: string 5 | arguments?: string 6 | configFile?: string 7 | workspaceFile?: string 8 | env: Record | undefined 9 | shellType: 'terminal' | 'child_process' 10 | pnpApi?: string 11 | pnpLoader?: string 12 | } 13 | 14 | export interface WorkerRunnerOptions { 15 | type: 'init' 16 | meta: WorkerInitMetadata 17 | debug: boolean 18 | astCollect: boolean 19 | } 20 | 21 | export interface EventReady { 22 | type: 'ready' 23 | configs: string[] 24 | workspaceSource: string | false 25 | } 26 | 27 | export interface EventDebug { 28 | type: 'debug' 29 | args: string[] 30 | } 31 | 32 | export interface EventError { 33 | type: 'error' 34 | error: string 35 | } 36 | 37 | export type WorkerEvent = EventReady | EventDebug | EventError 38 | 39 | declare module 'vitest' { 40 | export interface ProvidedContext { 41 | __vscode: { 42 | continuousFiles: string[] 43 | watchEveryFile: boolean 44 | rerunTriggered: boolean 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/worker/utils.ts: -------------------------------------------------------------------------------- 1 | // A compact (code-wise, probably not memory-wise) singly linked list node. 2 | type QueueNode = [value: T, next?: QueueNode] 3 | 4 | /** 5 | * Return a function for running multiple async operations with limited concurrency. 6 | */ 7 | export function limitConcurrency(concurrency = Number.POSITIVE_INFINITY): (func: (...args: Args) => PromiseLike | T, ...args: Args) => Promise { 8 | // The number of currently active + pending tasks. 9 | let count = 0 10 | 11 | // The head and tail of the pending task queue, built using a singly linked list. 12 | // Both head and tail are initially undefined, signifying an empty queue. 13 | // They both become undefined again whenever there are no pending tasks. 14 | let head: undefined | QueueNode<() => void> 15 | let tail: undefined | QueueNode<() => void> 16 | 17 | // A bookkeeping function executed whenever a task has been run to completion. 18 | const finish = () => { 19 | count-- 20 | 21 | // Check if there are further pending tasks in the queue. 22 | if (head) { 23 | // Allow the next pending task to run and pop it from the queue. 24 | head[0]() 25 | head = head[1] 26 | 27 | // The head may now be undefined if there are no further pending tasks. 28 | // In that case, set tail to undefined as well. 29 | tail = head && tail 30 | } 31 | } 32 | 33 | return (func, ...args) => { 34 | // Create a promise chain that: 35 | // 1. Waits for its turn in the task queue (if necessary). 36 | // 2. Runs the task. 37 | // 3. Allows the next pending task (if any) to run. 38 | return new Promise((resolve) => { 39 | if (count++ < concurrency) { 40 | // No need to queue if fewer than maxConcurrency tasks are running. 41 | resolve() 42 | } 43 | else if (tail) { 44 | // There are pending tasks, so append to the queue. 45 | tail = tail[1] = [resolve] 46 | } 47 | else { 48 | // No other pending tasks, initialize the queue with a new tail and head. 49 | head = tail = [resolve] 50 | } 51 | }).then(() => { 52 | // Running func here ensures that even a non-thenable result or an 53 | // immediately thrown error gets wrapped into a Promise. 54 | return func(...args) 55 | }).finally(finish) 56 | } 57 | } 58 | 59 | export function assert(condition: unknown, message: string | (() => string)): asserts condition { 60 | if (!condition) { 61 | throw new Error(typeof message === 'string' ? message : message()) 62 | } 63 | } 64 | 65 | export function normalizeDriveLetter(path: string) { 66 | if (process.platform !== 'win32') 67 | return path 68 | const currentDriveLetter = __dirname[0] 69 | const letterCase = currentDriveLetter === currentDriveLetter.toUpperCase() 70 | ? 'uppercase' 71 | : 'lowercase' 72 | const targetDriveLetter = path[0] 73 | if (letterCase === 'lowercase') { 74 | const driveLetter = targetDriveLetter.toLowerCase() 75 | return driveLetter + path.slice(1) 76 | } 77 | const driveLetter = targetDriveLetter.toUpperCase() 78 | return driveLetter + path.slice(1) 79 | } 80 | -------------------------------------------------------------------------------- /syntaxes/LICENSE: -------------------------------------------------------------------------------- 1 | vitest-snapshot.tmLanguage is forked from jest-community/vscode-jest 2 | 3 | https://github.com/jest-community/vscode-jest/blob/ab4e5c6e041bca12c9cf9fa3157002e55315a4d7/syntaxes/jest-snapshot.tmLanguage 4 | 5 | It is based on Microsoft/TypeScript-TmLanguage 6 | 7 | https://github.com/Microsoft/TypeScript-TmLanguage/blob/55e9f737b722895943e6647a9392bf286ed6af55/TypeScriptReact.tmLanguage 8 | 9 | Under this license: 10 | 11 | https://github.com/Microsoft/TypeScript-TmLanguage/blob/master/LICENSE.txt 12 | 13 | Copyright (c) Microsoft Corporation 14 | All rights reserved. 15 | 16 | MIT License 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 34 | THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /test-e2e/README.md: -------------------------------------------------------------------------------- 1 | # test-e2e 2 | 3 | To open playwright inspector, either run with `PWDEBUG` 4 | 5 | ```sh 6 | PWDEBUG=1 pnpm test-e2e 7 | ``` 8 | 9 | or use `page.pause` inside a test code 10 | 11 | ```ts 12 | await page.pase() 13 | ``` 14 | -------------------------------------------------------------------------------- /test-e2e/assertions.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@playwright/test' 2 | import type { TesterTestItem } from './tester' 3 | 4 | type TestState = 'passed' | 'failed' | 'skipped' | 'waiting' 5 | interface TestsTree { 6 | [test: string]: TestState | TestsTree 7 | } 8 | 9 | function getTitleFromState(state: TestState) { 10 | switch (state) { 11 | case 'passed': 12 | return '(Passed)' 13 | case 'failed': 14 | return '(Failed)' 15 | case 'skipped': 16 | return '(Skipped)' 17 | case 'waiting': 18 | return '(Not yet run)' 19 | } 20 | } 21 | 22 | expect.extend({ 23 | async toHaveState(item: TesterTestItem, state: TestState) { 24 | const title = await item.locator.getAttribute('aria-label') 25 | const pass = !!(title && title.includes(getTitleFromState(state))) 26 | 27 | return { 28 | pass, 29 | message: () => `${this.utils.matcherHint('toHaveState', title, state, { isNot: this.isNot })}\n\n` 30 | + `Locator: ${item.locator}\n` 31 | + `Expected: ${this.isNot ? 'not ' : ''}to have state: ${this.utils.printExpected(state)}\n` 32 | + `Received: ${this.utils.printReceived(title)}\n`, 33 | name: 'toHaveState', 34 | } 35 | }, 36 | async toHaveError(item: TesterTestItem, error: string) { 37 | const page = item.page 38 | const depth = Number(await item.locator.getAttribute('aria-level')) 39 | 40 | await expect(page.locator(`[aria-label*="${error}"][aria-level="${depth + 1}"]`)).toBeVisible() 41 | 42 | return { 43 | pass: true, 44 | message: () => '', 45 | } 46 | }, 47 | async toHaveTests(item: TesterTestItem, tests: TestsTree) { 48 | const page = item.page 49 | const depth = Number(await item.locator.getAttribute('aria-level')) 50 | const currentIndex = Number(await item.locator.getAttribute('data-index')) 51 | 52 | async function assert(test: string, level: number, state: TestState, itemIndex: number) { 53 | const [name, index = itemIndex] = test.split('|') 54 | let locator = `[aria-label*="${name} ${getTitleFromState(state)}"][aria-level="${level}"]` 55 | if (index) { 56 | locator += `[data-index="${index}"]` 57 | } 58 | await expect(page.locator(locator)).toBeAttached() 59 | } 60 | 61 | const counter = { index: currentIndex } 62 | 63 | async function traverse(tests: TestsTree, level = depth + 1) { 64 | for (const test in tests) { 65 | counter.index++ 66 | 67 | const item = tests[test] 68 | if (typeof item === 'string') { 69 | await assert(test, level, item, counter.index) 70 | } 71 | else { 72 | const [name, index = counter.index] = test.split('|') 73 | let locator = `[aria-label*="${name}"][aria-level="${level}"]` 74 | if (index) { 75 | locator += `[data-index="${index}"]` 76 | } 77 | await expect(page.locator(locator)).toBeAttached() 78 | 79 | await traverse(item, level + 1) 80 | } 81 | } 82 | } 83 | 84 | await traverse(tests) 85 | 86 | return { 87 | pass: true, 88 | message: () => '', 89 | } 90 | }, 91 | }) 92 | 93 | declare global { 94 | // eslint-disable-next-line ts/no-namespace 95 | export namespace PlaywrightTest { 96 | // eslint-disable-next-line unused-imports/no-unused-vars 97 | export interface Matchers { 98 | toHaveState: (state: TestState) => Promise 99 | /** 100 | * @example 101 | * expect(tester.tree.getFileItem('no-import.test.ts')).toHaveTests({ 102 | * multiply: 'waiting', 103 | * divide: 'waiting', 104 | * suite: { 105 | * 'some old test name': 'waiting', 106 | * }, 107 | * }) 108 | */ 109 | toHaveTests: (tests: TestsTree) => Promise 110 | toHaveError: (error: string) => Promise 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test-e2e/discovery.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import type { RunnerTestCase, RunnerTestSuite } from 'vitest' 3 | import { describe, expect, it } from 'vitest' 4 | import { createVitest } from 'vitest/node' 5 | import { astCollectTests } from '../src/worker/collect' 6 | 7 | describe('can discover tests', () => { 8 | it.for([ 9 | 'todo-import-suite.ts', 10 | 'todo-globals-suite.ts', 11 | ])('can discover todo tests inside a suite in %s', async (fixture) => { 12 | const vitest = await createVitest('test', { config: false }) 13 | const file = await astCollectTests( 14 | vitest.getCoreWorkspaceProject(), 15 | resolve(`test-e2e/fixtures/collect/${fixture}`), 16 | 'web', 17 | ) 18 | expect(file.filepath).toBe(resolve(`test-e2e/fixtures/collect/${fixture}`)) 19 | expect(file.name).toBe(`test-e2e/fixtures/collect/${fixture}`) 20 | 21 | expect(file.tasks).toHaveLength(1) 22 | const suite = file.tasks[0] as RunnerTestSuite 23 | expect(suite.name).toBe('TicketDetailBottomBar') 24 | expect(suite.mode).toBe('run') 25 | expect(suite.location).toMatchObject({ 26 | line: 3, 27 | column: 0, 28 | }) 29 | expect(suite.tasks).toHaveLength(2) 30 | 31 | const [testTask, suiteTask] = suite.tasks as [RunnerTestCase, RunnerTestSuite] 32 | expect(testTask.name).toBe('emits %s event when button is clicked') 33 | expect((testTask as any).dynamic).toBe(true) 34 | expect(testTask.mode).toBe('run') 35 | expect(testTask.location).toMatchObject({ 36 | line: 4, 37 | column: 31, // TODO: should it be 5 instead? 38 | }) 39 | 40 | expect(suiteTask.name).toBe('Drafts') 41 | expect(suiteTask.mode).toBe('skip') 42 | expect(suiteTask.location).toMatchObject({ 43 | line: 10, 44 | column: 2, 45 | }) 46 | expect(suiteTask.tasks).toHaveLength(2) 47 | 48 | const [todo1, todo2] = suiteTask.tasks as RunnerTestCase[] 49 | expect(todo1.name).toBe('should not display draft information if ticket has no draft') 50 | expect(todo1.mode).toBe('todo') 51 | expect(todo1.location).toMatchObject({ 52 | line: 11, 53 | column: 4, 54 | }) 55 | 56 | expect(todo2.name).toBe('should display draft information if ticket has a draft') 57 | expect(todo2.mode).toBe('todo') 58 | expect(todo2.location).toMatchObject({ 59 | line: 12, 60 | column: 4, 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /test-e2e/downloadSetup.ts: -------------------------------------------------------------------------------- 1 | import { download } from '@vscode/test-electron' 2 | import type { GlobalSetupContext } from 'vitest/node' 3 | 4 | export default async function downloadVscode({ provide }: GlobalSetupContext) { 5 | if (process.env.VSCODE_E2E_DOWNLOAD_PATH) 6 | provide('executablePath', process.env.VSCODE_E2E_DOWNLOAD_PATH) 7 | else 8 | provide('executablePath', await download()) 9 | } 10 | 11 | declare module 'vitest' { 12 | export interface ProvidedContext { 13 | executablePath: string 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-e2e/fixtures/collect/todo-globals-suite.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | describe('TicketDetailBottomBar', () => { 4 | it.each(['submit', 'discard'])( 5 | 'emits %s event when button is clicked', 6 | async (eventName) => { 7 | }, 8 | ) 9 | 10 | describe('Drafts', () => { 11 | it.todo('should not display draft information if ticket has no draft') 12 | it.todo('should display draft information if ticket has a draft') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test-e2e/fixtures/collect/todo-import-suite.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | 3 | describe('TicketDetailBottomBar', () => { 4 | it.each(['submit', 'discard'])( 5 | 'emits %s event when button is clicked', 6 | async (eventName) => { 7 | }, 8 | ) 9 | 10 | describe('Drafts', () => { 11 | it.todo('should not display draft information if ticket has no draft') 12 | it.todo('should display draft information if ticket has a draft') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test-e2e/helper.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import os from 'node:os' 3 | import path, { resolve } from 'node:path' 4 | import { _electron } from '@playwright/test' 5 | import type { Page } from '@playwright/test' 6 | import type { Awaitable } from 'vitest' 7 | import { test as baseTest, inject } from 'vitest' 8 | import { VSCodeTester } from './tester' 9 | 10 | // based on 11 | // https://github.com/microsoft/playwright-vscode/blob/1c2f766a3ef4b7633fb19103a3d930ebe385250e/tests-integration/tests/baseTest.ts#L41 12 | 13 | interface Context { 14 | page: Page 15 | tester: VSCodeTester 16 | } 17 | 18 | type LaunchFixture = (options: { 19 | extensionPath?: string 20 | workspacePath?: string 21 | trace?: 'on' | 'off' 22 | }) => Promise Awaitable) => Promise 24 | }> 25 | 26 | const defaultConfig = process.env as { 27 | VSCODE_E2E_EXTENSION_PATH?: string 28 | VSCODE_E2E_WORKSPACE_PATH?: string 29 | VSCODE_E2E_TRACE?: 'on' | 'off' 30 | } 31 | 32 | export const test = baseTest.extend<{ launch: LaunchFixture; taskName: string; logPath: string }>({ 33 | taskName: async ({ task }, use) => use(`${task.name}-${task.id}`), 34 | logPath: async ({ taskName }, use) => use(resolve(`./tests-logs-${taskName}.txt`)), 35 | launch: async ({ taskName, logPath }, use) => { 36 | const teardowns: (() => Promise)[] = [] 37 | 38 | await use(async (options) => { 39 | const executablePath = inject('executablePath') 40 | const extensionPath = options.extensionPath ?? defaultConfig.VSCODE_E2E_EXTENSION_PATH 41 | const workspacePath = options.workspacePath ?? defaultConfig.VSCODE_E2E_WORKSPACE_PATH 42 | const trace = (options.trace ?? defaultConfig.VSCODE_E2E_TRACE) === 'on' 43 | 44 | const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'vscode-e2e-')) 45 | const app = await _electron.launch({ 46 | executablePath, 47 | env: { 48 | ...process.env, 49 | VITEST_VSCODE_E2E_LOG_FILE: logPath, 50 | VITEST_VSCODE_LOG: 'verbose', 51 | }, 52 | args: [ 53 | '--no-sandbox', 54 | '--disable-gpu-sandbox', 55 | '--disable-updates', 56 | '--skip-welcome', 57 | '--skip-release-notes', 58 | '--disable-workspace-trust', 59 | `--extensions-dir=${path.join(tempDir, 'extensions')}`, 60 | `--user-data-dir=${path.join(tempDir, 'user-data')}`, 61 | extensionPath && `--extensionDevelopmentPath=${path.resolve(extensionPath)}`, 62 | workspacePath && `--folder-uri=file:${path.resolve(workspacePath)}`, 63 | ].filter((v): v is string => !!v), 64 | }) 65 | const page = await app.firstWindow() 66 | 67 | if (trace) 68 | await page.context().tracing.start({ screenshots: true, snapshots: true }) 69 | 70 | const teardown = async () => { 71 | if (trace) { 72 | await page.context().tracing.stop({ path: `test-results/${taskName}/basic.zip` }) 73 | } 74 | await app.close() 75 | await fs.promises.rm(tempDir, { recursive: true, force: true }) 76 | } 77 | teardowns.push(teardown) 78 | 79 | const tester = new VSCodeTester(page) 80 | 81 | async function step(name: string, fn: (context: Context) => Awaitable) { 82 | await page.reload() 83 | try { 84 | await fn({ page, tester }) 85 | } 86 | catch (err) { 87 | throw new Error(`Error during step "${name}"`, { cause: err }) 88 | } 89 | } 90 | 91 | await tester.openTestTab() 92 | 93 | return { page, tester, step } 94 | }) 95 | 96 | for (const teardown of teardowns) 97 | await teardown() 98 | }, 99 | }) 100 | -------------------------------------------------------------------------------- /test-e2e/tester.ts: -------------------------------------------------------------------------------- 1 | import { basename } from 'node:path' 2 | import fs from 'node:fs' 3 | import type { Locator, Page } from '@playwright/test' 4 | import { afterEach } from 'vitest' 5 | 6 | export class VSCodeTester { 7 | public tree: TesterTree 8 | public errors: TesterErrorOutput 9 | 10 | constructor( 11 | private page: Page, 12 | ) { 13 | this.tree = new TesterTree(page) 14 | this.errors = new TesterErrorOutput(page) 15 | } 16 | 17 | async openTestTab() { 18 | const tabLocator = this.page.getByRole('tab', { name: 'Testing' }) 19 | const attribute = await tabLocator.getAttribute('aria-selected') 20 | if (attribute !== 'true') 21 | await tabLocator.locator('a').click() 22 | } 23 | 24 | async runAllTests() { 25 | await this.page 26 | .getByRole('toolbar', { name: 'Testing actions', exact: true }) 27 | .getByRole('button', { name: /^Run Tests$/ }) 28 | .first() 29 | .click() 30 | } 31 | } 32 | 33 | class TesterTree { 34 | constructor( 35 | private page: Page, 36 | ) {} 37 | 38 | getResultsLocator() { 39 | return this.page.locator(`.result-summary > [custom-hover]`) 40 | } 41 | 42 | getFileItem(file: string) { 43 | const name = basename(file) 44 | return new TesterTestItem(name, this.page.locator(`[aria-label*="${name} ("]`), this.page) 45 | } 46 | 47 | async expand(path: string) { 48 | const segments = path.split('/') 49 | for (let i = 0; i < segments.length; i++) { 50 | const segment = segments[i] 51 | const locator = this.page 52 | // not yet run 53 | .locator(`[aria-label*="${segment} ("][aria-level="${i + 1}"]`) 54 | // test already run 55 | .or(this.page.locator(`[aria-label="${segment}"][aria-level="${i + 1}"]`)) 56 | const state = await locator.getAttribute('aria-expanded') 57 | if (state === 'true') 58 | continue 59 | await locator.click() 60 | } 61 | } 62 | } 63 | 64 | class TesterErrorOutput { 65 | constructor( 66 | private page: Page, 67 | ) {} 68 | 69 | async getInlineErrors() { 70 | const locator = this.page.locator('.test-error-content-widget') 71 | const text = await locator.allInnerTexts() 72 | return text.map(t => t.trim().replace(/\s/g, ' ')) 73 | } 74 | 75 | async getInlineExpectedOutput() { 76 | return await this.page.locator( 77 | '.test-output-peek .editor.original .view-lines[role="presentation"]', 78 | ).textContent() 79 | } 80 | 81 | async getInlineActualOutput() { 82 | return await this.page.locator( 83 | '.test-output-peek .editor.modified .view-lines[role="presentation"]', 84 | ).textContent() 85 | } 86 | } 87 | 88 | export class TesterTestItem { 89 | constructor( 90 | public name: string, 91 | public locator: Locator, 92 | public page: Page, 93 | ) {} 94 | 95 | async run() { 96 | await this.locator.hover() 97 | await this.locator.getByLabel('Run Test', { exact: true }).click() 98 | } 99 | 100 | async debug() { 101 | await this.locator.hover() 102 | await this.locator.getByLabel('Debug Test', { exact: true }).click() 103 | } 104 | 105 | async coverage() { 106 | await this.locator.hover() 107 | await this.locator.getByLabel('Run Test with Coverage', { exact: true }).click() 108 | } 109 | 110 | async toggleContinuousRun() { 111 | await this.locator.hover() 112 | await this.locator.getByLabel(/Turn (on|off) Continuous Run/).click() 113 | } 114 | 115 | async navigate() { 116 | await this.locator.click({ force: true }) 117 | await this.locator.press('Alt+Enter') 118 | // await this.locator.getByLabel(/Go to Test/).click() 119 | // wait until the page is navigated 120 | await this.page.getByRole('tab', { name: new RegExp(this.name) }).waitFor() 121 | } 122 | } 123 | 124 | const originalFiles = new Map() 125 | const createdFiles = new Set() 126 | afterEach(() => { 127 | originalFiles.forEach((content, file) => { 128 | fs.writeFileSync(file, content, 'utf-8') 129 | }) 130 | createdFiles.forEach((file) => { 131 | if (fs.existsSync(file)) 132 | fs.unlinkSync(file) 133 | }) 134 | originalFiles.clear() 135 | createdFiles.clear() 136 | }) 137 | 138 | export function editFile(file: string, callback: (content: string) => string) { 139 | const content = fs.readFileSync(file, 'utf-8') 140 | if (!originalFiles.has(file)) 141 | originalFiles.set(file, content) 142 | fs.writeFileSync(file, callback(content), 'utf-8') 143 | } 144 | -------------------------------------------------------------------------------- /test-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"] 3 | } 4 | -------------------------------------------------------------------------------- /test-e2e/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | // use Infinity on local for `page.pause()` 6 | testTimeout: process.env.CI ? 60_000 : Number.POSITIVE_INFINITY, 7 | fileParallelism: false, 8 | env: { 9 | VSCODE_E2E_EXTENSION_PATH: './', 10 | VSCODE_E2E_TRACE: 'on', 11 | }, 12 | setupFiles: [ 13 | './assertions.ts', 14 | ], 15 | globalSetup: [ 16 | './downloadSetup.ts', 17 | ], 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /test/TestData.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path' 2 | import * as vscode from 'vscode' 3 | import { expect } from 'chai' 4 | import { TestCase, TestFile, TestFolder, TestSuite, getTestData } from '../src/testTreeData' 5 | 6 | describe('TestData', () => { 7 | const ctrl = vscode.tests.createTestController('mocha', 'Vitest') 8 | describe('TestFile', () => { 9 | it('getTestNamePattern', async () => { 10 | const filepath = path.resolve(__dirname, './testdata/discover/00_simple.ts') 11 | const uri = vscode.Uri.file(filepath) 12 | const folderItem = ctrl.createTestItem( 13 | path.dirname(filepath), 14 | path.basename(path.dirname(filepath)), 15 | uri, 16 | ) 17 | TestFolder.register(folderItem) 18 | const testItem = ctrl.createTestItem( 19 | filepath, 20 | path.basename(filepath), 21 | uri, 22 | ) 23 | ctrl.items.add(testItem) 24 | const file = TestFile.register( 25 | testItem, 26 | folderItem, 27 | filepath, 28 | null as any, // not used yet 29 | '', 30 | ) 31 | const suiteItem = ctrl.createTestItem( 32 | `${filepath}_1`, 33 | 'describe', 34 | uri, 35 | ) 36 | testItem.children.add(suiteItem) 37 | 38 | const testItem1 = ctrl.createTestItem( 39 | `${filepath}_1_1`, 40 | 'test', 41 | uri, 42 | ) 43 | 44 | const testItem2 = ctrl.createTestItem( 45 | `${filepath}_1_2`, 46 | 'test 1', 47 | uri, 48 | ) 49 | 50 | const testItem3 = ctrl.createTestItem( 51 | `${filepath}_1_3`, 52 | 'test 2', 53 | uri, 54 | ) 55 | 56 | suiteItem.children.add(testItem1) 57 | suiteItem.children.add(testItem2) 58 | suiteItem.children.add(testItem3) 59 | 60 | const suite = TestSuite.register(suiteItem, testItem, file, false) 61 | 62 | expect(suite.getTestNamePattern()).to.equal('^\\s?describe') 63 | 64 | const test1 = TestCase.register(testItem1, suiteItem, file, false) 65 | const test2 = TestCase.register(testItem2, suiteItem, file, false) 66 | const test3 = TestCase.register(testItem3, suiteItem, file, false) 67 | 68 | expect(testItem1.parent).to.exist 69 | 70 | expect(test1.getTestNamePattern()).to.equal('^\\s?describe test$') 71 | expect(test2.getTestNamePattern()).to.equal('^\\s?describe test 1$') 72 | expect(test3.getTestNamePattern()).to.equal('^\\s?describe test 2$') 73 | }) 74 | 75 | it('throws an error if data was not set', () => { 76 | expect(() => getTestData({ label: 'invalid test' } as any)).to.throw(/Test data not found for "invalid test"/) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/config.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import { homedir } from 'node:os' 3 | import { expect } from 'chai' 4 | import { resolveConfigPath } from '../src/config' 5 | 6 | it('correctly resolves ~', () => { 7 | expect(resolveConfigPath('~/test')).to.equal( 8 | resolve(homedir(), 'test'), 9 | ) 10 | }) 11 | -------------------------------------------------------------------------------- /test/pkg.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { findFirstUniqueFolderNames } from '../src/api/pkg' 3 | 4 | it('correctly makes prefixes unique', () => { 5 | expect(findFirstUniqueFolderNames([ 6 | '/User/usr/vitest/packages/pkg1/react/vitest.config.ts', 7 | '/User/usr/vitest/packages/pkg2/react/vitest.config.ts', 8 | '/User/usr/vitest/packages/pkg2/some-new-field/react/vitest.config.ts', 9 | '/User/usr/vitest/react/vitest.config.ts', 10 | ])).to.eql([ 11 | 'pkg1', 12 | 'pkg2', 13 | 'some-new-field', 14 | 'vitest', 15 | ]) 16 | }) 17 | -------------------------------------------------------------------------------- /test/testdata/discover/00_simple.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | describe('describe', () => { 4 | it('test', () => { 5 | expect(1).toBe(1) 6 | }) 7 | it.each([1, 2, 3])("test %i", (a) => { 8 | expect(a).toBe(a); 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "compilerOptions": { 4 | "types": ["@types/mocha"] 5 | }, 6 | "include": ["."] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["ESNext"], 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "types": [], 8 | "strict": true, 9 | "noEmit": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["./tsconfig.base.json"], 3 | "include": ["src", "./debug-shims.d.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig([ 4 | { 5 | entry: ['./src/extension.ts'], 6 | external: ['vscode'], 7 | format: 'cjs', 8 | define: { 9 | 'process.env.EXTENSION_NODE_ENV': JSON.stringify(process.env.EXTENSION_NODE_ENV || 'production'), 10 | }, 11 | }, 12 | { 13 | entry: { 14 | worker: './src/worker/index.ts', 15 | }, 16 | format: 'cjs', 17 | }, 18 | { 19 | entry: ['./src/worker/setupFile.ts'], 20 | external: ['vitest'], 21 | format: 'esm', 22 | }, 23 | ]) 24 | -------------------------------------------------------------------------------- /vitest.workspace.vscode.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config' 2 | 3 | export default defineWorkspace([ 4 | './test-e2e', 5 | ]) 6 | --------------------------------------------------------------------------------