test: jest warning assert utils

This commit is contained in:
Evan You 2019-08-26 16:08:23 -04:00
parent 8511237ed9
commit 3efe0ba3cd
5 changed files with 130 additions and 39 deletions

View File

@ -1,6 +1,9 @@
import { reactive, isReactive, toRaw, markNonReactive } from '../src/reactive' import { reactive, isReactive, toRaw, markNonReactive } from '../src/reactive'
import { mockWarn } from '@vue/runtime-test'
describe('reactivity/reactive', () => { describe('reactivity/reactive', () => {
mockWarn()
test('Object', () => { test('Object', () => {
const original = { foo: 1 } const original = { foo: 1 }
const observed = reactive(original) const observed = reactive(original)
@ -124,17 +127,11 @@ describe('reactivity/reactive', () => {
}) })
test('non-observable values', () => { test('non-observable values', () => {
const warn = jest.spyOn(console, 'warn')
let lastMsg: string
warn.mockImplementation(msg => {
lastMsg = msg
})
const getMsg = (value: any) =>
`value cannot be made reactive: ${String(value)}`
const assertValue = (value: any) => { const assertValue = (value: any) => {
reactive(value) reactive(value)
expect(lastMsg).toMatch(getMsg(value)) expect(
`value cannot be made reactive: ${String(value)}`
).toHaveBeenWarnedLast()
} }
// number // number
@ -151,8 +148,6 @@ describe('reactivity/reactive', () => {
const s = Symbol() const s = Symbol()
assertValue(s) assertValue(s)
warn.mockRestore()
// built-ins should work and return same value // built-ins should work and return same value
const p = Promise.resolve() const p = Promise.resolve()
expect(reactive(p)).toBe(p) expect(reactive(p)).toBe(p)

View File

@ -11,18 +11,10 @@ import {
effect, effect,
ref ref
} from '../src' } from '../src'
import { mockWarn } from '@vue/runtime-test'
describe('reactivity/readonly', () => { describe('reactivity/readonly', () => {
let warn: any mockWarn()
beforeEach(() => {
warn = jest.spyOn(console, 'warn')
warn.mockImplementation(() => {})
})
afterEach(() => {
warn.mockRestore()
})
describe('Object', () => { describe('Object', () => {
it('should make nested values readonly', () => { it('should make nested values readonly', () => {
@ -49,16 +41,24 @@ describe('reactivity/readonly', () => {
const observed: any = readonly({ foo: 1, bar: { baz: 2 } }) const observed: any = readonly({ foo: 1, bar: { baz: 2 } })
observed.foo = 2 observed.foo = 2
expect(observed.foo).toBe(1) expect(observed.foo).toBe(1)
expect(warn).toHaveBeenCalledTimes(1) expect(
`Set operation on key "foo" failed: target is readonly.`
).toHaveBeenWarnedLast()
observed.bar.baz = 3 observed.bar.baz = 3
expect(observed.bar.baz).toBe(2) expect(observed.bar.baz).toBe(2)
expect(warn).toHaveBeenCalledTimes(2) expect(
`Set operation on key "baz" failed: target is readonly.`
).toHaveBeenWarnedLast()
delete observed.foo delete observed.foo
expect(observed.foo).toBe(1) expect(observed.foo).toBe(1)
expect(warn).toHaveBeenCalledTimes(3) expect(
`Delete operation on key "foo" failed: target is readonly.`
).toHaveBeenWarnedLast()
delete observed.bar.baz delete observed.bar.baz
expect(observed.bar.baz).toBe(2) expect(observed.bar.baz).toBe(2)
expect(warn).toHaveBeenCalledTimes(4) expect(
`Delete operation on key "baz" failed: target is readonly.`
).toHaveBeenWarnedLast()
}) })
it('should allow mutation when unlocked', () => { it('should allow mutation when unlocked', () => {
@ -73,7 +73,7 @@ describe('reactivity/readonly', () => {
expect(observed.foo).toBeUndefined() expect(observed.foo).toBeUndefined()
expect(observed.bar.qux).toBe(3) expect(observed.bar.qux).toBe(3)
expect('baz' in observed.bar).toBe(false) expect('baz' in observed.bar).toBe(false)
expect(warn).not.toHaveBeenCalled() expect(`target is readonly`).not.toHaveBeenWarned()
}) })
it('should not trigger effects when locked', () => { it('should not trigger effects when locked', () => {
@ -128,22 +128,28 @@ describe('reactivity/readonly', () => {
const observed: any = readonly([{ foo: 1 }]) const observed: any = readonly([{ foo: 1 }])
observed[0] = 1 observed[0] = 1
expect(observed[0]).not.toBe(1) expect(observed[0]).not.toBe(1)
expect(warn).toHaveBeenCalledTimes(1) expect(
`Set operation on key "0" failed: target is readonly.`
).toHaveBeenWarned()
observed[0].foo = 2 observed[0].foo = 2
expect(observed[0].foo).toBe(1) expect(observed[0].foo).toBe(1)
expect(warn).toHaveBeenCalledTimes(2) expect(
`Set operation on key "foo" failed: target is readonly.`
).toHaveBeenWarned()
// should block length mutation // should block length mutation
observed.length = 0 observed.length = 0
expect(observed.length).toBe(1) expect(observed.length).toBe(1)
expect(observed[0].foo).toBe(1) expect(observed[0].foo).toBe(1)
expect(warn).toHaveBeenCalledTimes(3) expect(
`Set operation on key "length" failed: target is readonly.`
).toHaveBeenWarned()
// mutation methods invoke set/length internally and thus are blocked as well // mutation methods invoke set/length internally and thus are blocked as well
observed.push(2) observed.push(2)
expect(observed.length).toBe(1) expect(observed.length).toBe(1)
// push triggers two warnings on [1] and .length // push triggers two warnings on [1] and .length
expect(warn).toHaveBeenCalledTimes(5) expect(`target is readonly.`).toHaveBeenWarnedTimes(5)
}) })
it('should allow mutation when unlocked', () => { it('should allow mutation when unlocked', () => {
@ -159,7 +165,7 @@ describe('reactivity/readonly', () => {
expect(observed[2]).toBe(3) expect(observed[2]).toBe(3)
expect(observed[0].foo).toBe(2) expect(observed[0].foo).toBe(2)
expect(observed[0].bar.baz).toBe(3) expect(observed[0].bar.baz).toBe(3)
expect(warn).not.toHaveBeenCalled() expect(`target is readonly`).not.toHaveBeenWarned()
}) })
it('should not trigger effects when locked', () => { it('should not trigger effects when locked', () => {
@ -232,7 +238,9 @@ describe('reactivity/readonly', () => {
map.set(key, 1) map.set(key, 1)
expect(dummy).toBeUndefined() expect(dummy).toBeUndefined()
expect(map.has(key)).toBe(false) expect(map.has(key)).toBe(false)
expect(warn).toHaveBeenCalledTimes(1) expect(
`Set operation on key "${key}" failed: target is readonly.`
).toHaveBeenWarned()
}) })
test('should allow mutation & trigger effect when unlocked', () => { test('should allow mutation & trigger effect when unlocked', () => {
@ -249,7 +257,7 @@ describe('reactivity/readonly', () => {
lock() lock()
expect(dummy).toBe(isWeak ? 1 : 2) expect(dummy).toBe(isWeak ? 1 : 2)
expect(map.get(key)).toBe(1) expect(map.get(key)).toBe(1)
expect(warn).not.toHaveBeenCalled() expect(`target is readonly`).not.toHaveBeenWarned()
}) })
if (Collection === Map) { if (Collection === Map) {
@ -301,7 +309,9 @@ describe('reactivity/readonly', () => {
set.add(key) set.add(key)
expect(dummy).toBe(false) expect(dummy).toBe(false)
expect(set.has(key)).toBe(false) expect(set.has(key)).toBe(false)
expect(warn).toHaveBeenCalledTimes(1) expect(
`Add operation on key "${key}" failed: target is readonly.`
).toHaveBeenWarned()
}) })
test('should allow mutation & trigger effect when unlocked', () => { test('should allow mutation & trigger effect when unlocked', () => {
@ -317,7 +327,7 @@ describe('reactivity/readonly', () => {
lock() lock()
expect(dummy).toBe(true) expect(dummy).toBe(true)
expect(set.has(key)).toBe(true) expect(set.has(key)).toBe(true)
expect(warn).not.toHaveBeenCalled() expect(`target is readonly`).not.toHaveBeenWarned()
}) })
if (Collection === Set) { if (Collection === Set) {
@ -396,6 +406,8 @@ describe('reactivity/readonly', () => {
const n: any = readonly(ref(1)) const n: any = readonly(ref(1))
n.value = 2 n.value = 2
expect(n.value).toBe(1) expect(n.value).toBe(1)
expect(warn).toHaveBeenCalledTimes(1) expect(
`Set operation on key "value" failed: target is readonly.`
).toHaveBeenWarned()
}) })
}) })

View File

@ -2,7 +2,7 @@ import { toRaw, reactive, readonly } from './reactive'
import { track, trigger } from './effect' import { track, trigger } from './effect'
import { OperationTypes } from './operations' import { OperationTypes } from './operations'
import { LOCKED } from './lock' import { LOCKED } from './lock'
import { isObject } from '@vue/shared' import { isObject, capitalize } from '@vue/shared'
const toReactive = (value: any) => (isObject(value) ? reactive(value) : value) const toReactive = (value: any) => (isObject(value) ? reactive(value) : value)
const toReadonly = (value: any) => (isObject(value) ? readonly(value) : value) const toReadonly = (value: any) => (isObject(value) ? readonly(value) : value)
@ -166,9 +166,9 @@ function createReadonlyMethod(
return function(...args: any[]) { return function(...args: any[]) {
if (LOCKED) { if (LOCKED) {
if (__DEV__) { if (__DEV__) {
const key = args[0] ? `on key "${args[0]}"` : `` const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn( console.warn(
`${type} operation ${key}failed: target is readonly.`, `${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this) toRaw(this)
) )
} }

View File

@ -10,4 +10,5 @@ export const render = createRenderer({
export { serialize } from './serialize' export { serialize } from './serialize'
export { triggerEvent } from './triggerEvent' export { triggerEvent } from './triggerEvent'
export * from './nodeOps' export * from './nodeOps'
export * from './jestUtils'
export * from '@vue/runtime-core' export * from '@vue/runtime-core'

View File

@ -0,0 +1,83 @@
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenWarned(): R
toHaveBeenWarnedLast(): R
toHaveBeenWarnedTimes(n: number): R
}
}
}
export function mockWarn() {
expect.extend({
toHaveBeenWarned(received: string) {
const passed = warn.mock.calls.some(
args => args[0].indexOf(received) > -1
)
if (passed) {
return {
pass: true,
message: () => `expected "${received}" not to have been warned.`
}
} else {
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
return {
pass: false,
message: () =>
`expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`
}
}
},
toHaveBeenWarnedLast(received: string) {
const passed =
warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1
if (passed) {
return {
pass: true,
message: () => `expected "${received}" not to have been warned last.`
}
} else {
const msgs = warn.mock.calls.map(args => args[0]).join('\n - ')
return {
pass: false,
message: () =>
`expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`
}
}
},
toHaveBeenWarnedTimes(received: string, n: number) {
let found = 0
warn.mock.calls.forEach(args => {
if (args[0].indexOf(received) > -1) {
found++
}
})
if (found > 0) {
return {
pass: true,
message: () =>
`expected "${received}" not to have been warned ${n} times.`
}
} else {
return {
pass: false,
message: () =>
`expected "${received}" to have been warned ${n} times but got ${found}.`
}
}
}
})
let warn: jest.SpyInstance
beforeEach(() => {
warn = jest.spyOn(console, 'warn')
warn.mockImplementation(() => {})
})
afterEach(() => {
warn.mockRestore()
})
}