···4343export class AroundHookMultipleCallsError extends Error {
4444 public name = 'AroundHookMultipleCallsError'
4545}
4646+4747+// `test.fails` doesn't flip the test result when this error is thrown
4848+export class TestSyntaxError extends Error {
4949+ public name = 'TestSyntaxError'
5050+5151+ constructor(message: string) {
5252+ super(message)
5353+ // use custom property so this survives when the error
5454+ // is serialized on `packages/expect` side (e.g. for `expect.soft`)
5555+ // and `packages/runner` can still detect it during `test.fails` handling
5656+ Object.defineProperty(this, '__vitest_test_syntax_error__', {
5757+ value: true,
5858+ enumerable: false,
5959+ })
6060+ }
6161+}
+1
packages/runner/src/index.ts
···11export { recordArtifact } from './artifact'
22+export { TestSyntaxError } from './errors'
23export {
34 afterAll,
45 afterEach,
+2-2
packages/runner/src/run.ts
···761761 }
762762 }
763763764764- // if test is marked to be failed, flip the result
764764+ // if test is marked to be failed, flip the result unless `TestSyntaxError` is present
765765 if (test.fails) {
766766 if (test.result.state === 'pass') {
767767 const error = processError(new Error('Expect test to fail'))
768768 test.result.state = 'fail'
769769 test.result.errors = [error]
770770 }
771771- else {
771771+ else if (!test.result.errors?.some(e => e.__vitest_test_syntax_error__)) {
772772 test.result.state = 'pass'
773773 test.result.errors = undefined
774774 }
···22import type { Test } from '@vitest/runner'
33import type { DomainSnapshotAdapter } from '@vitest/snapshot'
44import { chai, createAssertionMessage, equals, iterableEquality, recordAsyncExpect, subsetEquality, wrapAssertion } from '@vitest/expect'
55+import { TestSyntaxError } from '@vitest/runner'
56import { getNames } from '@vitest/runner/utils'
67import {
78 addSerializer,
···6263}
63646465function getTest(obj: Chai.Assertion) {
6565- const test = chai.util.flag(obj, 'vitest-test')
6666+ const test = chai.util.flag(obj, 'vitest-test') as Test | undefined
6667 if (!test) {
6768 throw new Error(`'${getAssertionName(obj)}' cannot be used without test context`)
6869 }
6969- return test as Test
7070+ if (test.fails) {
7171+ throw new TestSyntaxError(`'${getAssertionName(obj)}' cannot be used with 'test.fails'`)
7272+ }
7373+ return test
7074}
71757276function validateAssertion(assertion: Chai.Assertion): void {