Skip to content

Commit 25b3e9a

Browse files
committed
chore: fix other error messages
1 parent 81cb81e commit 25b3e9a

File tree

6 files changed

+115
-45
lines changed

6 files changed

+115
-45
lines changed

packages/expect/src/jest-expect.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Constructable } from '@vitest/utils'
44
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
55
import type { Assertion, ChaiPlugin } from './types'
66
import { isMockFunction } from '@vitest/spy'
7-
import { assertTypes } from '@vitest/utils/helpers'
7+
import { assertTypes, ordinal } from '@vitest/utils/helpers'
88
import c from 'tinyrainbow'
99
import { JEST_MATCHERS_OBJECT } from './constants'
1010
import {
@@ -642,12 +642,12 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
642642
const isCalled = times <= callCount
643643
this.assert(
644644
nthCall && equalsArgumentArray(nthCall, args),
645-
`expected ${ordinalOf(
645+
`expected ${ordinal(
646646
times,
647647
)} "${spyName}" call to have been called with #{exp}${
648648
isCalled ? `` : `, but called only ${callCount} times`
649649
}`,
650-
`expected ${ordinalOf(
650+
`expected ${ordinal(
651651
times,
652652
)} "${spyName}" call to not have been called with #{exp}`,
653653
args,
@@ -1046,7 +1046,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
10461046
const results
10471047
= action === 'return' ? spy.mock.results : spy.mock.settledResults
10481048
const result = results[nthCall - 1]
1049-
const ordinalCall = `${ordinalOf(nthCall)} call`
1049+
const ordinalCall = `${ordinal(nthCall)} call`
10501050

10511051
this.assert(
10521052
condition(spy, nthCall, value),
@@ -1213,32 +1213,13 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
12131213
)
12141214
}
12151215

1216-
function ordinalOf(i: number) {
1217-
const j = i % 10
1218-
const k = i % 100
1219-
1220-
if (j === 1 && k !== 11) {
1221-
return `${i}st`
1222-
}
1223-
1224-
if (j === 2 && k !== 12) {
1225-
return `${i}nd`
1226-
}
1227-
1228-
if (j === 3 && k !== 13) {
1229-
return `${i}rd`
1230-
}
1231-
1232-
return `${i}th`
1233-
}
1234-
12351216
function formatCalls(spy: MockInstance, msg: string, showActualCall?: any) {
12361217
if (spy.mock.calls.length) {
12371218
msg += c.gray(
12381219
`\n\nReceived:\n\n${spy.mock.calls
12391220
.map((callArg, i) => {
12401221
let methodCall = c.bold(
1241-
` ${ordinalOf(i + 1)} ${spy.getMockName()} call:\n\n`,
1222+
` ${ordinal(i + 1)} ${spy.getMockName()} call:\n\n`,
12421223
)
12431224
if (showActualCall) {
12441225
methodCall += diff(showActualCall, callArg, {
@@ -1275,7 +1256,7 @@ function formatReturns(
12751256
`\n\nReceived:\n\n${results
12761257
.map((callReturn, i) => {
12771258
let methodCall = c.bold(
1278-
` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`,
1259+
` ${ordinal(i + 1)} ${spy.getMockName()} call return:\n\n`,
12791260
)
12801261
if (showActualReturn) {
12811262
methodCall += diff(showActualReturn, callReturn.value, {

packages/runner/src/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export class FixtureAccessError extends Error {
2828
public name = 'FixtureAccessError'
2929
}
3030

31+
export class FixtureParseError extends Error {
32+
public name = 'FixtureParseError'
33+
}
34+
3135
export class AroundHookSetupError extends Error {
3236
public name = 'AroundHookSetupError'
3337
}

packages/runner/src/fixture.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FixtureFn, Suite, VitestRunner } from './types'
22
import type { File, FixtureOptions, TestContext } from './types/tasks'
3-
import { createDefer, filterOutComments, isObject } from '@vitest/utils/helpers'
4-
import { FixtureAccessError, FixtureDependencyError } from './errors'
3+
import { createDefer, filterOutComments, isObject, ordinal } from '@vitest/utils/helpers'
4+
import { FixtureAccessError, FixtureDependencyError, FixtureParseError } from './errors'
55
import { getTestFixtures } from './map'
66
import { getCurrentSuite } from './suite'
77

@@ -269,12 +269,14 @@ export async function callFixtureCleanupFrom(context: object, fromIndex: number)
269269
cleanupFnArray.length = fromIndex
270270
}
271271

272+
type SuiteHook = 'beforeAll' | 'afterAll' | 'aroundAll'
273+
272274
export interface WithFixturesOptions {
273275
/**
274276
* Whether this is a suite-level hook (beforeAll/afterAll/aroundAll).
275277
* Suite hooks can only access file/worker scoped fixtures and static values.
276278
*/
277-
suiteHook?: 'beforeAll' | 'afterAll' | 'aroundAll'
279+
suiteHook?: SuiteHook
278280
/**
279281
* The test context to use. If not provided, the hookContext passed to the
280282
* returned function will be used.
@@ -548,8 +550,8 @@ function resolveDeps(
548550
return pendingFixtures
549551
}
550552

551-
function validateSuiteHook(fn: Function, hook: string, suiteError: Error | undefined) {
552-
const usedProps = getUsedProps(fn)
553+
function validateSuiteHook(fn: Function, hook: SuiteHook, suiteError: Error | undefined) {
554+
const usedProps = getUsedProps(fn, { sourceError: suiteError, suiteHook: hook })
553555
if (usedProps.size) {
554556
const error = new FixtureAccessError(
555557
`The ${hook} hook uses fixtures "${[...usedProps].join('", "')}", but has no access to context. `
@@ -565,6 +567,7 @@ function validateSuiteHook(fn: Function, hook: string, suiteError: Error | undef
565567
}
566568

567569
const kPropsSymbol = Symbol('$vitest:fixture-props')
570+
const kPropNamesSymbol = Symbol('$vitest:fixture-prop-names')
568571

569572
interface FixturePropsOptions {
570573
index?: number
@@ -578,7 +581,21 @@ export function configureProps(fn: Function, options: FixturePropsOptions): void
578581
})
579582
}
580583

581-
function getUsedProps(fn: Function): Set<string> {
584+
function memoProps(fn: Function, props: Set<string>): Set<string> {
585+
(fn as any)[kPropNamesSymbol] = props
586+
return props
587+
}
588+
589+
interface PropsParserOptions {
590+
sourceError?: Error | undefined
591+
suiteHook?: SuiteHook
592+
}
593+
594+
function getUsedProps(fn: Function, { sourceError, suiteHook }: PropsParserOptions = {}): Set<string> {
595+
if (kPropNamesSymbol in fn) {
596+
return fn[kPropNamesSymbol] as Set<string>
597+
}
598+
582599
const {
583600
index: fixturesIndex = 0,
584601
original: implementation = fn,
@@ -595,24 +612,31 @@ function getUsedProps(fn: Function): Set<string> {
595612
}
596613
const match = fnString.match(/[^(]*\(([^)]*)/)
597614
if (!match) {
598-
return new Set()
615+
return memoProps(fn, new Set())
599616
}
600617

601618
const args = splitByComma(match[1])
602619
if (!args.length) {
603-
return new Set()
620+
return memoProps(fn, new Set())
604621
}
605622

606623
const fixturesArgument = args[fixturesIndex]
607624

608625
if (!fixturesArgument) {
609-
return new Set()
626+
return memoProps(fn, new Set())
610627
}
611628

612629
if (!(fixturesArgument[0] === '{' && fixturesArgument.endsWith('}'))) {
613-
throw new Error(
614-
`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${fixturesArgument}".`,
630+
const ordinalArgument = ordinal(fixturesIndex + 1)
631+
const error = new FixtureParseError(
632+
`The ${ordinalArgument} argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). `
633+
+ `Instead, received "${fixturesArgument}".`
634+
+ `${(suiteHook ? ` If you used internal "suite" task as the ${ordinalArgument} argument previously, access it in the ${ordinal(fixturesIndex + 2)} argument instead.` : '')}`,
615635
)
636+
if (sourceError) {
637+
error.stack = sourceError.stack?.replace(sourceError.message, error.message)
638+
}
639+
throw error
616640
}
617641

618642
const _first = fixturesArgument.slice(1, -1).replace(/\s/g, '')
@@ -622,12 +646,16 @@ function getUsedProps(fn: Function): Set<string> {
622646

623647
const last = props.at(-1)
624648
if (last && last.startsWith('...')) {
625-
throw new Error(
649+
const error = new FixtureParseError(
626650
`Rest parameters are not supported in fixtures, received "${last}".`,
627651
)
652+
if (sourceError) {
653+
error.stack = sourceError.stack?.replace(sourceError.message, error.message)
654+
}
655+
throw error
628656
}
629657

630-
return new Set(props)
658+
return memoProps(fn, new Set(props))
631659
}
632660

633661
function splitByComma(s: string) {

packages/utils/src/helpers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,25 @@ function isMergeableObject(item: any): item is object {
360360
return isPlainObject(item) && !Array.isArray(item)
361361
}
362362

363+
export function ordinal(i: number): string {
364+
const j = i % 10
365+
const k = i % 100
366+
367+
if (j === 1 && k !== 11) {
368+
return `${i}st`
369+
}
370+
371+
if (j === 2 && k !== 12) {
372+
return `${i}nd`
373+
}
374+
375+
if (j === 3 && k !== 13) {
376+
return `${i}rd`
377+
}
378+
379+
return `${i}th`
380+
}
381+
363382
/**
364383
* Deep merge :P
365384
*

test/cli/test/__snapshots__/fails.test.ts.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ Error: Error thrown in afterEach fixture
8585
Error: Error thrown in beforeEach fixture"
8686
`;
8787
88-
exports[`should fail test-extend/fixture-rest-params.test.ts 1`] = `"Error: The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "...rest"."`;
88+
exports[`should fail test-extend/fixture-rest-params.test.ts 1`] = `"FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "...rest"."`;
8989
90-
exports[`should fail test-extend/fixture-rest-props.test.ts 1`] = `"Error: Rest parameters are not supported in fixtures, received "...rest"."`;
90+
exports[`should fail test-extend/fixture-rest-props.test.ts 1`] = `"FixtureParseError: Rest parameters are not supported in fixtures, received "...rest"."`;
9191
92-
exports[`should fail test-extend/fixture-without-destructuring.test.ts 1`] = `"Error: The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "context"."`;
92+
exports[`should fail test-extend/fixture-without-destructuring.test.ts 1`] = `"FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "context"."`;
9393
94-
exports[`should fail test-extend/test-rest-params.test.ts 1`] = `"Error: The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "...rest"."`;
94+
exports[`should fail test-extend/test-rest-params.test.ts 1`] = `"FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "...rest"."`;
9595
96-
exports[`should fail test-extend/test-rest-props.test.ts 1`] = `"Error: Rest parameters are not supported in fixtures, received "...rest"."`;
96+
exports[`should fail test-extend/test-rest-props.test.ts 1`] = `"FixtureParseError: Rest parameters are not supported in fixtures, received "...rest"."`;
9797
98-
exports[`should fail test-extend/test-without-destructuring.test.ts 1`] = `"Error: The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "context"."`;
98+
exports[`should fail test-extend/test-without-destructuring.test.ts 1`] = `"FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "context"."`;
9999
100100
exports[`should fail test-timeout.test.ts 1`] = `
101101
"Error: Test timed out in 20ms.

test/cli/test/scoped-fixtures.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ test('beforeAll/afterAll hooks throw error when accessing test-scoped fixtures',
660660
`)
661661
})
662662

663-
test.only('global beforeAll/afterAll hooks throw error when accessing any fixture', async () => {
663+
test('global beforeAll/afterAll hooks throw error when accessing any fixture', async () => {
664664
const { stderr, fixtures } = await runFixtureTests(({ log }) => {
665665
return it
666666
.extend('fileValue', { scope: 'file' }, () => {
@@ -701,6 +701,44 @@ test.only('global beforeAll/afterAll hooks throw error when accessing any fixtur
701701
`)
702702
})
703703

704+
test('global beforeAll/afterAll hooks throw error when accessing any fixture', async () => {
705+
const { stderr, fixtures } = await runFixtureTests(({ log }) => {
706+
return it
707+
.extend('fileValue', { scope: 'file' }, () => {
708+
log('fileValue setup')
709+
return 'file-scoped'
710+
})
711+
}, {
712+
'basic.test.ts': ({ extendedTest, beforeAll }) => {
713+
beforeAll<{ fileValue: string }>((suite) => {
714+
console.log('>> fixture | beforeAll | file:', suite.fileValue)
715+
})
716+
extendedTest('test1', ({}) => {})
717+
},
718+
})
719+
720+
expect(fixtures).toMatchInlineSnapshot(`""`)
721+
expect(stderr).toMatchInlineSnapshot(`
722+
"
723+
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
724+
725+
FAIL basic.test.ts [ basic.test.ts ]
726+
FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "suite". If you used internal "suite" task as the 1st argument previously, access it in the 2nd argument instead.
727+
❯ basic.test.ts:4:3
728+
2| import { extendedTest, expect, expectTypeOf, describe, beforeAll, afte…
729+
3| const results = await (({ extendedTest, beforeAll }) => {
730+
4| beforeAll((suite) => {
731+
| ^
732+
5| console.log(">> fixture | beforeAll | file:", suite.fileValue);
733+
6| });
734+
❯ basic.test.ts:9:1
735+
736+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
737+
738+
"
739+
`)
740+
})
741+
704742
test('aroundAll hooks receive file/worker fixtures, not test fixtures', async () => {
705743
const { stderr, fixtures, tests } = await runFixtureTests(({ log }) => {
706744
return it

0 commit comments

Comments
 (0)