Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vscode. VS Code extension for Vitest vitest.dev/vscode
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469import { readFileSync, rmSync } from 'node:fs'import { createRequire } from 'node:module'import { resolve } from 'node:path'import { beforeAll, beforeEach, describe, onTestFailed } from 'vitest'import { expect } from '@playwright/test'import { test } from './utils/helper'import { addFile, deleteFile, editFile, renameFile } from './utils/tester'
// Vitst extension doesn't work with CI flagbeforeAll(() => { delete process.env.CI delete process.env.GITHUB_ACTIONS})
beforeEach<{ logPath: string }>(({ logPath }) => { onTestFailed(() => { console.error(`Log during test:\n${readFileSync(logPath, 'utf-8')}`) if (!process.env.CI) { rmSync(logPath) } })})
test('basic', async ({ launch }) => { const { page, tester } = await launch({ workspacePath: './samples/e2e', })
await expect(page.getByText('No test results yet.')).toBeVisible()
await tester.tree.expand('test') await tester.tree.expand('test/pass.test.ts')
const allPassTests = tester.tree.getFileItem('pass.test.ts') await expect(allPassTests).toHaveTests({ 'all-pass': 'waiting', })
await tester.tree.expand('test/fail.test.ts')
const allFailTests = tester.tree.getFileItem('fail.test.ts') await expect(allFailTests).toHaveTests({ 'all-fail': 'waiting', })
await tester.tree.expand('test/mix.test.ts')
const mixedTests = tester.tree.getFileItem('mix.test.ts') await expect(mixedTests).toHaveTests({ 'mix-pass': 'waiting', 'mix-fail': 'waiting', })
await tester.runAllTests()
await expect(tester.tree.getResultsLocator()).toHaveText('2/4') await expect(allPassTests).toHaveState('passed') await expect(allPassTests).toHaveTests({ 'all-pass': 'passed', }) await expect(allFailTests).toHaveState('failed') await expect(allFailTests).toHaveTests({ 'all-fail': 'failed', }) await expect(mixedTests).toHaveState('failed') await expect(mixedTests).toHaveTests({ 'mix-pass': 'passed', 'mix-fail': 'failed', })})
test('workspaces', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/monorepo-vitest-workspace', }) await tester.tree.expand('packages/react/test')
const basicTest = tester.tree.getFileItem('basic.test.tsx [@vitest/test-react]')
await expect(basicTest.locator).toBeVisible() await expect(tester.tree.getFileItem('basic.test.tsx [0]').locator).toBeVisible()
await basicTest.run()
// only runs tests in a single workspace project await expect(tester.tree.getResultsLocator()).toHaveText('1/1')
await tester.runAllTests()
await expect(tester.tree.getResultsLocator()).toHaveText('4/4')})
test('running a project does not update other projects', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/projects', })
await tester.tree.expand('test')
const nodeTest = tester.tree.getFileItem('basic.test.ts', 'node') const happyDomTest = tester.tree.getFileItem('basic.test.ts', 'happy-dom')
await expect(nodeTest.locator).toBeVisible() await expect(happyDomTest.locator).toBeVisible()
await tester.tree.expand('test/basic.test.ts [node]') await tester.tree.expand('test/basic.test.ts [happy-dom]')
await expect(nodeTest).toHaveTests({ 'check|4': 'waiting', }) await expect(happyDomTest).toHaveTests({ 'check|2': 'waiting', })
await nodeTest.run()
await expect(tester.tree.getResultsLocator()).toHaveText('1/1') await expect(nodeTest).toHaveState('passed') await expect(nodeTest).toHaveTests({ check: 'passed', }) // happy-dom project should remain untouched await expect(happyDomTest).toHaveTests({ check: 'waiting', })})
test('custom imba language', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/imba', })
await tester.tree.expand('test') await tester.tree.expand('src/components')
await tester.runAllTests()
await expect(tester.tree.getResultsLocator()).toHaveText('3/4', { timeout: 10_000, }) await expect(tester.tree.getFileItem('basic.test.imba')).toHaveState('passed') await expect(tester.tree.getFileItem('utils.imba')).toHaveState('passed') await expect(tester.tree.getFileItem('counter.imba')).toHaveState('failed')})
test('browser mode correctly collects tests', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/browser', })
await tester.tree.expand('test/console.test.ts [chromium]') const consoleTest = tester.tree.getFileItem('console.test.ts', 'chromium')
await expect(consoleTest).toHaveTests({ console: 'waiting', })
editFile('samples/browser/test/console.test.ts', (content) => `/arakara---\n${content}`)
await expect(consoleTest).toHaveError('Unterminated regular expression')})
test('watcher updates the file if there are several config files', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/multiple-configs', })
await tester.tree.expand('app1/test-app1.test.ts') const app1Test = tester.tree.getFileItem('test-app1.test.ts')
await expect(app1Test).toHaveTests({ math: 'waiting', })
await tester.tree.expand('app2/test-app2.test.ts') const app2Test = tester.tree.getFileItem('test-app2.test.ts')
await expect(app2Test).toHaveTests({ math: 'waiting', })
editFile('samples/multiple-configs/app1/test-app1.test.ts', (content) => content.replace('math', 'math-123'), ) editFile('samples/multiple-configs/app2/test-app2.test.ts', (content) => content.replace('math', 'math-987'), )
await expect(app1Test).toHaveTests({ 'math-123': 'waiting', }) await expect(app2Test).toHaveTests({ 'math-987': 'waiting', })})
// Vitest 5 formats the values in `test.each` titles with pretty-format// instead of loupe, so strings are no longer wrapped in quotesfunction getVitestMajor(sample: string): number { const { version } = createRequire(resolve(sample, 'package.json'))('vitest/package.json') return Number(version.split('.')[0])}
test('ast collector keeps the pattern on rerun', async ({ launch }) => { const sample = 'samples/ast-collector' const tableTestName = getVitestMajor(sample) >= 5 ? 'table1: returns ab when a is added b' : "table1: returns 'ab' when 'a' is added 'b'"
const { tester } = await launch({ workspacePath: sample, })
await tester.tree.expand('test/each.test.ts/testing') // dynamic tests have a "pattern" label await tester.tree.expand('test/each.test.ts/pattern')
const item = tester.tree.getFileItem('each.test.ts')
await expect(item).toHaveTests({ testing: { // all pass: %i => %i 'pattern|3': 'waiting', // table1: returns $expected when $a is added $b 'pattern|4': 'waiting', }, // testing %s 'pattern|5': { 'hello world': 'waiting', // testing %s and %s 'pattern|7': 'waiting', }, })
await tester.runAllTests()
await tester.tree.expand('test/each.test.ts/testing 1') await tester.tree.expand('test/each.test.ts/testing 2')
await expect(item).toHaveTests({ 'testing|2': { // all pass: %i => %i 'pattern|3': 'waiting', 'all pass: 1 => 1': 'passed', 'all pass: 2 => 2': 'passed', // table1: returns $expected when $a is added $b 'pattern|6': 'waiting', 'table1: returns 2 when 1 is added 1': 'passed', [tableTestName]: 'passed', }, // testing %s 'pattern|9': 'waiting', 'testing 1': { 'hello world|11': 'passed', 'pattern|12': 'waiting', 'testing test 3|13': 'passed', 'testing test 4|14': 'passed', }, 'testing 2': { 'hello world|16': 'passed', 'pattern|17': 'waiting', 'testing test 3|18': 'passed', 'testing test 4|19': 'passed', }, })})
describe('continuous testing', () => { test('reruns tests on test file change', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/continuous', })
await tester.tree.expand('test/imports-divide.test.ts') const item = tester.tree.getFileItem('imports-divide.test.ts')
await expect(item).toHaveTests({ divide: 'waiting', })
await item.toggleContinuousRun()
editFile('samples/continuous/test/imports-divide.test.ts', (content) => `${content}\n`)
await expect(item).toHaveTests({ divide: 'passed', })
editFile('samples/continuous/src/calculator.ts', (content) => content.replace('a / b', '1000'))
await expect(item).toHaveTests({ divide: 'failed', }) await item.navigate()
const errors = await tester.errors.getInlineErrors()
expect(errors).toEqual(['1000 != 2']) })
test('editing an imported file only reruns affected tests', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/continuous', })
await tester.tree.expand('test/imports-divide.test.ts') await tester.tree.expand('test/imports-multiply.test.ts') await tester.tree.expand('test/no-import.test.ts')
const divideTest = tester.tree.getFileItem('imports-divide.test.ts') const multiplyTest = tester.tree.getFileItem('imports-multiply.test.ts') const noImportTest = tester.tree.getFileItem('no-import.test.ts')
await expect(divideTest).toHaveTests({ divide: 'waiting' }) await expect(multiplyTest).toHaveTests({ multiply: 'waiting' }) await expect(noImportTest).toHaveTests({ multiply: 'waiting', divide: 'waiting', })
await divideTest.toggleContinuousRun() await multiplyTest.toggleContinuousRun() await noImportTest.toggleContinuousRun()
// trigger initial run by touching each test file editFile('samples/continuous/test/imports-divide.test.ts', (content) => `${content}\n`) await expect(divideTest).toHaveTests({ divide: 'passed' })
editFile('samples/continuous/test/imports-multiply.test.ts', (content) => `${content}\n`) await expect(multiplyTest).toHaveTests({ multiply: 'passed' })
editFile('samples/continuous/test/no-import.test.ts', (content) => `${content}\n`) await expect(noImportTest).toHaveTests({ multiply: 'passed', divide: 'passed', })
// break calculator.ts — only importing tests should rerun and fail editFile('samples/continuous/src/calculator.ts', (content) => content.replace('a * b', '0').replace('a / b', '0'), )
await expect(divideTest).toHaveTests({ divide: 'failed' }) await expect(multiplyTest).toHaveTests({ multiply: 'failed' }) // no-import.test.ts should remain passed — it doesn't import calculator await expect(noImportTest).toHaveTests({ multiply: 'passed', divide: 'passed', }) })})
test('deno runtime', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/deno', })
await tester.tree.expand('test') await tester.tree.expand('test/deno.test.ts')
const denoTest = tester.tree.getFileItem('deno.test.ts') await expect(denoTest).toHaveTests({ 'deno-exists': 'waiting', })
await tester.runAllTests()
await expect(tester.tree.getResultsLocator()).toHaveText('1/1') await expect(denoTest).toHaveState('passed') await expect(denoTest).toHaveTests({ 'deno-exists': 'passed', })})
test('adding and deleting files updates the tree', async ({ launch }) => { const sample = 'samples/basic-v4'
const { tester } = await launch({ workspacePath: `./${sample}`, })
await tester.tree.expand('test/deep/deeper')
const deepTest = tester.tree.getFileItem('deep.test.ts') await expect(deepTest.locator).toBeVisible()
// also expand a parallel branch so we can verify it stays await tester.tree.expand('test/add.test.ts') const addTest = tester.tree.getFileItem('add.test.ts') await expect(addTest.locator).toBeVisible()
// add a second file in deeper addFile( `${sample}/test/deep/deeper/second.test.ts`, `import { expect, it } from 'vitest'\n\nit('second', () => {\n expect(2).toBe(2)\n})\n`, )
await tester.tree.expand('test/deep/deeper') const secondTest = tester.tree.getFileItem('second.test.ts') await expect(secondTest.locator).toBeVisible() await expect(deepTest.locator).toBeVisible()
// delete the first file — second file should remain, folder stays deleteFile(`${sample}/test/deep/deeper/deep.test.ts`)
await expect(deepTest.locator).not.toBeVisible() await expect(secondTest.locator).toBeVisible()
// delete the second file — folder should disappear from the tree deleteFile(`${sample}/test/deep/deeper/second.test.ts`)
await expect(secondTest.locator).not.toBeVisible()
// parallel tree branch is untouched await expect(addTest.locator).toBeVisible()})
test('renaming a folder back preserves test items', async ({ launch }) => { const { tester } = await launch({ workspacePath: './samples/basic-v4', })
await tester.tree.expand('test/deep/deeper')
const deepTest = tester.tree.getFileItem('deep.test.ts') await expect(deepTest.locator).toBeVisible()
// Rename deeper -> deeperer renameFile('samples/basic-v4/test/deep/deeper', 'samples/basic-v4/test/deep/deeperer')
await tester.tree.expand('test/deep/deeperer') const renamedTest = tester.tree.getFileItem('deep.test.ts') await expect(renamedTest.locator).toBeVisible()
// Rename back deeperer -> deeper renameFile('samples/basic-v4/test/deep/deeperer', 'samples/basic-v4/test/deep/deeper')
await tester.tree.expand('test/deep/deeper') const restoredTest = tester.tree.getFileItem('deep.test.ts') await expect(restoredTest.locator).toBeVisible()})
test.skipIf(process.env.TEST_LEGACY)('opens trace view', async ({ launch }) => { const { page, tester } = await launch({ workspacePath: './samples/browser-v5', })
// Run the browser tests to generate a trace report. await tester.tree.expand('basic.test.ts [chromium]') await tester.runAllTests() await expect(tester.tree.getResultsLocator()).toHaveText('2/2', { timeout: 15_000 })
// Open the recorded trace from the test's context menu. await page.locator('[aria-label*="records a trace (Passed)"]').click({ button: 'right' }) // VS Code enables menu mouse-up handlers 100ms after rendering. await page.getByRole('menuitem', { name: 'Open Trace View', exact: true }).click({ delay: 150 })
// Verify the webview displays the selected test's recorded button. const traceView = page .frameLocator('iframe.webview') .frameLocator('#active-frame') .getByTestId('trace-view') const traceViewFrame = traceView.frameLocator('iframe') await expect(traceView).toBeVisible() await expect(traceViewFrame.getByRole('button', { name: 'Submit FOO' })).toBeVisible()})