Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: split vitest into separate packages #2575

Merged
merged 8 commits into from Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/expect/README.md
@@ -0,0 +1,17 @@
# @vitest/expect

Jest's expect matchers as a Chai plugin.

## Usage

```js
import * as chai from 'chai'
import { JestAsymmetricMatchers, JestChaiExpect, JestExtend } from '@vitest/expect'

// allows using expect.extend instead of chai.use to extend plugins
chai.use(JestExtend)
// adds all jest matchers to expect
chai.use(JestChaiExpect)
// adds asymmetric matchers like stringContaining, objectContaining
chai.use(JestAsymmetricMatchers)
```
37 changes: 37 additions & 0 deletions packages/expect/package.json
@@ -0,0 +1,37 @@
{
"name": "@vitest/expect",
"type": "module",
"version": "0.26.2",
"description": "Jest's expect matchers as a Chai plugin",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/vitest-dev/vitest.git",
"directory": "packages/expect"
},
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./*": "./*"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "rimraf dist && rollup -c",
"dev": "rollup -c --watch",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@vitest/spy": "workspace:*",
"@vitest/utils": "workspace:*",
"chai": "^4.3.7",
"picocolors": "^1.0.0"
}
}
51 changes: 51 additions & 0 deletions packages/expect/rollup.config.js
@@ -0,0 +1,51 @@
import { builtinModules } from 'module'
import esbuild from 'rollup-plugin-esbuild'
import dts from 'rollup-plugin-dts'
import { defineConfig } from 'rollup'
import pkg from './package.json'

const external = [
...builtinModules,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
]

const plugins = [
esbuild({
target: 'node14',
}),
]

export default defineConfig([
{
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'esm',
entryFileNames: '[name].js',
chunkFileNames: 'chunk-[name].js',
},
external,
plugins,
onwarn,
},
{
input: 'src/index.ts',
output: {
dir: 'dist',
entryFileNames: '[name].d.ts',
format: 'esm',
},
external,
plugins: [
dts({ respectExternal: true }),
],
onwarn,
},
])

function onwarn(message) {
if (['EMPTY_BUNDLE', 'CIRCULAR_DEPENDENCY'].includes(message.code))
return
console.error(message)
}
@@ -1,3 +1,3 @@
export const GLOBAL_EXPECT = Symbol.for('expect-global')
export const MATCHERS_OBJECT = Symbol.for('matchers-object')
export const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object')
export const GLOBAL_EXPECT = Symbol.for('expect-global')
7 changes: 7 additions & 0 deletions packages/expect/src/index.ts
@@ -0,0 +1,7 @@
export * from './jest-asymmetric-matchers'
export * from './jest-utils'
export * from './constants'
export * from './types'
export { getState, setState } from './state'
export { JestChaiExpect } from './jest-expect'
export { JestExtend } from './jest-extend'
@@ -1,6 +1,6 @@
import type { ChaiPlugin, MatcherState } from '../../types/chai'
import type { ChaiPlugin, MatcherState } from './types'
import { GLOBAL_EXPECT } from './constants'
import { getState } from './jest-expect'
import { getState } from './state'
import * as matcherUtils from './jest-matcher-utils'

import { equals, isA } from './jest-utils'
Expand Down
@@ -1,44 +1,14 @@
import c from 'picocolors'
import { AssertionError } from 'chai'
import type { EnhancedSpy } from '../spy'
import { isMockFunction } from '../spy'
import { addSerializer } from '../snapshot/port/plugins'
import type { Constructable, Test } from '../../types'
import { assertTypes } from '../../utils'
import { unifiedDiff } from '../../utils/diff'
import type { ChaiPlugin, MatcherState } from '../../types/chai'
import { assertTypes, unifiedDiff } from '@vitest/utils'
import type { Constructable } from '@vitest/utils'
import type { EnhancedSpy } from '@vitest/spy'
import { isMockFunction } from '@vitest/spy'
import type { ChaiPlugin } from './types'
import { arrayBufferEquality, generateToBeMessage, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils'
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
import { stringify } from './jest-matcher-utils'
import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants'

if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
const globalState = new WeakMap<Vi.ExpectStatic, MatcherState>()
const matchers = Object.create(null)
Object.defineProperty(globalThis, MATCHERS_OBJECT, {
get: () => globalState,
})
Object.defineProperty(globalThis, JEST_MATCHERS_OBJECT, {
configurable: true,
get: () => ({
state: globalState.get((globalThis as any)[GLOBAL_EXPECT]),
matchers,
}),
})
}

export const getState = <State extends MatcherState = MatcherState>(expect: Vi.ExpectStatic): State =>
(globalThis as any)[MATCHERS_OBJECT].get(expect)

export const setState = <State extends MatcherState = MatcherState>(
state: Partial<State>,
expect: Vi.ExpectStatic,
): void => {
const map = (globalThis as any)[MATCHERS_OBJECT]
const current = map.get(expect) || {}
Object.assign(current, state)
map.set(expect, current)
}
import { JEST_MATCHERS_OBJECT } from './constants'

// Jest Expect Compact
export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
Expand Down Expand Up @@ -681,7 +651,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
return result.call(this, ...args)
},
(err: any) => {
throw new Error(`promise rejected "${toString(err)}" instead of resolving`)
throw new Error(`promise rejected "${String(err)}" instead of resolving`)
},
)
}
Expand Down Expand Up @@ -710,7 +680,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
return async (...args: any[]) => {
return wrapper.then(
(value: any) => {
throw new Error(`promise resolved "${toString(value)}" instead of rejecting`)
throw new Error(`promise resolved "${String(value)}" instead of rejecting`)
},
(err: any) => {
utils.flag(this, 'object', err)
Expand All @@ -723,19 +693,4 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {

return proxy
})

utils.addMethod(
chai.expect,
'addSnapshotSerializer',
addSerializer,
)
}

function toString(value: any) {
try {
return `${value}`
}
catch (_error) {
return 'unknown'
}
}
Expand Up @@ -4,11 +4,10 @@ import type {
MatcherState,
MatchersObject,
SyncExpectationResult,
} from '../../types/chai'
import { getSnapshotClient } from '../snapshot/chai'
} from './types'
import { JEST_MATCHERS_OBJECT } from './constants'
import { AsymmetricMatcher } from './jest-asymmetric-matchers'
import { getState } from './jest-expect'
import { getState } from './state'

import * as matcherUtils from './jest-matcher-utils'

Expand Down Expand Up @@ -39,7 +38,6 @@ const getMatcherState = (assertion: Chai.AssertionStatic & Chai.Assertion, expec
equals,
// needed for built-in jest-snapshots, but we don't use it
suppressedErrors: [],
snapshotState: getSnapshotClient().snapshotState!,
}

return {
Expand Down
@@ -1,36 +1,15 @@
// we are using only the ones needed by @testing-library/jest-dom
// if you need more, just ask

import c from 'picocolors'
import type { PrettyFormatOptions } from 'pretty-format'
import { format as prettyFormat, plugins as prettyFormatPlugins } from 'pretty-format'
import { unifiedDiff } from '../../utils/diff'
import type { DiffOptions, MatcherHintOptions } from '../../types/matcher-utils'
import { stringify, unifiedDiff } from '@vitest/utils'
import type { DiffOptions, MatcherHintOptions } from './types'

export { stringify }

export const EXPECTED_COLOR = c.green
export const RECEIVED_COLOR = c.red
export const INVERTED_COLOR = c.inverse
export const BOLD_WEIGHT = c.bold
export const DIM_COLOR = c.dim

const {
AsymmetricMatcher,
DOMCollection,
DOMElement,
Immutable,
ReactElement,
ReactTestComponent,
} = prettyFormatPlugins

const PLUGINS = [
ReactTestComponent,
ReactElement,
DOMElement,
DOMCollection,
Immutable,
AsymmetricMatcher,
]

export function matcherHint(
matcherName: string,
received = 'received',
Expand All @@ -39,12 +18,12 @@ export function matcherHint(
) {
const {
comment = '',
expectedColor = EXPECTED_COLOR,
isDirectExpectCall = false, // seems redundant with received === ''
isNot = false,
promise = '',
receivedColor = RECEIVED_COLOR,
secondArgument = '',
expectedColor = EXPECTED_COLOR,
receivedColor = RECEIVED_COLOR,
secondArgumentColor = EXPECTED_COLOR,
} = options
let hint = ''
Expand Down Expand Up @@ -102,35 +81,6 @@ const SPACE_SYMBOL = '\u{00B7}' // middle dot
const replaceTrailingSpaces = (text: string): string =>
text.replace(/\s+$/gm, spaces => SPACE_SYMBOL.repeat(spaces.length))

export function stringify(object: unknown, maxDepth = 10, { maxLength, ...options }: PrettyFormatOptions & { maxLength?: number } = {}): string {
const MAX_LENGTH = maxLength ?? 10000
let result

try {
result = prettyFormat(object, {
maxDepth,
escapeString: false,
// min: true,
plugins: PLUGINS,
...options,
})
}
catch {
result = prettyFormat(object, {
callToJSON: false,
maxDepth,
escapeString: false,
// min: true,
plugins: PLUGINS,
...options,
})
}

return result.length >= MAX_LENGTH && maxDepth > 1
? stringify(object, Math.floor(maxDepth / 2))
: result
}

export const printReceived = (object: unknown): string =>
RECEIVED_COLOR(replaceTrailingSpaces(stringify(object)))
export const printExpected = (value: unknown): string =>
Expand Down
Expand Up @@ -21,8 +21,9 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
import { isObject } from '../../utils'
import type { Tester } from '../../types/chai'

import { isObject } from '@vitest/utils'
import type { Tester } from './types'

// Extracted out of jasmine 2.5.2
export function equals(
Expand Down
30 changes: 30 additions & 0 deletions packages/expect/src/state.ts
@@ -0,0 +1,30 @@
import type { MatcherState } from './types'
import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants'

if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
const globalState = new WeakMap<Vi.ExpectStatic, MatcherState>()
const matchers = Object.create(null)
Object.defineProperty(globalThis, MATCHERS_OBJECT, {
get: () => globalState,
})
Object.defineProperty(globalThis, JEST_MATCHERS_OBJECT, {
configurable: true,
get: () => ({
state: globalState.get((globalThis as any)[GLOBAL_EXPECT]),
matchers,
}),
})
}

export const getState = <State extends MatcherState = MatcherState>(expect: Vi.ExpectStatic): State =>
(globalThis as any)[MATCHERS_OBJECT].get(expect)

export const setState = <State extends MatcherState = MatcherState>(
state: Partial<State>,
expect: Vi.ExpectStatic,
): void => {
const map = (globalThis as any)[MATCHERS_OBJECT]
const current = map.get(expect) || {}
Object.assign(current, state)
map.set(expect, current)
}