diff --git a/package.json b/package.json index 74c5e146..7ae913dd 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "scripts": { "start": "concurrently \"tsc --emitDeclarationOnly -w\" \"cross-env TARGET=es rollup -c -w\"", "build": "rimraf dist typings && tsc --emitDeclarationOnly && rollup -c", - "lint": "prettier --write --parser typescript \"{src,test,test-dts}/*.ts?(x)\" && prettier --write \"{src,test}/*.js\"", + "lint": "prettier --write --parser typescript \"{src,test,test-dts}/**/*.ts?(x)\" && prettier --write \"{src,test}/**/*.js\"", "test": "yarn test-dts && yarn test-unit", "test-unit": "cross-env NODE_ENV=test jest", "test-dts": "tsc -p ./test-dts/tsconfig.json && yarn build && tsc -p ./test-dts/tsconfig.build.json", diff --git a/src/apis/computed.ts b/src/apis/computed.ts index 341a0c9e..bdc2c856 100644 --- a/src/apis/computed.ts +++ b/src/apis/computed.ts @@ -1,34 +1,34 @@ -import { getCurrentVue, getCurrentVM } from '../runtimeContext'; -import { createRef, Ref } from '../reactivity'; -import { defineComponentInstance } from '../helper'; -import { warn } from '../utils'; +import { getCurrentVue, getCurrentVM } from '../runtimeContext' +import { createRef, Ref } from '../reactivity' +import { defineComponentInstance } from '../helper' +import { warn } from '../utils' interface Option { - get: () => T; - set: (value: T) => void; + get: () => T + set: (value: T) => void } export interface ComputedRef extends WritableComputedRef { - readonly value: T; + readonly value: T } export interface WritableComputedRef extends Ref {} // read-only -export function computed(getter: Option['get']): ComputedRef; +export function computed(getter: Option['get']): ComputedRef // writable -export function computed(options: Option): WritableComputedRef; +export function computed(options: Option): WritableComputedRef // implement export function computed( options: Option['get'] | Option ): ComputedRef | WritableComputedRef { - const vm = getCurrentVM(); - let get: Option['get'], set: Option['set'] | undefined; + const vm = getCurrentVM() + let get: Option['get'], set: Option['set'] | undefined if (typeof options === 'function') { - get = options; + get = options } else { - get = options.get; - set = options.set; + get = options.get + set = options.set } const computedHost = defineComponentInstance(getCurrentVue(), { @@ -38,19 +38,19 @@ export function computed( set, }, }, - }); + }) - vm && vm.$on('hook:destroyed', () => computedHost.$destroy()); + vm && vm.$on('hook:destroyed', () => computedHost.$destroy()) return createRef({ get: () => (computedHost as any).$$state, set: (v: T) => { if (__DEV__ && !set) { - warn('Computed property was assigned to but it has no setter.', vm!); - return; + warn('Computed property was assigned to but it has no setter.', vm!) + return } - (computedHost as any).$$state = v; + ;(computedHost as any).$$state = v }, - }); + }) } diff --git a/src/apis/inject.ts b/src/apis/inject.ts index c5f7f50b..9b8e533b 100644 --- a/src/apis/inject.ts +++ b/src/apis/inject.ts @@ -1,59 +1,65 @@ -import { ComponentInstance } from '../component'; -import { currentVMInFn } from '../helper'; -import { hasOwn, warn } from '../utils'; -import { getCurrentVM } from '../runtimeContext'; +import { ComponentInstance } from '../component' +import { currentVMInFn } from '../helper' +import { hasOwn, warn } from '../utils' +import { getCurrentVM } from '../runtimeContext' -const NOT_FOUND = {}; +const NOT_FOUND = {} export interface InjectionKey extends Symbol {} -function resolveInject(provideKey: InjectionKey | string, vm: ComponentInstance): any { - let source = vm; +function resolveInject( + provideKey: InjectionKey | string, + vm: ComponentInstance +): any { + let source = vm while (source) { // @ts-ignore if (source._provided && hasOwn(source._provided, provideKey)) { //@ts-ignore - return source._provided[provideKey]; + return source._provided[provideKey] } - source = source.$parent; + source = source.$parent } - return NOT_FOUND; + return NOT_FOUND } export function provide(key: InjectionKey | string, value: T): void { - const vm: any = currentVMInFn('provide'); - if (!vm) return; + const vm: any = currentVMInFn('provide') + if (!vm) return if (!vm._provided) { - const provideCache = {}; + const provideCache = {} Object.defineProperty(vm, '_provided', { get: () => provideCache, set: (v) => Object.assign(provideCache, v), - }); + }) } - vm._provided[key as string] = value; + vm._provided[key as string] = value } -export function inject(key: InjectionKey | string): T | undefined; -export function inject(key: InjectionKey | string, defaultValue: T): T; -export function inject(key: InjectionKey | string, defaultValue?: unknown) { +export function inject(key: InjectionKey | string): T | undefined +export function inject(key: InjectionKey | string, defaultValue: T): T +export function inject( + key: InjectionKey | string, + defaultValue?: unknown +) { if (!key) { - return defaultValue; + return defaultValue } - const vm = getCurrentVM(); + const vm = getCurrentVM() if (vm) { - const val = resolveInject(key, vm); + const val = resolveInject(key, vm) if (val !== NOT_FOUND) { - return val; + return val } else { if (defaultValue === undefined && process.env.NODE_ENV !== 'production') { - warn(`Injection "${String(key)}" not found`, vm); + warn(`Injection "${String(key)}" not found`, vm) } - return defaultValue; + return defaultValue } } else { - warn(`inject() can only be used inside setup() or functional components.`); + warn(`inject() can only be used inside setup() or functional components.`) } } diff --git a/src/apis/lifecycle.ts b/src/apis/lifecycle.ts index d6f6651e..62457d1b 100644 --- a/src/apis/lifecycle.ts +++ b/src/apis/lifecycle.ts @@ -1,44 +1,49 @@ -import { VueConstructor } from 'vue'; -import { ComponentInstance } from '../component'; -import { getCurrentVue, setCurrentVM, getCurrentVM } from '../runtimeContext'; -import { currentVMInFn } from '../helper'; +import { VueConstructor } from 'vue' +import { ComponentInstance } from '../component' +import { getCurrentVue, setCurrentVM, getCurrentVM } from '../runtimeContext' +import { currentVMInFn } from '../helper' -const genName = (name: string) => `on${name[0].toUpperCase() + name.slice(1)}`; +const genName = (name: string) => `on${name[0].toUpperCase() + name.slice(1)}` function createLifeCycle(lifeCyclehook: string) { return (callback: Function) => { - const vm = currentVMInFn(genName(lifeCyclehook)); + const vm = currentVMInFn(genName(lifeCyclehook)) if (vm) { - injectHookOption(getCurrentVue(), vm, lifeCyclehook, callback); + injectHookOption(getCurrentVue(), vm, lifeCyclehook, callback) } - }; + } } -function injectHookOption(Vue: VueConstructor, vm: ComponentInstance, hook: string, val: Function) { - const options = vm.$options as any; - const mergeFn = Vue.config.optionMergeStrategies[hook]; - options[hook] = mergeFn(options[hook], wrapHookCall(vm, val)); +function injectHookOption( + Vue: VueConstructor, + vm: ComponentInstance, + hook: string, + val: Function +) { + const options = vm.$options as any + const mergeFn = Vue.config.optionMergeStrategies[hook] + options[hook] = mergeFn(options[hook], wrapHookCall(vm, val)) } function wrapHookCall(vm: ComponentInstance, fn: Function) { return (...args: any) => { - let preVm = getCurrentVM(); - setCurrentVM(vm); + let preVm = getCurrentVM() + setCurrentVM(vm) try { - return fn(...args); + return fn(...args) } finally { - setCurrentVM(preVm); + setCurrentVM(preVm) } - }; + } } // export const onCreated = createLifeCycle('created'); -export const onBeforeMount = createLifeCycle('beforeMount'); -export const onMounted = createLifeCycle('mounted'); -export const onBeforeUpdate = createLifeCycle('beforeUpdate'); -export const onUpdated = createLifeCycle('updated'); -export const onBeforeUnmount = createLifeCycle('beforeDestroy'); -export const onUnmounted = createLifeCycle('destroyed'); -export const onErrorCaptured = createLifeCycle('errorCaptured'); -export const onActivated = createLifeCycle('activated'); -export const onDeactivated = createLifeCycle('deactivated'); -export const onServerPrefetch = createLifeCycle('serverPrefetch'); +export const onBeforeMount = createLifeCycle('beforeMount') +export const onMounted = createLifeCycle('mounted') +export const onBeforeUpdate = createLifeCycle('beforeUpdate') +export const onUpdated = createLifeCycle('updated') +export const onBeforeUnmount = createLifeCycle('beforeDestroy') +export const onUnmounted = createLifeCycle('destroyed') +export const onErrorCaptured = createLifeCycle('errorCaptured') +export const onActivated = createLifeCycle('activated') +export const onDeactivated = createLifeCycle('deactivated') +export const onServerPrefetch = createLifeCycle('serverPrefetch') diff --git a/src/apis/state.ts b/src/apis/state.ts index 15b21d1e..dcd22c95 100644 --- a/src/apis/state.ts +++ b/src/apis/state.ts @@ -14,4 +14,4 @@ export { toRaw, shallowRef, triggerRef, -} from '../reactivity'; +} from '../reactivity' diff --git a/src/apis/watch.ts b/src/apis/watch.ts index 342597fc..69f9acc3 100644 --- a/src/apis/watch.ts +++ b/src/apis/watch.ts @@ -1,75 +1,75 @@ -import { ComponentInstance } from '../component'; -import { Ref, isRef, isReactive } from '../reactivity'; -import { assert, logError, noopFn, warn, isFunction } from '../utils'; -import { defineComponentInstance } from '../helper'; -import { getCurrentVM, getCurrentVue } from '../runtimeContext'; -import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey } from '../symbols'; -import { ComputedRef } from './computed'; +import { ComponentInstance } from '../component' +import { Ref, isRef, isReactive } from '../reactivity' +import { assert, logError, noopFn, warn, isFunction } from '../utils' +import { defineComponentInstance } from '../helper' +import { getCurrentVM, getCurrentVue } from '../runtimeContext' +import { WatcherPreFlushQueueKey, WatcherPostFlushQueueKey } from '../symbols' +import { ComputedRef } from './computed' -export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void; +export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void -export type WatchSource = Ref | ComputedRef | (() => T); +export type WatchSource = Ref | ComputedRef | (() => T) export type WatchCallback = ( value: V, oldValue: OV, onInvalidate: InvalidateCbRegistrator -) => any; +) => any type MapSources = { - [K in keyof T]: T[K] extends WatchSource ? V : never; -}; + [K in keyof T]: T[K] extends WatchSource ? V : never +} type MapOldSources = { [K in keyof T]: T[K] extends WatchSource ? Immediate extends true ? V | undefined : V - : never; -}; + : never +} export interface WatchOptionsBase { - flush?: FlushMode; + flush?: FlushMode // onTrack?: ReactiveEffectOptions['onTrack']; // onTrigger?: ReactiveEffectOptions['onTrigger']; } -type InvalidateCbRegistrator = (cb: () => void) => void; +type InvalidateCbRegistrator = (cb: () => void) => void -export type FlushMode = 'pre' | 'post' | 'sync'; +export type FlushMode = 'pre' | 'post' | 'sync' export interface WatchOptions extends WatchOptionsBase { - immediate?: Immediate; - deep?: boolean; + immediate?: Immediate + deep?: boolean } export interface VueWatcher { - lazy: boolean; - get(): any; - teardown(): void; + lazy: boolean + get(): any + teardown(): void } -export type WatchStopHandle = () => void; +export type WatchStopHandle = () => void -let fallbackVM: ComponentInstance; +let fallbackVM: ComponentInstance function flushPreQueue(this: any) { - flushQueue(this, WatcherPreFlushQueueKey); + flushQueue(this, WatcherPreFlushQueueKey) } function flushPostQueue(this: any) { - flushQueue(this, WatcherPostFlushQueueKey); + flushQueue(this, WatcherPostFlushQueueKey) } function hasWatchEnv(vm: any) { - return vm[WatcherPreFlushQueueKey] !== undefined; + return vm[WatcherPreFlushQueueKey] !== undefined } function installWatchEnv(vm: any) { - vm[WatcherPreFlushQueueKey] = []; - vm[WatcherPostFlushQueueKey] = []; - vm.$on('hook:beforeUpdate', flushPreQueue); - vm.$on('hook:updated', flushPostQueue); + vm[WatcherPreFlushQueueKey] = [] + vm[WatcherPostFlushQueueKey] = [] + vm.$on('hook:beforeUpdate', flushPreQueue) + vm.$on('hook:updated', flushPostQueue) } function getWatcherOption(options?: Partial): WatchOptions { @@ -80,7 +80,7 @@ function getWatcherOption(options?: Partial): WatchOptions { flush: 'post', }, ...options, - }; + } } function getWatchEffectOption(options?: Partial): WatchOptions { @@ -91,55 +91,62 @@ function getWatchEffectOption(options?: Partial): WatchOptions { flush: 'post', }, ...options, - }; + } } function getWatcherVM() { - let vm = getCurrentVM(); + let vm = getCurrentVM() if (!vm) { if (!fallbackVM) { - fallbackVM = defineComponentInstance(getCurrentVue()); + fallbackVM = defineComponentInstance(getCurrentVue()) } - vm = fallbackVM; + vm = fallbackVM } else if (!hasWatchEnv(vm)) { - installWatchEnv(vm); + installWatchEnv(vm) } - return vm; + return vm } function flushQueue(vm: any, key: any) { - const queue = vm[key]; + const queue = vm[key] for (let index = 0; index < queue.length; index++) { - queue[index](); + queue[index]() } - queue.length = 0; + queue.length = 0 } -function queueFlushJob(vm: any, fn: () => void, mode: Exclude) { +function queueFlushJob( + vm: any, + fn: () => void, + mode: Exclude +) { // flush all when beforeUpdate and updated are not fired const fallbackFlush = () => { vm.$nextTick(() => { if (vm[WatcherPreFlushQueueKey].length) { - flushQueue(vm, WatcherPreFlushQueueKey); + flushQueue(vm, WatcherPreFlushQueueKey) } if (vm[WatcherPostFlushQueueKey].length) { - flushQueue(vm, WatcherPostFlushQueueKey); + flushQueue(vm, WatcherPostFlushQueueKey) } - }); - }; + }) + } switch (mode) { case 'pre': - fallbackFlush(); - vm[WatcherPreFlushQueueKey].push(fn); - break; + fallbackFlush() + vm[WatcherPreFlushQueueKey].push(fn) + break case 'post': - fallbackFlush(); - vm[WatcherPostFlushQueueKey].push(fn); - break; + fallbackFlush() + vm[WatcherPostFlushQueueKey].push(fn) + break default: - assert(false, `flush must be one of ["post", "pre", "sync"], but got ${mode}`); - break; + assert( + false, + `flush must be one of ["post", "pre", "sync"], but got ${mode}` + ) + break } } @@ -148,14 +155,14 @@ function createVueWatcher( getter: () => any, callback: (n: any, o: any) => any, options: { - deep: boolean; - sync: boolean; - immediateInvokeCallback?: boolean; - noRun?: boolean; - before?: () => void; + deep: boolean + sync: boolean + immediateInvokeCallback?: boolean + noRun?: boolean + before?: () => void } ): VueWatcher { - const index = vm._watchers.length; + const index = vm._watchers.length // @ts-ignore: use undocumented options vm.$watch(getter, callback, { immediate: options.immediateInvokeCallback, @@ -163,19 +170,19 @@ function createVueWatcher( lazy: options.noRun, sync: options.sync, before: options.before, - }); + }) - return vm._watchers[index]; + return vm._watchers[index] } // We have to monkeypatch the teardown function so Vue will run // runCleanup() when it tears down the watcher on unmmount. function patchWatcherTeardown(watcher: VueWatcher, runCleanup: () => void) { - const _teardown = watcher.teardown; + const _teardown = watcher.teardown watcher.teardown = function (...args) { - _teardown.apply(watcher, args); - runCleanup(); - }; + _teardown.apply(watcher, args) + runCleanup() + } } function createWatcher( @@ -184,100 +191,104 @@ function createWatcher( cb: WatchCallback | null, options: WatchOptions ): () => void { - const flushMode = options.flush; - const isSync = flushMode === 'sync'; - let cleanup: (() => void) | null; + const flushMode = options.flush + const isSync = flushMode === 'sync' + let cleanup: (() => void) | null const registerCleanup: InvalidateCbRegistrator = (fn: () => void) => { cleanup = () => { try { - fn(); + fn() } catch (error) { - logError(error, vm, 'onCleanup()'); + logError(error, vm, 'onCleanup()') } - }; - }; + } + } // cleanup before running getter again const runCleanup = () => { if (cleanup) { - cleanup(); - cleanup = null; + cleanup() + cleanup = null } - }; + } const createScheduler = (fn: T): T => { - if (isSync || /* without a current active instance, ignore pre|post mode */ vm === fallbackVM) { - return fn; + if ( + isSync || + /* without a current active instance, ignore pre|post mode */ vm === + fallbackVM + ) { + return fn } return (((...args: any[]) => queueFlushJob( vm, () => { - fn(...args); + fn(...args) }, flushMode as 'pre' | 'post' - )) as any) as T; - }; + )) as any) as T + } // effect watch if (cb === null) { - const getter = () => (source as WatchEffect)(registerCleanup); + const getter = () => (source as WatchEffect)(registerCleanup) const watcher = createVueWatcher(vm, getter, noopFn, { deep: options.deep || false, sync: isSync, before: runCleanup, - }); + }) - patchWatcherTeardown(watcher, runCleanup); + patchWatcherTeardown(watcher, runCleanup) // enable the watcher update - watcher.lazy = false; - const originGet = watcher.get.bind(watcher); + watcher.lazy = false + const originGet = watcher.get.bind(watcher) // always run watchEffect - watcher.get = createScheduler(originGet); + watcher.get = createScheduler(originGet) return () => { - watcher.teardown(); - }; + watcher.teardown() + } } - let deep = options.deep; + let deep = options.deep - let getter: () => any; + let getter: () => any if (Array.isArray(source)) { - getter = () => source.map((s) => (isRef(s) ? s.value : s())); + getter = () => source.map((s) => (isRef(s) ? s.value : s())) } else if (isRef(source)) { - getter = () => source.value; + getter = () => source.value } else if (isReactive(source)) { - getter = () => source; - deep = true; + getter = () => source + deep = true } else if (isFunction(source)) { - getter = source as () => any; + getter = source as () => any } else { - getter = noopFn; + getter = noopFn warn( `Invalid watch source: ${JSON.stringify(source)}. A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`, vm - ); + ) } const applyCb = (n: any, o: any) => { // cleanup before running cb again - runCleanup(); - cb(n, o, registerCleanup); - }; - let callback = createScheduler(applyCb); + runCleanup() + cb(n, o, registerCleanup) + } + let callback = createScheduler(applyCb) if (options.immediate) { - const originalCallbck = callback; + const originalCallbck = callback // `shiftCallback` is used to handle the first sync effect run. // The subsequent callbacks will redirect to `callback`. let shiftCallback = (n: any, o: any) => { - shiftCallback = originalCallbck; - applyCb(n, o); - }; + shiftCallback = originalCallbck + applyCb(n, o) + } callback = (n: any, o: any) => { - shiftCallback(n, o); - }; + shiftCallback(n, o) + } } // @ts-ignore: use undocumented option "sync" @@ -285,21 +296,24 @@ function createWatcher( immediate: options.immediate, deep: deep, sync: isSync, - }); + }) // Once again, we have to hack the watcher for proper teardown - const watcher = vm._watchers[vm._watchers.length - 1]; - patchWatcherTeardown(watcher, runCleanup); + const watcher = vm._watchers[vm._watchers.length - 1] + patchWatcherTeardown(watcher, runCleanup) return () => { - stop(); - }; + stop() + } } -export function watchEffect(effect: WatchEffect, options?: WatchOptionsBase): WatchStopHandle { - const opts = getWatchEffectOption(options); - const vm = getWatcherVM(); - return createWatcher(vm, effect, null, opts); +export function watchEffect( + effect: WatchEffect, + options?: WatchOptionsBase +): WatchStopHandle { + const opts = getWatchEffectOption(options) + const vm = getWatcherVM() + return createWatcher(vm, effect, null, opts) } // overload #1: array of multiple sources + cb @@ -313,21 +327,24 @@ export function watch< sources: T, cb: WatchCallback, MapOldSources>, options?: WatchOptions -): WatchStopHandle; +): WatchStopHandle // overload #2: single source + cb export function watch = false>( source: WatchSource, cb: WatchCallback, options?: WatchOptions -): WatchStopHandle; +): WatchStopHandle // overload #3: watching reactive object w/ cb -export function watch = false>( +export function watch< + T extends object, + Immediate extends Readonly = false +>( source: T, cb: WatchCallback, options?: WatchOptions -): WatchStopHandle; +): WatchStopHandle // implementation export function watch( @@ -335,10 +352,10 @@ export function watch( cb: WatchCallback, options?: WatchOptions ): WatchStopHandle { - let callback: WatchCallback | null = null; + let callback: WatchCallback | null = null if (typeof cb === 'function') { // source watch - callback = cb as WatchCallback; + callback = cb as WatchCallback } else { // effect watch if (__DEV__) { @@ -346,14 +363,14 @@ export function watch( `\`watch(fn, options?)\` signature has been moved to a separate API. ` + `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` + `supports \`watch(source, cb, options?) signature.` - ); + ) } - options = cb as Partial; - callback = null; + options = cb as Partial + callback = null } - const opts = getWatcherOption(options); - const vm = getWatcherVM(); + const opts = getWatcherOption(options) + const vm = getWatcherVM() - return createWatcher(vm, source, callback, opts); + return createWatcher(vm, source, callback, opts) } diff --git a/src/component/component.ts b/src/component/component.ts index 0c9edd24..8751a22b 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -1,25 +1,29 @@ -import Vue, { VueConstructor, VNode, ComponentOptions as Vue2ComponentOptions } from 'vue'; -import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'; -import { UnwrapRef } from '../reactivity'; -import { HasDefined } from '../types/basic'; +import Vue, { + VueConstructor, + VNode, + ComponentOptions as Vue2ComponentOptions, +} from 'vue' +import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' +import { UnwrapRef } from '../reactivity' +import { HasDefined } from '../types/basic' -export type Data = { [key: string]: unknown }; +export type Data = { [key: string]: unknown } -export type ComponentInstance = InstanceType; +export type ComponentInstance = InstanceType // public properties exposed on the proxy, which is used as the render context // in templates (as `this` in the render option) export type ComponentRenderProxy

= { - $data: S; - $props: PublicProps; - $attrs: Data; - $refs: Data; - $slots: Data; - $root: ComponentInstance | null; - $parent: ComponentInstance | null; - $emit: (event: string, ...args: unknown[]) => void; + $data: S + $props: PublicProps + $attrs: Data + $refs: Data + $slots: Data + $root: ComponentInstance | null + $parent: ComponentInstance | null + $emit: (event: string, ...args: unknown[]) => void } & P & - S; + S // for Vetur and TSX support type VueConstructorProxy = VueConstructor & { @@ -27,8 +31,8 @@ type VueConstructorProxy = VueConstructor & { ExtractPropTypes, UnwrapRef, ExtractPropTypes - >; -}; + > +} type VueProxy = Vue2ComponentOptions< Vue, @@ -38,31 +42,31 @@ type VueProxy = Vue2ComponentOptions< PropsOptions, ExtractPropTypes > & - VueConstructorProxy; + VueConstructorProxy export interface SetupContext { - readonly attrs: Record; - readonly slots: { [key: string]: (...args: any[]) => VNode[] }; - readonly parent: ComponentInstance | null; - readonly root: ComponentInstance; - readonly listeners: { [key: string]: Function }; + readonly attrs: Record + readonly slots: { [key: string]: (...args: any[]) => VNode[] } + readonly parent: ComponentInstance | null + readonly root: ComponentInstance + readonly listeners: { [key: string]: Function } - emit(event: string, ...args: any[]): void; + emit(event: string, ...args: any[]): void } export type SetupFunction = ( this: void, props: Props, ctx: SetupContext -) => RawBindings | (() => VNode | null); +) => RawBindings | (() => VNode | null) interface ComponentOptionsWithProps< PropsOptions = ComponentPropsOptions, RawBindings = Data, Props = ExtractPropTypes > { - props?: PropsOptions; - setup?: SetupFunction; + props?: PropsOptions + setup?: SetupFunction } interface ComponentOptionsWithArrayProps< @@ -70,19 +74,19 @@ interface ComponentOptionsWithArrayProps< RawBindings = Data, Props = Readonly<{ [key in PropNames]?: any }> > { - props?: PropNames[]; - setup?: SetupFunction; + props?: PropNames[] + setup?: SetupFunction } interface ComponentOptionsWithoutProps { - props?: undefined; - setup?: SetupFunction; + props?: undefined + setup?: SetupFunction } // overload 1: object format with no props export function defineComponent( options: ComponentOptionsWithoutProps -): VueProxy; +): VueProxy // overload 2: object format with array props declaration // props inferred as { [key in PropNames]?: any } // return type is for Vetur and TSX support @@ -95,7 +99,7 @@ export function defineComponent< options: ( ComponentOptionsWithArrayProps) & Omit, keyof ComponentOptionsWithProps> -): VueProxy, RawBindings>; +): VueProxy, RawBindings> // overload 3: object format with object props declaration // see `ExtractPropTypes` in ./componentProps.ts export function defineComponent< @@ -110,16 +114,16 @@ export function defineComponent< ? ComponentOptionsWithProps : ComponentOptionsWithProps) & Omit, keyof ComponentOptionsWithProps> -): VueProxy; +): VueProxy // implementation, close to no-op export function defineComponent(options: any) { - return options as any; + return options as any } // overload 1: object format with no props export function createComponent( options: ComponentOptionsWithoutProps -): VueProxy; +): VueProxy // overload 2: object format with array props declaration // props inferred as { [key in PropNames]?: any } // return type is for Vetur and TSX support @@ -132,7 +136,7 @@ export function createComponent< options: ( ComponentOptionsWithArrayProps) & Omit, keyof ComponentOptionsWithProps> -): VueProxy, RawBindings>; +): VueProxy, RawBindings> // overload 3: object format with object props declaration // see `ExtractPropTypes` in ./componentProps.ts export function createComponent< @@ -147,11 +151,11 @@ export function createComponent< ? ComponentOptionsWithProps : ComponentOptionsWithProps) & Omit, keyof ComponentOptionsWithProps> -): VueProxy; +): VueProxy // implementation, deferring to defineComponent, but logging a warning in dev mode export function createComponent(options: any) { if (__DEV__) { - Vue.util.warn('`createComponent` has been renamed to `defineComponent`.'); + Vue.util.warn('`createComponent` has been renamed to `defineComponent`.') } - return defineComponent(options); + return defineComponent(options) } diff --git a/src/component/componentProps.ts b/src/component/componentProps.ts index 3593e49d..a6eeb5cd 100644 --- a/src/component/componentProps.ts +++ b/src/component/componentProps.ts @@ -1,48 +1,53 @@ -import { Data } from './component'; +import { Data } from './component' -export type ComponentPropsOptions

= ComponentObjectPropsOptions

| string[]; +export type ComponentPropsOptions

= + | ComponentObjectPropsOptions

+ | string[] export type ComponentObjectPropsOptions

= { - [K in keyof P]: Prop | null; -}; + [K in keyof P]: Prop | null +} -export type Prop = PropOptions | PropType; +export type Prop = PropOptions | PropType -type DefaultFactory = () => T | null | undefined; +type DefaultFactory = () => T | null | undefined export interface PropOptions { - type?: PropType | true | null; - required?: boolean; - default?: T | DefaultFactory | null | undefined; - validator?(value: unknown): boolean; + type?: PropType | true | null + required?: boolean + default?: T | DefaultFactory | null | undefined + validator?(value: unknown): boolean } -export type PropType = PropConstructor | PropConstructor[]; +export type PropType = PropConstructor | PropConstructor[] type PropConstructor = | { new (...args: any[]): T & object } | { (): T } - | { new (...args: string[]): Function }; + | { new (...args: string[]): Function } type RequiredKeys = { [K in keyof T]: T[K] extends | { required: true } | (MakeDefaultRequired extends true ? { default: any } : never) ? K - : never; -}[keyof T]; + : never +}[keyof T] -type OptionalKeys = Exclude>; +type OptionalKeys = Exclude< + keyof T, + RequiredKeys +> type ExtractFunctionPropType< T extends Function, TArgs extends Array = any[], TResult = any -> = T extends (...args: TArgs) => TResult ? T : never; +> = T extends (...args: TArgs) => TResult ? T : never type ExtractCorrectPropType = T extends Function ? ExtractFunctionPropType - : Exclude; + : Exclude // prettier-ignore type InferPropType = T extends null @@ -56,7 +61,10 @@ type InferPropType = T extends null : T extends Prop ? ExtractCorrectPropType : T; -export type ExtractPropTypes = O extends object +export type ExtractPropTypes< + O, + MakeDefaultRequired extends boolean = true +> = O extends object ? { [K in RequiredKeys]: InferPropType } & { [K in OptionalKeys]?: InferPropType } - : { [K in string]: any }; + : { [K in string]: any } diff --git a/src/component/index.ts b/src/component/index.ts index a62de486..a880a467 100644 --- a/src/component/index.ts +++ b/src/component/index.ts @@ -6,5 +6,5 @@ export { SetupContext, ComponentInstance, ComponentRenderProxy, -} from './component'; -export { PropType, PropOptions } from './componentProps'; +} from './component' +export { PropType, PropOptions } from './componentProps' diff --git a/src/reactivity/index.ts b/src/reactivity/index.ts index 514ca889..1b18ad29 100644 --- a/src/reactivity/index.ts +++ b/src/reactivity/index.ts @@ -1,4 +1,11 @@ -export { reactive, isReactive, markRaw, shallowReactive, toRaw, isRaw } from './reactive'; +export { + reactive, + isReactive, + markRaw, + shallowReactive, + toRaw, + isRaw, +} from './reactive' export { ref, isRef, @@ -10,5 +17,5 @@ export { unref, shallowRef, triggerRef, -} from './ref'; -export { set } from './set'; +} from './ref' +export { set } from './set' diff --git a/src/reactivity/reactive.ts b/src/reactivity/reactive.ts index 7ddff737..dd66744f 100644 --- a/src/reactivity/reactive.ts +++ b/src/reactivity/reactive.ts @@ -1,21 +1,23 @@ -import { AnyObject } from '../types/basic'; -import { getCurrentVue } from '../runtimeContext'; -import { isPlainObject, def, hasOwn, warn } from '../utils'; -import { isComponentInstance, defineComponentInstance } from '../helper'; +import { AnyObject } from '../types/basic' +import { getCurrentVue } from '../runtimeContext' +import { isPlainObject, def, hasOwn, warn } from '../utils' +import { isComponentInstance, defineComponentInstance } from '../helper' import { AccessControlIdentifierKey, ReactiveIdentifierKey, RawIdentifierKey, RefKey, -} from '../symbols'; -import { isRef, UnwrapRef } from './ref'; +} from '../symbols' +import { isRef, UnwrapRef } from './ref' -const AccessControlIdentifier = {}; -const ReactiveIdentifier = {}; -const RawIdentifier = {}; +const AccessControlIdentifier = {} +const ReactiveIdentifier = {} +const RawIdentifier = {} export function isRaw(obj: any): boolean { - return hasOwn(obj, RawIdentifierKey) && obj[RawIdentifierKey] === RawIdentifier; + return ( + hasOwn(obj, RawIdentifierKey) && obj[RawIdentifierKey] === RawIdentifier + ) } export function isReactive(obj: any): boolean { @@ -23,7 +25,7 @@ export function isReactive(obj: any): boolean { Object.isExtensible(obj) && hasOwn(obj, ReactiveIdentifierKey) && obj[ReactiveIdentifierKey] === ReactiveIdentifier - ); + ) } /** @@ -38,22 +40,22 @@ function setupAccessControl(target: AnyObject): void { isRef(target) || isComponentInstance(target) ) { - return; + return } if ( hasOwn(target, AccessControlIdentifierKey) && target[AccessControlIdentifierKey] === AccessControlIdentifier ) { - return; + return } if (Object.isExtensible(target)) { - def(target, AccessControlIdentifierKey, AccessControlIdentifier); + def(target, AccessControlIdentifierKey, AccessControlIdentifier) } - const keys = Object.keys(target); + const keys = Object.keys(target) for (let i = 0; i < keys.length; i++) { - defineAccessControl(target, keys[i]); + defineAccessControl(target, keys[i]) } } @@ -61,101 +63,112 @@ function setupAccessControl(target: AnyObject): void { * Auto unwrapping when access property */ export function defineAccessControl(target: AnyObject, key: any, val?: any) { - if (key === '__ob__') return; + if (key === '__ob__') return - let getter: (() => any) | undefined; - let setter: ((x: any) => void) | undefined; - const property = Object.getOwnPropertyDescriptor(target, key); + let getter: (() => any) | undefined + let setter: ((x: any) => void) | undefined + const property = Object.getOwnPropertyDescriptor(target, key) if (property) { if (property.configurable === false) { - return; + return } - getter = property.get; - setter = property.set; - if ((!getter || setter) /* not only have getter */ && arguments.length === 2) { - val = target[key]; + getter = property.get + setter = property.set + if ( + (!getter || setter) /* not only have getter */ && + arguments.length === 2 + ) { + val = target[key] } } - setupAccessControl(val); + setupAccessControl(val) Object.defineProperty(target, key, { enumerable: true, configurable: true, get: function getterHandler() { - const value = getter ? getter.call(target) : val; + const value = getter ? getter.call(target) : val // if the key is equal to RefKey, skip the unwrap logic if (key !== RefKey && isRef(value)) { - return value.value; + return value.value } else { - return value; + return value } }, set: function setterHandler(newVal) { - if (getter && !setter) return; + if (getter && !setter) return - const value = getter ? getter.call(target) : val; + const value = getter ? getter.call(target) : val // If the key is equal to RefKey, skip the unwrap logic // If and only if "value" is ref and "newVal" is not a ref, // the assignment should be proxied to "value" ref. if (key !== RefKey && isRef(value) && !isRef(newVal)) { - value.value = newVal; + value.value = newVal } else if (setter) { - setter.call(target, newVal); + setter.call(target, newVal) } else { - val = newVal; + val = newVal } - setupAccessControl(newVal); + setupAccessControl(newVal) }, - }); + }) } function observe(obj: T): T { - const Vue = getCurrentVue(); - let observed: T; + const Vue = getCurrentVue() + let observed: T if (Vue.observable) { - observed = Vue.observable(obj); + observed = Vue.observable(obj) } else { const vm = defineComponentInstance(Vue, { data: { $$state: obj, }, - }); - observed = vm._data.$$state; + }) + observed = vm._data.$$state } - return observed; + return observed } export function shallowReactive(obj: T): T { if (__DEV__ && !obj) { - warn('"shallowReactive()" is called without provide an "object".'); + warn('"shallowReactive()" is called without provide an "object".') // @ts-ignore - return; + return } - if (!isPlainObject(obj) || isReactive(obj) || isRaw(obj) || !Object.isExtensible(obj)) { - return obj as any; + if ( + !isPlainObject(obj) || + isReactive(obj) || + isRaw(obj) || + !Object.isExtensible(obj) + ) { + return obj as any } - const observed = observe({}); - markReactive(observed, true); - setupAccessControl(observed); + const observed = observe({}) + markReactive(observed, true) + setupAccessControl(observed) - const ob = (observed as any).__ob__; + const ob = (observed as any).__ob__ for (const key of Object.keys(obj)) { - let val = obj[key]; - let getter: (() => any) | undefined; - let setter: ((x: any) => void) | undefined; - const property = Object.getOwnPropertyDescriptor(obj, key); + let val = obj[key] + let getter: (() => any) | undefined + let setter: ((x: any) => void) | undefined + const property = Object.getOwnPropertyDescriptor(obj, key) if (property) { if (property.configurable === false) { - continue; + continue } - getter = property.get; - setter = property.set; - if ((!getter || setter) /* not only have getter */ && arguments.length === 2) { - val = obj[key]; + getter = property.get + setter = property.set + if ( + (!getter || setter) /* not only have getter */ && + arguments.length === 2 + ) { + val = obj[key] } } @@ -164,22 +177,22 @@ export function shallowReactive(obj: T): T { enumerable: true, configurable: true, get: function getterHandler() { - const value = getter ? getter.call(obj) : val; - ob.dep.depend(); - return value; + const value = getter ? getter.call(obj) : val + ob.dep.depend() + return value }, set: function setterHandler(newVal) { - if (getter && !setter) return; + if (getter && !setter) return if (setter) { - setter.call(obj, newVal); + setter.call(obj, newVal) } else { - val = newVal; + val = newVal } - ob.dep.notify(); + ob.dep.notify() }, - }); + }) } - return (observed as unknown) as T; + return (observed as unknown) as T } export function markReactive(target: any, shallow = false) { @@ -190,26 +203,26 @@ export function markReactive(target: any, shallow = false) { isRef(target) || isComponentInstance(target) ) { - return; + return } if ( hasOwn(target, ReactiveIdentifierKey) && target[ReactiveIdentifierKey] === ReactiveIdentifier ) { - return; + return } if (Object.isExtensible(target)) { - def(target, ReactiveIdentifierKey, ReactiveIdentifier); + def(target, ReactiveIdentifierKey, ReactiveIdentifier) } if (shallow) { - return; + return } - const keys = Object.keys(target); + const keys = Object.keys(target) for (let i = 0; i < keys.length; i++) { - markReactive(target[keys[i]]); + markReactive(target[keys[i]]) } } @@ -218,20 +231,25 @@ export function markReactive(target: any, shallow = false) { */ export function reactive(obj: T): UnwrapRef { if (__DEV__ && !obj) { - warn('"reactive()" is called without provide an "object".'); + warn('"reactive()" is called without provide an "object".') // @ts-ignore - return; + return } - if (!isPlainObject(obj) || isReactive(obj) || isRaw(obj) || !Object.isExtensible(obj)) { - return obj as any; + if ( + !isPlainObject(obj) || + isReactive(obj) || + isRaw(obj) || + !Object.isExtensible(obj) + ) { + return obj as any } - const observed = observe(obj); + const observed = observe(obj) // def(obj, ReactiveIdentifierKey, ReactiveIdentifier); - markReactive(obj); - setupAccessControl(observed); - return observed as UnwrapRef; + markReactive(obj) + setupAccessControl(observed) + return observed as UnwrapRef } /** @@ -239,21 +257,21 @@ export function reactive(obj: T): UnwrapRef { */ export function markRaw(obj: T): T { if (!isPlainObject(obj) || !Object.isExtensible(obj)) { - return obj; + return obj } // set the vue observable flag at obj - def(obj, '__ob__', (observe({}) as any).__ob__); + def(obj, '__ob__', (observe({}) as any).__ob__) // mark as Raw - def(obj, RawIdentifierKey, RawIdentifier); + def(obj, RawIdentifierKey, RawIdentifier) - return obj; + return obj } export function toRaw(observed: T): T { if (isRaw(observe) || !Object.isExtensible(observed)) { - return observed; + return observed } - return (observed as any).__ob__.value || observed; + return (observed as any).__ob__.value || observed } diff --git a/src/reactivity/ref.ts b/src/reactivity/ref.ts index 6dece4ad..a5e44481 100644 --- a/src/reactivity/ref.ts +++ b/src/reactivity/ref.ts @@ -1,32 +1,32 @@ -import { Data } from '../component'; -import { RefKey } from '../symbols'; -import { proxy, isPlainObject, warn } from '../utils'; -import { reactive, isReactive, shallowReactive } from './reactive'; -import { ComputedRef } from '../apis/computed'; +import { Data } from '../component' +import { RefKey } from '../symbols' +import { proxy, isPlainObject, warn } from '../utils' +import { reactive, isReactive, shallowReactive } from './reactive' +import { ComputedRef } from '../apis/computed' -declare const _refBrand: unique symbol; +declare const _refBrand: unique symbol export interface Ref { - readonly [_refBrand]: true; - value: T; + readonly [_refBrand]: true + value: T } -export type ToRefs = { [K in keyof T]: Ref }; +export type ToRefs = { [K in keyof T]: Ref } -export type CollectionTypes = IterableCollections | WeakCollections; +export type CollectionTypes = IterableCollections | WeakCollections -type IterableCollections = Map | Set; -type WeakCollections = WeakMap | WeakSet; +type IterableCollections = Map | Set +type WeakCollections = WeakMap | WeakSet // corner case when use narrows type // Ex. type RelativePath = string & { __brand: unknown } // RelativePath extends object -> true -type BaseTypes = string | number | boolean | Node | Window; +type BaseTypes = string | number | boolean | Node | Window export type UnwrapRef = T extends ComputedRef ? UnwrapRefSimple : T extends Ref ? UnwrapRefSimple - : UnwrapRefSimple; + : UnwrapRefSimple type UnwrapRefSimple = T extends Function | CollectionTypes | BaseTypes | Ref ? T @@ -34,7 +34,7 @@ type UnwrapRefSimple = T extends Function | CollectionTypes | BaseTypes | Ref ? T : T extends object ? UnwrappedObject - : T; + : T // Extract all known symbols from an object // when unwrapping Object the symbols are not `in keyof`, this should cover all the @@ -42,32 +42,42 @@ type UnwrapRefSimple = T extends Function | CollectionTypes | BaseTypes | Ref type SymbolExtract = (T extends { [Symbol.asyncIterator]: infer V } ? { [Symbol.asyncIterator]: V } : {}) & - (T extends { [Symbol.hasInstance]: infer V } ? { [Symbol.hasInstance]: V } : {}) & - (T extends { [Symbol.isConcatSpreadable]: infer V } ? { [Symbol.isConcatSpreadable]: V } : {}) & + (T extends { [Symbol.hasInstance]: infer V } + ? { [Symbol.hasInstance]: V } + : {}) & + (T extends { [Symbol.isConcatSpreadable]: infer V } + ? { [Symbol.isConcatSpreadable]: V } + : {}) & (T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) & (T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) & (T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) & (T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) & (T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) & (T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) & - (T extends { [Symbol.toPrimitive]: infer V } ? { [Symbol.toPrimitive]: V } : {}) & - (T extends { [Symbol.toStringTag]: infer V } ? { [Symbol.toStringTag]: V } : {}) & - (T extends { [Symbol.unscopables]: infer V } ? { [Symbol.unscopables]: V } : {}); - -type UnwrappedObject = { [P in keyof T]: UnwrapRef } & SymbolExtract; + (T extends { [Symbol.toPrimitive]: infer V } + ? { [Symbol.toPrimitive]: V } + : {}) & + (T extends { [Symbol.toStringTag]: infer V } + ? { [Symbol.toStringTag]: V } + : {}) & + (T extends { [Symbol.unscopables]: infer V } + ? { [Symbol.unscopables]: V } + : {}) + +type UnwrappedObject = { [P in keyof T]: UnwrapRef } & SymbolExtract interface RefOption { - get(): T; - set?(x: T): void; + get(): T + set?(x: T): void } class RefImpl implements Ref { - readonly [_refBrand]!: true; - public value!: T; + readonly [_refBrand]!: true + public value!: T constructor({ get, set }: RefOption) { proxy(this, 'value', { get, set, - }); + }) } } @@ -75,72 +85,77 @@ export function createRef(options: RefOption) { // seal the ref, this could prevent ref from being observed // It's safe to seal the ref, since we really shouldn't extend it. // related issues: #79 - return Object.seal(new RefImpl(options)); + return Object.seal(new RefImpl(options)) } -export function ref(raw: T): T extends Ref ? T : Ref>; -export function ref(raw: T): Ref>; -export function ref(): Ref; +export function ref( + raw: T +): T extends Ref ? T : Ref> +export function ref(raw: T): Ref> +export function ref(): Ref export function ref(raw?: unknown) { if (isRef(raw)) { - return raw; + return raw } - const value = reactive({ [RefKey]: raw }); + const value = reactive({ [RefKey]: raw }) return createRef({ get: () => value[RefKey] as any, set: (v) => ((value[RefKey] as any) = v), - }); + }) } export function isRef(value: any): value is Ref { - return value instanceof RefImpl; + return value instanceof RefImpl } export function unref(ref: T): T extends Ref ? V : T { - return isRef(ref) ? (ref.value as any) : ref; + return isRef(ref) ? (ref.value as any) : ref } export function toRefs(obj: T): ToRefs { - if (!isPlainObject(obj)) return obj as any; + if (!isPlainObject(obj)) return obj as any if (__DEV__ && !isReactive(obj)) { - warn(`toRefs() expects a reactive object but received a plain one.`); + warn(`toRefs() expects a reactive object but received a plain one.`) } - const ret: any = {}; + const ret: any = {} for (const key in obj) { - ret[key] = toRef(obj, key); + ret[key] = toRef(obj, key) } - return ret; + return ret } -export function toRef(object: T, key: K): Ref { - const v = object[key]; - if (isRef(v)) return v; +export function toRef( + object: T, + key: K +): Ref { + const v = object[key] + if (isRef(v)) return v return createRef({ get: () => object[key], set: (v) => (object[key] = v), - }); + }) } -export function shallowRef(value: T): T extends Ref ? T : Ref; -export function shallowRef(): Ref; +export function shallowRef(value: T): T extends Ref ? T : Ref +export function shallowRef(): Ref export function shallowRef(raw?: unknown) { if (isRef(raw)) { - return raw; + return raw } - const value = shallowReactive({ [RefKey]: raw }); + const value = shallowReactive({ [RefKey]: raw }) return createRef({ get: () => value[RefKey] as any, set: (v) => ((value[RefKey] as any) = v), - }); + }) } export function triggerRef(value: any) { - if (!isRef(value)) return; + if (!isRef(value)) return - value.value = value.value; + value.value = value.value } diff --git a/src/reactivity/set.ts b/src/reactivity/set.ts index 3e542a72..1c2562e4 100644 --- a/src/reactivity/set.ts +++ b/src/reactivity/set.ts @@ -1,9 +1,9 @@ -import { getCurrentVue } from '../runtimeContext'; -import { isArray } from '../utils'; -import { defineAccessControl, markReactive } from './reactive'; +import { getCurrentVue } from '../runtimeContext' +import { isArray } from '../utils' +import { defineAccessControl, markReactive } from './reactive' function isUndef(v: any): boolean { - return v === undefined || v === null; + return v === undefined || v === null } function isPrimitive(value: any): boolean { @@ -13,12 +13,12 @@ function isPrimitive(value: any): boolean { // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' - ); + ) } function isValidArrayIndex(val: any): boolean { - const n = parseFloat(String(val)); - return n >= 0 && Math.floor(n) === n && isFinite(val); + const n = parseFloat(String(val)) + return n >= 0 && Math.floor(n) === n && isFinite(val) } /** @@ -27,38 +27,40 @@ function isValidArrayIndex(val: any): boolean { * already exist. */ export function set(target: any, key: any, val: T): T { - const Vue = getCurrentVue(); - const { warn, defineReactive } = Vue.util; + const Vue = getCurrentVue() + const { warn, defineReactive } = Vue.util if (__DEV__ && (isUndef(target) || isPrimitive(target))) { - warn(`Cannot set reactive property on undefined, null, or primitive value: ${target}`); + warn( + `Cannot set reactive property on undefined, null, or primitive value: ${target}` + ) } if (isArray(target) && isValidArrayIndex(key)) { - target.length = Math.max(target.length, key); - target.splice(key, 1, val); - return val; + target.length = Math.max(target.length, key) + target.splice(key, 1, val) + return val } if (key in target && !(key in Object.prototype)) { - target[key] = val; - return val; + target[key] = val + return val } - const ob = target.__ob__; + const ob = target.__ob__ if (target._isVue || (ob && ob.vmCount)) { __DEV__ && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' - ); - return val; + ) + return val } if (!ob) { - target[key] = val; - return val; + target[key] = val + return val } - defineReactive(ob.value, key, val); + defineReactive(ob.value, key, val) // IMPORTANT: define access control before trigger watcher - defineAccessControl(target, key, val); - markReactive(ob.value[key]); + defineAccessControl(target, key, val) + markReactive(ob.value[key]) - ob.dep.notify(); - return val; + ob.dep.notify() + return val } diff --git a/src/types/basic.ts b/src/types/basic.ts index 5a801e02..9adb6cbb 100644 --- a/src/types/basic.ts +++ b/src/types/basic.ts @@ -1,4 +1,4 @@ -export type AnyObject = Record; +export type AnyObject = Record // Conditional returns can enforce identical types. // See here: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 @@ -6,4 +6,4 @@ export type AnyObject = Record; type Equal = (() => U extends Left ? 1 : 0) extends (() => U extends Right ? 1 : 0) ? true : false; -export type HasDefined = Equal extends true ? false : true; +export type HasDefined = Equal extends true ? false : true diff --git a/test/apis/computed.spec.js b/test/apis/computed.spec.js index 0bba4f55..9700e1f6 100644 --- a/test/apis/computed.spec.js +++ b/test/apis/computed.spec.js @@ -1,197 +1,197 @@ -const Vue = require('vue/dist/vue.common.js'); -const { ref, computed } = require('../../src'); +const Vue = require('vue/dist/vue.common.js') +const { ref, computed } = require('../../src') describe('Hooks computed', () => { beforeEach(() => { - warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); - }); + warn = jest.spyOn(global.console, 'error').mockImplementation(() => null) + }) afterEach(() => { - warn.mockRestore(); - }); + warn.mockRestore() + }) - it('basic usage', done => { + it('basic usage', (done) => { const vm = new Vue({ template: '

{{ b }}
', setup() { - const a = ref(1); - const b = computed(() => a.value + 1); + const a = ref(1) + const b = computed(() => a.value + 1) return { a, b, - }; + } }, - }).$mount(); - expect(vm.b).toBe(2); - expect(vm.$el.textContent).toBe('2'); - vm.a = 2; - expect(vm.b).toBe(3); + }).$mount() + expect(vm.b).toBe(2) + expect(vm.$el.textContent).toBe('2') + vm.a = 2 + expect(vm.b).toBe(3) waitForUpdate(() => { - expect(vm.$el.textContent).toBe('3'); - }).then(done); - }); + expect(vm.$el.textContent).toBe('3') + }).then(done) + }) - it('with setter', done => { + it('with setter', (done) => { const vm = new Vue({ template: '
{{ b }}
', setup() { - const a = ref(1); + const a = ref(1) const b = computed({ get: () => a.value + 1, - set: v => (a.value = v - 1), - }); + set: (v) => (a.value = v - 1), + }) return { a, b, - }; + } }, - }).$mount(); - expect(vm.b).toBe(2); - expect(vm.$el.textContent).toBe('2'); - vm.a = 2; - expect(vm.b).toBe(3); + }).$mount() + expect(vm.b).toBe(2) + expect(vm.$el.textContent).toBe('2') + vm.a = 2 + expect(vm.b).toBe(3) waitForUpdate(() => { - expect(vm.$el.textContent).toBe('3'); - vm.b = 1; - expect(vm.a).toBe(0); + expect(vm.$el.textContent).toBe('3') + vm.b = 1 + expect(vm.a).toBe(0) }) .then(() => { - expect(vm.$el.textContent).toBe('1'); + expect(vm.$el.textContent).toBe('1') }) - .then(done); - }); + .then(done) + }) it('warn assigning to computed with no setter', () => { const vm = new Vue({ setup() { - const b = computed(() => 1); + const b = computed(() => 1) return { b, - }; + } }, - }); - vm.b = 2; + }) + vm.b = 2 expect(warn.mock.calls[0][0]).toMatch( '[Vue warn]: Computed property was assigned to but it has no setter.' - ); - }); + ) + }) - it('watching computed', done => { - const spy = jest.fn(); + it('watching computed', (done) => { + const spy = jest.fn() const vm = new Vue({ setup() { - const a = ref(1); - const b = computed(() => a.value + 1); + const a = ref(1) + const b = computed(() => a.value + 1) return { a, b, - }; + } }, - }); - vm.$watch('b', spy); - vm.a = 2; + }) + vm.$watch('b', spy) + vm.a = 2 waitForUpdate(() => { - expect(spy).toHaveBeenCalledWith(3, 2); - }).then(done); - }); + expect(spy).toHaveBeenCalledWith(3, 2) + }).then(done) + }) it('caching', () => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) const b = computed(() => { - spy(); - return a.value + 1; - }); + spy() + return a.value + 1 + }) return { a, b, - }; + } }, - }); - expect(spy.mock.calls.length).toBe(0); - vm.b; - expect(spy.mock.calls.length).toBe(1); - vm.b; - expect(spy.mock.calls.length).toBe(1); - }); + }) + expect(spy.mock.calls.length).toBe(0) + vm.b + expect(spy.mock.calls.length).toBe(1) + vm.b + expect(spy.mock.calls.length).toBe(1) + }) - it('as component', done => { + it('as component', (done) => { const Comp = Vue.extend({ template: `
{{ b }} {{ c }}
`, setup() { - const a = ref(1); + const a = ref(1) const b = computed(() => { - return a.value + 1; - }); + return a.value + 1 + }) return { a, b, - }; + } }, - }); + }) const vm = new Comp({ setup(_, { _vm }) { const c = computed(() => { - return _vm.b + 1; - }); + return _vm.b + 1 + }) return { c, - }; + } }, - }).$mount(); - expect(vm.b).toBe(2); - expect(vm.c).toBe(3); - expect(vm.$el.textContent).toBe('2 3'); - vm.a = 2; - expect(vm.b).toBe(3); - expect(vm.c).toBe(4); + }).$mount() + expect(vm.b).toBe(2) + expect(vm.c).toBe(3) + expect(vm.$el.textContent).toBe('2 3') + vm.a = 2 + expect(vm.b).toBe(3) + expect(vm.c).toBe(4) waitForUpdate(() => { - expect(vm.$el.textContent).toBe('3 4'); - }).then(done); - }); + expect(vm.$el.textContent).toBe('3 4') + }).then(done) + }) it('rethrow computed error', () => { const vm = new Vue({ setup() { const a = computed(() => { - throw new Error('rethrow'); - }); + throw new Error('rethrow') + }) return { a, - }; + } }, - }); - expect(() => vm.a).toThrowError('rethrow'); - }); + }) + expect(() => vm.a).toThrowError('rethrow') + }) it('Mixins should not break computed properties', () => { const ExampleComponent = Vue.extend({ props: ['test'], - render: h => h('div'), - setup: props => ({ example: computed(() => props.test) }), - }); + render: (h) => h('div'), + setup: (props) => ({ example: computed(() => props.test) }), + }) Vue.mixin({ computed: { foobar() { - return 'test'; + return 'test' }, }, - }); + }) const app = new Vue({ - render: h => + render: (h) => h('div', [ h(ExampleComponent, { props: { test: 'A' } }), h(ExampleComponent, { props: { test: 'B' } }), ]), - }).$mount(); + }).$mount() - expect(app.$children[0].example).toBe('A'); - expect(app.$children[1].example).toBe('B'); - }); -}); + expect(app.$children[0].example).toBe('A') + expect(app.$children[1].example).toBe('B') + }) +}) diff --git a/test/apis/inject.spec.js b/test/apis/inject.spec.js index 839f965d..9954028b 100644 --- a/test/apis/inject.spec.js +++ b/test/apis/inject.spec.js @@ -1,39 +1,39 @@ -const Vue = require('vue/dist/vue.common.js'); -const { inject, provide, ref, reactive } = require('../../src'); +const Vue = require('vue/dist/vue.common.js') +const { inject, provide, ref, reactive } = require('../../src') -let injected; +let injected const injectedComp = { render() {}, setup() { return { foo: inject('foo'), bar: inject('bar'), - }; + } }, created() { - injected = [this.foo, this.bar]; + injected = [this.foo, this.bar] }, -}; +} beforeEach(() => { - injected = null; -}); + injected = null +}) describe('Hooks provide/inject', () => { beforeEach(() => { - warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); - }); + warn = jest.spyOn(global.console, 'error').mockImplementation(() => null) + }) afterEach(() => { - warn.mockRestore(); - }); + warn.mockRestore() + }) it('should work', () => { new Vue({ template: ``, setup() { - const count = ref(1); - provide('foo', count); - provide('bar', false); + const count = ref(1) + provide('foo', count) + provide('bar', false) }, components: { child: { @@ -43,37 +43,37 @@ describe('Hooks provide/inject', () => { }, }, }, - }).$mount(); + }).$mount() - expect(injected).toEqual([1, false]); - }); + expect(injected).toEqual([1, false]) + }) it('should return a default value when inject not found', () => { - let injected; + let injected new Vue({ template: ``, components: { child: { template: `
{{ msg }}
`, setup() { - injected = inject('not-existed-inject-key', 'foo'); + injected = inject('not-existed-inject-key', 'foo') return { injected, - }; + } }, }, }, - }).$mount(); + }).$mount() - expect(injected).toBe('foo'); - }); + expect(injected).toBe('foo') + }) - it('should work for ref value', done => { - const Msg = Symbol(); + it('should work for ref value', (done) => { + const Msg = Symbol() const app = new Vue({ template: ``, setup() { - provide(Msg, ref('hello')); + provide(Msg, ref('hello')) }, components: { child: { @@ -81,53 +81,53 @@ describe('Hooks provide/inject', () => { setup() { return { msg: inject(Msg), - }; + } }, }, }, - }).$mount(); + }).$mount() - app.$children[0].msg = 'bar'; + app.$children[0].msg = 'bar' waitForUpdate(() => { - expect(app.$el.textContent).toBe('bar'); - }).then(done); - }); + expect(app.$el.textContent).toBe('bar') + }).then(done) + }) - it('should work for reactive value', done => { - const State = Symbol(); - let obj; + it('should work for reactive value', (done) => { + const State = Symbol() + let obj const app = new Vue({ template: ``, setup() { - provide(State, reactive({ msg: 'foo' })); + provide(State, reactive({ msg: 'foo' })) }, components: { child: { template: `
{{ state.msg }}
`, setup() { - obj = inject(State); + obj = inject(State) return { state: obj, - }; + } }, }, }, - }).$mount(); - expect(obj.msg).toBe('foo'); - app.$children[0].state.msg = 'bar'; + }).$mount() + expect(obj.msg).toBe('foo') + app.$children[0].state.msg = 'bar' waitForUpdate(() => { - expect(app.$el.textContent).toBe('bar'); - }).then(done); - }); + expect(app.$el.textContent).toBe('bar') + }).then(done) + }) it('should work when combined with 2.x provide option', () => { - const State = Symbol(); - let obj1; - let obj2; + const State = Symbol() + let obj1 + let obj2 new Vue({ template: ``, setup() { - provide(State, { msg: 'foo' }); + provide(State, { msg: 'foo' }) }, provide: { X: { msg: 'bar' }, @@ -135,14 +135,14 @@ describe('Hooks provide/inject', () => { components: { child: { setup() { - obj1 = inject(State); - obj2 = inject('X'); + obj1 = inject(State) + obj2 = inject('X') }, template: `
`, }, }, - }).$mount(); - expect(obj1.msg).toBe('foo'); - expect(obj2.msg).toBe('bar'); - }); -}); + }).$mount() + expect(obj1.msg).toBe('foo') + expect(obj2.msg).toBe('bar') + }) +}) diff --git a/test/apis/lifecycle.spec.js b/test/apis/lifecycle.spec.js index 05c24a14..7f291279 100644 --- a/test/apis/lifecycle.spec.js +++ b/test/apis/lifecycle.spec.js @@ -1,4 +1,4 @@ -const Vue = require('vue/dist/vue.common.js'); +const Vue = require('vue/dist/vue.common.js') const { onBeforeMount, onMounted, @@ -8,155 +8,155 @@ const { onUnmounted, onErrorCaptured, getCurrentInstance, -} = require('../../src'); +} = require('../../src') describe('Hooks lifecycle', () => { describe('beforeMount', () => { it('should not have mounted', () => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ render() {}, setup(_, { _vm }) { onBeforeMount(() => { - expect(_vm._isMounted).toBe(false); - expect(_vm.$el).toBeUndefined(); // due to empty mount - expect(_vm._vnode).toBeNull(); - expect(_vm._watcher).toBeNull(); - spy(); - }); + expect(_vm._isMounted).toBe(false) + expect(_vm.$el).toBeUndefined() // due to empty mount + expect(_vm._vnode).toBeNull() + expect(_vm._watcher).toBeNull() + spy() + }) }, - }); - expect(spy).not.toHaveBeenCalled(); - vm.$mount(); - expect(spy).toHaveBeenCalled(); - }); - }); + }) + expect(spy).not.toHaveBeenCalled() + vm.$mount() + expect(spy).toHaveBeenCalled() + }) + }) describe('mounted', () => { it('should have mounted', () => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ template: '
', setup(_, { _vm }) { onMounted(() => { - expect(_vm._isMounted).toBe(true); - expect(_vm.$el.tagName).toBe('DIV'); - expect(_vm._vnode.tag).toBe('div'); - spy(); - }); + expect(_vm._isMounted).toBe(true) + expect(_vm.$el.tagName).toBe('DIV') + expect(_vm._vnode.tag).toBe('div') + spy() + }) }, - }); - expect(spy).not.toHaveBeenCalled(); - vm.$mount(); - expect(spy).toHaveBeenCalled(); - }); + }) + expect(spy).not.toHaveBeenCalled() + vm.$mount() + expect(spy).toHaveBeenCalled() + }) it('should call for manually mounted instance with parent', () => { - const spy = jest.fn(); - const parent = new Vue(); - expect(spy).not.toHaveBeenCalled(); + const spy = jest.fn() + const parent = new Vue() + expect(spy).not.toHaveBeenCalled() new Vue({ parent, template: '
', setup() { onMounted(() => { - spy(); - }); + spy() + }) }, - }).$mount(); - expect(spy).toHaveBeenCalled(); - }); + }).$mount() + expect(spy).toHaveBeenCalled() + }) it('should mount child parent in correct order', () => { - const calls = []; + const calls = [] new Vue({ template: '
', setup() { onMounted(() => { - calls.push('parent'); - }); + calls.push('parent') + }) }, components: { test: { template: '', setup(_, { _vm }) { onMounted(() => { - expect(_vm.$el.parentNode).toBeTruthy(); - calls.push('child'); - }); + expect(_vm.$el.parentNode).toBeTruthy() + calls.push('child') + }) }, components: { nested: { template: '
', setup(_, { _vm }) { onMounted(() => { - expect(_vm.$el.parentNode).toBeTruthy(); - calls.push('nested'); - }); + expect(_vm.$el.parentNode).toBeTruthy() + calls.push('nested') + }) }, }, }, }, }, - }).$mount(); - expect(calls).toEqual(['nested', 'child', 'parent']); - }); + }).$mount() + expect(calls).toEqual(['nested', 'child', 'parent']) + }) it('getCurrentInstance should be available', () => { - const parent = new Vue(); - let instance; + const parent = new Vue() + let instance new Vue({ parent, template: '
', setup() { onMounted(() => { - instance = getCurrentInstance(); - }); + instance = getCurrentInstance() + }) }, - }).$mount(); - expect(instance).toBeDefined(); - }); + }).$mount() + expect(instance).toBeDefined() + }) it('getCurrentInstance should not be available on promised hook', () => { - const parent = new Vue(); - let instance; - let promisedInstance; + const parent = new Vue() + let instance + let promisedInstance new Vue({ parent, template: '
', setup() { onMounted(async () => { - instance = getCurrentInstance(); - await Promise.resolve(); - promisedInstance = getCurrentInstance(); - }); + instance = getCurrentInstance() + await Promise.resolve() + promisedInstance = getCurrentInstance() + }) }, - }).$mount(); - expect(instance).toBeDefined(); - expect(promisedInstance).not.toBeDefined(); - }); - }); + }).$mount() + expect(instance).toBeDefined() + expect(promisedInstance).not.toBeDefined() + }) + }) describe('beforeUpdate', () => { it('should be called before update', (done) => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ template: '
{{ msg }}
', data: { msg: 'foo' }, setup(_, { _vm }) { onBeforeUpdate(() => { - expect(_vm.$el.textContent).toBe('foo'); - spy(); - }); + expect(_vm.$el.textContent).toBe('foo') + spy() + }) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.msg = 'bar'; - expect(spy).not.toHaveBeenCalled(); // should be async + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.msg = 'bar' + expect(spy).not.toHaveBeenCalled() // should be async waitForUpdate(() => { - expect(spy).toHaveBeenCalled(); - }).then(done); - }); + expect(spy).toHaveBeenCalled() + }).then(done) + }) it('should be called before render and allow mutating state', (done) => { const vm = new Vue({ @@ -164,29 +164,29 @@ describe('Hooks lifecycle', () => { data: { msg: 'foo' }, setup(_, { _vm }) { onBeforeUpdate(() => { - _vm.msg += '!'; - }); + _vm.msg += '!' + }) }, - }).$mount(); - expect(vm.$el.textContent).toBe('foo'); - vm.msg = 'bar'; + }).$mount() + expect(vm.$el.textContent).toBe('foo') + vm.msg = 'bar' waitForUpdate(() => { - expect(vm.$el.textContent).toBe('bar!'); - }).then(done); - }); + expect(vm.$el.textContent).toBe('bar!') + }).then(done) + }) it('should not be called after destroy', (done) => { - const beforeUpdate = jest.fn(); - const destroyed = jest.fn(); + const beforeUpdate = jest.fn() + const destroyed = jest.fn() Vue.component('todo', { template: '
{{todo.done}}
', props: ['todo'], setup() { - onBeforeUpdate(beforeUpdate); - onUnmounted(destroyed); + onBeforeUpdate(beforeUpdate) + onUnmounted(destroyed) }, - }); + }) const vm = new Vue({ template: ` @@ -197,46 +197,46 @@ describe('Hooks lifecycle', () => { data() { return { todos: [{ id: 1, done: false }], - }; + } }, computed: { pendingTodos() { - return this.todos.filter((t) => !t.done); + return this.todos.filter((t) => !t.done) }, }, - }).$mount(); + }).$mount() - vm.todos[0].done = true; + vm.todos[0].done = true waitForUpdate(() => { - expect(destroyed).toHaveBeenCalled(); - expect(beforeUpdate).not.toHaveBeenCalled(); - }).then(done); - }); - }); + expect(destroyed).toHaveBeenCalled() + expect(beforeUpdate).not.toHaveBeenCalled() + }).then(done) + }) + }) describe('updated', () => { it('should be called after update', (done) => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ template: '
{{ msg }}
', data: { msg: 'foo' }, setup(_, { _vm }) { onUpdated(() => { - expect(_vm.$el.textContent).toBe('bar'); - spy(); - }); + expect(_vm.$el.textContent).toBe('bar') + spy() + }) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.msg = 'bar'; - expect(spy).not.toHaveBeenCalled(); // should be async + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.msg = 'bar' + expect(spy).not.toHaveBeenCalled() // should be async waitForUpdate(() => { - expect(spy).toHaveBeenCalled(); - }).then(done); - }); + expect(spy).toHaveBeenCalled() + }).then(done) + }) it('should be called after children are updated', (done) => { - const calls = []; + const calls = [] const vm = new Vue({ template: '
{{ msg }}
', data: { msg: 'foo' }, @@ -245,40 +245,40 @@ describe('Hooks lifecycle', () => { template: `
`, setup(_, { _vm }) { onUpdated(() => { - expect(_vm.$el.textContent).toBe('bar'); - calls.push('child'); - }); + expect(_vm.$el.textContent).toBe('bar') + calls.push('child') + }) }, }, }, setup(_, { _vm }) { onUpdated(() => { - expect(_vm.$el.textContent).toBe('bar'); - calls.push('parent'); - }); + expect(_vm.$el.textContent).toBe('bar') + calls.push('parent') + }) }, - }).$mount(); + }).$mount() - expect(calls).toEqual([]); - vm.msg = 'bar'; - expect(calls).toEqual([]); + expect(calls).toEqual([]) + vm.msg = 'bar' + expect(calls).toEqual([]) waitForUpdate(() => { - expect(calls).toEqual(['child', 'parent']); - }).then(done); - }); + expect(calls).toEqual(['child', 'parent']) + }).then(done) + }) it('should not be called after destroy', (done) => { - const updated = jest.fn(); - const destroyed = jest.fn(); + const updated = jest.fn() + const destroyed = jest.fn() Vue.component('todo', { template: '
{{todo.done}}
', props: ['todo'], setup() { - onUpdated(updated); - onUnmounted(destroyed); + onUpdated(updated) + onUnmounted(destroyed) }, - }); + }) const vm = new Vue({ template: ` @@ -289,100 +289,100 @@ describe('Hooks lifecycle', () => { data() { return { todos: [{ id: 1, done: false }], - }; + } }, computed: { pendingTodos() { - return this.todos.filter((t) => !t.done); + return this.todos.filter((t) => !t.done) }, }, - }).$mount(); + }).$mount() - vm.todos[0].done = true; + vm.todos[0].done = true waitForUpdate(() => { - expect(destroyed).toHaveBeenCalled(); - expect(updated).not.toHaveBeenCalled(); - }).then(done); - }); - }); + expect(destroyed).toHaveBeenCalled() + expect(updated).not.toHaveBeenCalled() + }).then(done) + }) + }) describe('beforeUnmount', () => { it('should be called before destroy', () => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ render() {}, setup(_, { _vm }) { onBeforeUnmount(() => { - expect(_vm._isBeingDestroyed).toBe(false); - expect(_vm._isDestroyed).toBe(false); - spy(); - }); + expect(_vm._isBeingDestroyed).toBe(false) + expect(_vm._isDestroyed).toBe(false) + spy() + }) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.$destroy(); - vm.$destroy(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls.length).toBe(1); - }); - }); + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.$destroy() + vm.$destroy() + expect(spy).toHaveBeenCalled() + expect(spy.mock.calls.length).toBe(1) + }) + }) describe('unmounted', () => { it('should be called after destroy', () => { - const spy = jest.fn(); + const spy = jest.fn() const vm = new Vue({ render() {}, setup(_, { _vm }) { onUnmounted(() => { - expect(_vm._isBeingDestroyed).toBe(true); - expect(_vm._isDestroyed).toBe(true); - spy(); - }); + expect(_vm._isBeingDestroyed).toBe(true) + expect(_vm._isDestroyed).toBe(true) + spy() + }) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.$destroy(); - vm.$destroy(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls.length).toBe(1); - }); - }); + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.$destroy() + vm.$destroy() + expect(spy).toHaveBeenCalled() + expect(spy.mock.calls.length).toBe(1) + }) + }) describe('errorCaptured', () => { - let globalSpy; + let globalSpy beforeEach(() => { - globalSpy = Vue.config.errorHandler = jest.fn(); - }); + globalSpy = Vue.config.errorHandler = jest.fn() + }) afterEach(() => { - Vue.config.errorHandler = null; - }); + Vue.config.errorHandler = null + }) it('should capture error from child component', () => { - const spy = jest.fn(); + const spy = jest.fn() - let child; - let err; + let child + let err const Child = { setup(_, { _vm }) { - child = _vm; - err = new Error('child'); - throw err; + child = _vm + err = new Error('child') + throw err }, render() {}, - }; + } new Vue({ setup() { - onErrorCaptured(spy); + onErrorCaptured(spy) }, render: (h) => h(Child), - }).$mount(); + }).$mount() - expect(spy).toHaveBeenCalledWith(err, child, 'data()'); + expect(spy).toHaveBeenCalledWith(err, child, 'data()') // should propagate by default - expect(globalSpy).toHaveBeenCalledWith(err, child, 'data()'); - }); - }); -}); + expect(globalSpy).toHaveBeenCalledWith(err, child, 'data()') + }) + }) +}) diff --git a/test/apis/state.spec.js b/test/apis/state.spec.js index 756e0691..6bca885f 100644 --- a/test/apis/state.spec.js +++ b/test/apis/state.spec.js @@ -1,62 +1,70 @@ -const Vue = require('vue/dist/vue.common.js'); -const { reactive, ref, watch, set, toRefs, computed, unref } = require('../../src'); +const Vue = require('vue/dist/vue.common.js') +const { + reactive, + ref, + watch, + set, + toRefs, + computed, + unref, +} = require('../../src') describe('api/ref', () => { it('should work with array', () => { - let arr; + let arr new Vue({ setup() { - arr = ref([2]); - arr.value.push(3); - arr.value.unshift(1); + arr = ref([2]) + arr.value.push(3) + arr.value.unshift(1) }, - }); - expect(arr.value).toEqual([1, 2, 3]); - }); + }) + expect(arr.value).toEqual([1, 2, 3]) + }) it('should hold a value', () => { - const a = ref(1); - expect(a.value).toBe(1); - a.value = 2; - expect(a.value).toBe(2); - }); + const a = ref(1) + expect(a.value).toBe(1) + a.value = 2 + expect(a.value).toBe(2) + }) it('should be reactive', (done) => { - const a = ref(1); - let dummy; + const a = ref(1) + let dummy watch( a, () => { - dummy = a.value; + dummy = a.value }, { immediate: true } - ); - expect(dummy).toBe(1); - a.value = 2; + ) + expect(dummy).toBe(1) + a.value = 2 waitForUpdate(() => { - expect(dummy).toBe(2); - }).then(done); - }); + expect(dummy).toBe(2) + }).then(done) + }) it('should make nested properties reactive', (done) => { const a = ref({ count: 1, - }); - let dummy; + }) + let dummy watch( a, () => { - dummy = a.value.count; + dummy = a.value.count }, { deep: true, immediate: true } - ); - expect(dummy).toBe(1); - a.value.count = 2; + ) + expect(dummy).toBe(1) + a.value.count = 2 waitForUpdate(() => { - expect(dummy).toBe(2); - }).then(done); - }); -}); + expect(dummy).toBe(2) + }).then(done) + }) +}) describe('api/reactive', () => { it('should work', (done) => { @@ -66,322 +74,322 @@ describe('api/reactive', () => { state: reactive({ count: 0, }), - }; + } }, render(h) { - return h('div', [h('span', this.state.count)]); + return h('div', [h('span', this.state.count)]) }, - }).$mount(); + }).$mount() - expect(app.$el.querySelector('span').textContent).toBe('0'); - app.state.count++; + expect(app.$el.querySelector('span').textContent).toBe('0') + app.state.count++ waitForUpdate(() => { - expect(app.$el.querySelector('span').textContent).toBe('1'); - }).then(done); - }); + expect(app.$el.querySelector('span').textContent).toBe('1') + }).then(done) + }) it('should warn for non-object params', () => { - warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); - reactive(); + warn = jest.spyOn(global.console, 'error').mockImplementation(() => null) + reactive() expect(warn.mock.calls[0][0]).toMatch( '[Vue warn]: "reactive()" is called without provide an "object".' - ); - reactive(false); + ) + reactive(false) expect(warn.mock.calls[1][0]).toMatch( '[Vue warn]: "reactive()" is called without provide an "object".' - ); - expect(warn).toBeCalledTimes(2); - warn.mockRestore(); - }); -}); + ) + expect(warn).toBeCalledTimes(2) + warn.mockRestore() + }) +}) describe('api/toRefs', () => { it('should work', (done) => { const state = reactive({ foo: 1, bar: 2, - }); + }) - let dummy; + let dummy watch( () => state, () => { - dummy = state.foo; + dummy = state.foo }, { immediate: true } - ); - const stateAsRefs = toRefs(state); - expect(dummy).toBe(1); - expect(stateAsRefs.foo.value).toBe(1); - expect(stateAsRefs.bar.value).toBe(2); - state.foo++; + ) + const stateAsRefs = toRefs(state) + expect(dummy).toBe(1) + expect(stateAsRefs.foo.value).toBe(1) + expect(stateAsRefs.bar.value).toBe(2) + state.foo++ waitForUpdate(() => { - dummy = 2; - expect(stateAsRefs.foo.value).toBe(2); - stateAsRefs.foo.value++; + dummy = 2 + expect(stateAsRefs.foo.value).toBe(2) + stateAsRefs.foo.value++ }) .then(() => { - dummy = 3; - expect(state.foo).toBe(3); + dummy = 3 + expect(state.foo).toBe(3) }) - .then(done); - }); + .then(done) + }) it('should proxy plain object but not make it a reactive', () => { - warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); - const spy = jest.fn(); + warn = jest.spyOn(global.console, 'error').mockImplementation(() => null) + const spy = jest.fn() const state = { foo: 1, bar: 2, - }; + } - watch(() => state, spy, { flush: 'sync', lazy: true }); - const stateAsRefs = toRefs(state); + watch(() => state, spy, { flush: 'sync', lazy: true }) + const stateAsRefs = toRefs(state) expect(warn.mock.calls[0][0]).toMatch( '[Vue warn]: toRefs() expects a reactive object but received a plain one.' - ); + ) - expect(stateAsRefs.foo.value).toBe(1); - expect(stateAsRefs.bar.value).toBe(2); - state.foo++; - expect(stateAsRefs.foo.value).toBe(2); + expect(stateAsRefs.foo.value).toBe(1) + expect(stateAsRefs.bar.value).toBe(2) + state.foo++ + expect(stateAsRefs.foo.value).toBe(2) - stateAsRefs.foo.value++; - expect(state.foo).toBe(3); + stateAsRefs.foo.value++ + expect(state.foo).toBe(3) - expect(spy).not.toHaveBeenCalled(); - expect(warn).toBeCalledTimes(1); - warn.mockRestore(); - }); -}); + expect(spy).not.toHaveBeenCalled() + expect(warn).toBeCalledTimes(1) + warn.mockRestore() + }) +}) describe('unwrapping', () => { it('should work', () => { const obj = reactive({ a: ref(0), - }); - const objWrapper = ref(obj); - let dummy; + }) + const objWrapper = ref(obj) + let dummy watch( () => obj, () => { - dummy = obj.a; + dummy = obj.a }, { deep: true, flush: 'sync', immediate: true } - ); - expect(dummy).toBe(0); - expect(obj.a).toBe(0); - expect(objWrapper.value.a).toBe(0); - obj.a++; - expect(dummy).toBe(1); - objWrapper.value.a++; - expect(dummy).toBe(2); - }); + ) + expect(dummy).toBe(0) + expect(obj.a).toBe(0) + expect(objWrapper.value.a).toBe(0) + obj.a++ + expect(dummy).toBe(1) + objWrapper.value.a++ + expect(dummy).toBe(2) + }) it('should not unwrap a ref', () => { - const a = ref(0); - const b = ref(a); - expect(a.value).toBe(0); - expect(b).toBe(a); - }); + const a = ref(0) + const b = ref(a) + expect(a.value).toBe(0) + expect(b).toBe(a) + }) it('should not unwrap a ref when re-assign', () => { - const a = ref('foo'); - expect(a.value).toBe('foo'); - const b = ref(); - a.value = b; - expect(a.value).toBe(b); - }); + const a = ref('foo') + expect(a.value).toBe('foo') + const b = ref() + a.value = b + expect(a.value).toBe(b) + }) it('should unwrap ref in a nested object', () => { - const a = ref(0); + const a = ref(0) const b = ref({ count: a, - }); - expect(b.value.count).toBe(0); - a.value++; - expect(b.value.count).toBe(1); - }); + }) + expect(b.value.count).toBe(0) + a.value++ + expect(b.value.count).toBe(1) + }) it('should unwrap when re-assign', () => { - const a = ref(); - const b = ref(a); - expect(b.value).toBe(a.value); - const c = ref(0); + const a = ref() + const b = ref(a) + expect(b.value).toBe(a.value) + const c = ref(0) b.value = { count: c, - }; - expect(b.value.count).toBe(0); - c.value++; - expect(b.value.count).toBe(1); - }); + } + expect(b.value.count).toBe(0) + c.value++ + expect(b.value.count).toBe(1) + }) it('should keep reactivity(same ref)', () => { - const a = ref(1); + const a = ref(1) const obj = reactive({ a, b: { c: a, }, - }); - let dummy1; - let dummy2; + }) + let dummy1 + let dummy2 watch( () => obj, () => { - dummy1 = obj.a; - dummy2 = obj.b.c; + dummy1 = obj.a + dummy2 = obj.b.c }, { deep: true, flush: 'sync', immediate: true } - ); - expect(dummy1).toBe(1); - expect(dummy2).toBe(1); - a.value++; - expect(dummy1).toBe(2); - expect(dummy2).toBe(2); - obj.a++; - expect(dummy1).toBe(3); - expect(dummy2).toBe(3); - }); + ) + expect(dummy1).toBe(1) + expect(dummy2).toBe(1) + a.value++ + expect(dummy1).toBe(2) + expect(dummy2).toBe(2) + obj.a++ + expect(dummy1).toBe(3) + expect(dummy2).toBe(3) + }) it('should keep reactivity(different ref)', () => { - const count = ref(1); - const count1 = ref(1); + const count = ref(1) + const count1 = ref(1) const obj = reactive({ a: count, b: { c: count1, }, - }); + }) - let dummy1; - let dummy2; + let dummy1 + let dummy2 watch( () => obj, () => { - dummy1 = obj.a; - dummy2 = obj.b.c; + dummy1 = obj.a + dummy2 = obj.b.c }, { deep: true, flush: 'sync', immediate: true } - ); - expect(dummy1).toBe(1); - expect(dummy2).toBe(1); - expect(obj.a).toBe(1); - expect(obj.b.c).toBe(1); - obj.a++; - expect(dummy1).toBe(2); - expect(dummy2).toBe(1); - expect(count.value).toBe(2); - expect(count1.value).toBe(1); - count.value++; - expect(dummy1).toBe(3); - expect(count.value).toBe(3); - count1.value++; - expect(dummy2).toBe(2); - expect(count1.value).toBe(2); - }); + ) + expect(dummy1).toBe(1) + expect(dummy2).toBe(1) + expect(obj.a).toBe(1) + expect(obj.b.c).toBe(1) + obj.a++ + expect(dummy1).toBe(2) + expect(dummy2).toBe(1) + expect(count.value).toBe(2) + expect(count1.value).toBe(1) + count.value++ + expect(dummy1).toBe(3) + expect(count.value).toBe(3) + count1.value++ + expect(dummy2).toBe(2) + expect(count1.value).toBe(2) + }) it('should keep reactivity(new property of object)', () => { - const count = ref(1); + const count = ref(1) const obj = reactive({ a: {}, b: [], - }); - let dummy; + }) + let dummy watch( () => obj, () => { - dummy = obj.a.foo; + dummy = obj.a.foo }, { deep: true, flush: 'sync' } - ); - expect(dummy).toBe(undefined); - set(obj.a, 'foo', count); - expect(dummy).toBe(1); - count.value++; - expect(dummy).toBe(2); - obj.a.foo++; - expect(dummy).toBe(3); - }); + ) + expect(dummy).toBe(undefined) + set(obj.a, 'foo', count) + expect(dummy).toBe(1) + count.value++ + expect(dummy).toBe(2) + obj.a.foo++ + expect(dummy).toBe(3) + }) it('ref should be replaced)', () => { - const bRef = ref(1); + const bRef = ref(1) const obj = reactive({ a: { b: bRef, }, - }); + }) - let dummy; + let dummy watch( () => obj, () => { - dummy = obj.a.b; + dummy = obj.a.b }, { deep: true, lazy: true, flush: 'sync' } - ); - expect(dummy).toBeUndefined(); - const replacedRef = ref(2); - obj.a.b = replacedRef; - expect(dummy).toBe(2); - obj.a.b++; - expect(replacedRef.value).toBe(3); - expect(dummy).toBe(3); + ) + expect(dummy).toBeUndefined() + const replacedRef = ref(2) + obj.a.b = replacedRef + expect(dummy).toBe(2) + obj.a.b++ + expect(replacedRef.value).toBe(3) + expect(dummy).toBe(3) // bRef.value should not change - expect(bRef.value).toBe(1); - }); + expect(bRef.value).toBe(1) + }) it('should not unwrap ref in Array index', () => { - const a = ref(0); + const a = ref(0) const state = reactive({ list: [a], - }); + }) - expect(state.list[0]).toBe(a); - expect(state.list[0].value).toBe(0); - }); + expect(state.list[0]).toBe(a) + expect(state.list[0].value).toBe(0) + }) it('should unrwap ref', () => { - expect(unref(0)).toBe(0); - expect(unref(ref(0))).toBe(0); - expect(unref({ value: 1 })).toStrictEqual({ value: 1 }); - }); + expect(unref(0)).toBe(0) + expect(unref(ref(0))).toBe(0) + expect(unref({ value: 1 })).toStrictEqual({ value: 1 }) + }) it('should now unwrap plain object when using set at Array', () => { const state = reactive({ list: [], - }); + }) - let dummy; + let dummy watch( () => state.list, () => { - dummy = state.list[0].count; + dummy = state.list[0].count }, { lazy: true, flush: 'sync' } - ); - expect(dummy).toBeUndefined(); - const a = ref(0); + ) + expect(dummy).toBeUndefined() + const a = ref(0) set(state.list, 0, { count: a, - }); - expect(dummy).toBe(a); - }); + }) + expect(dummy).toBe(a) + }) it('should not call the computed property until accessing it', () => { - const spy = jest.fn(); + const spy = jest.fn() const state = reactive({ count: 1, double: computed(() => { - spy(); - return state.count * 2; + spy() + return state.count * 2 }), - }); + }) - expect(spy).not.toHaveBeenCalled(); - expect(state.double).toBe(2); - expect(spy).toHaveBeenCalled(); - }); -}); + expect(spy).not.toHaveBeenCalled() + expect(state.double).toBe(2) + expect(spy).toHaveBeenCalled() + }) +}) diff --git a/test/apis/watch.spec.js b/test/apis/watch.spec.js index fe2422d7..feab0997 100644 --- a/test/apis/watch.spec.js +++ b/test/apis/watch.spec.js @@ -1,325 +1,325 @@ -const Vue = require('vue/dist/vue.common.js'); -const { ref, reactive, watch, watchEffect } = require('../../src'); +const Vue = require('vue/dist/vue.common.js') +const { ref, reactive, watch, watchEffect } = require('../../src') describe('api/watch', () => { - const anyFn = expect.any(Function); - let spy; + const anyFn = expect.any(Function) + let spy beforeEach(() => { - spy = jest.fn(); - }); + spy = jest.fn() + }) afterEach(() => { - spy.mockReset(); - }); + spy.mockReset() + }) it('should work', (done) => { - const onCleanupSpy = jest.fn(); + const onCleanupSpy = jest.fn() const vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) watch( a, (n, o, _onCleanup) => { - spy(n, o, _onCleanup); - _onCleanup(onCleanupSpy); + spy(n, o, _onCleanup) + _onCleanup(onCleanupSpy) }, { immediate: true } - ); + ) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1, undefined, anyFn); - expect(onCleanupSpy).toHaveBeenCalledTimes(0); - vm.a = 2; - vm.a = 3; - expect(spy).toBeCalledTimes(1); + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1, undefined, anyFn) + expect(onCleanupSpy).toHaveBeenCalledTimes(0) + vm.a = 2 + vm.a = 3 + expect(spy).toBeCalledTimes(1) waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(3, 1, anyFn); - expect(onCleanupSpy).toHaveBeenCalledTimes(1); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(3, 1, anyFn) + expect(onCleanupSpy).toHaveBeenCalledTimes(1) - vm.$destroy(); + vm.$destroy() }) .then(() => { - expect(onCleanupSpy).toHaveBeenCalledTimes(2); + expect(onCleanupSpy).toHaveBeenCalledTimes(2) }) - .then(done); - }); + .then(done) + }) it('basic usage(value wrapper)', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - watch(a, (n, o) => spy(n, o), { flush: 'pre', immediate: true }); + const a = ref(1) + watch(a, (n, o) => spy(n, o), { flush: 'pre', immediate: true }) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1, undefined); - vm.a = 2; - expect(spy).toBeCalledTimes(1); + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1, undefined) + vm.a = 2 + expect(spy).toBeCalledTimes(1) waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('basic usage(function)', (done) => { const vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) watch( () => a.value, (n, o) => spy(n, o), { immediate: true } - ); + ) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1, undefined); - vm.a = 2; - expect(spy).toBeCalledTimes(1); + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1, undefined) + vm.a = 2 + expect(spy).toBeCalledTimes(1) waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('multiple cbs (after option merge)', (done) => { - const spy1 = jest.fn(); - const a = ref(1); + const spy1 = jest.fn() + const a = ref(1) const Test = Vue.extend({ setup() { - watch(a, (n, o) => spy1(n, o)); + watch(a, (n, o) => spy1(n, o)) }, - }); + }) new Test({ setup() { - watch(a, (n, o) => spy(n, o)); + watch(a, (n, o) => spy(n, o)) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - a.value = 2; + }).$mount() + a.value = 2 waitForUpdate(() => { - expect(spy1).toHaveBeenLastCalledWith(2, 1); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy1).toHaveBeenLastCalledWith(2, 1) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('with option: lazy', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - watch(a, (n, o) => spy(n, o), { lazy: true }); + const a = ref(1) + watch(a, (n, o) => spy(n, o), { lazy: true }) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.a = 2; + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.a = 2 waitForUpdate(() => { - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('with option: deep', (done) => { const vm = new Vue({ setup() { - const a = ref({ b: 1 }); - watch(a, (n, o) => spy(n, o), { lazy: true, deep: true }); + const a = ref({ b: 1 }) + watch(a, (n, o) => spy(n, o), { lazy: true, deep: true }) return { a, - }; + } }, template: `
{{a}}
`, - }).$mount(); - const oldA = vm.a; - expect(spy).not.toHaveBeenCalled(); - vm.a.b = 2; - expect(spy).not.toHaveBeenCalled(); + }).$mount() + const oldA = vm.a + expect(spy).not.toHaveBeenCalled() + vm.a.b = 2 + expect(spy).not.toHaveBeenCalled() waitForUpdate(() => { - expect(spy).toHaveBeenLastCalledWith(vm.a, vm.a); - vm.a = { b: 3 }; + expect(spy).toHaveBeenLastCalledWith(vm.a, vm.a) + vm.a = { b: 3 } }) .then(() => { - expect(spy).toHaveBeenLastCalledWith(vm.a, oldA); + expect(spy).toHaveBeenLastCalledWith(vm.a, oldA) }) - .then(done); - }); + .then(done) + }) it('should flush after render (immediate=false)', (done) => { - let rerenderedText; + let rerenderedText const vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) watch( a, (newVal, oldVal) => { - spy(newVal, oldVal); - rerenderedText = vm.$el.textContent; + spy(newVal, oldVal) + rerenderedText = vm.$el.textContent }, { lazy: true } - ); + ) return { a, - }; + } }, render(h) { - return h('div', this.a); + return h('div', this.a) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.a = 2; + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.a = 2 waitForUpdate(() => { - expect(rerenderedText).toBe('2'); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(rerenderedText).toBe('2') + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('should flush after render (immediate=true)', (done) => { - let rerenderedText; + let rerenderedText var vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) watch( a, (newVal, oldVal) => { - spy(newVal, oldVal); + spy(newVal, oldVal) if (vm) { - rerenderedText = vm.$el.textContent; + rerenderedText = vm.$el.textContent } }, { immediate: true } - ); + ) return { a, - }; + } }, render(h) { - return h('div', this.a); + return h('div', this.a) }, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1, undefined); - vm.a = 2; + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1, undefined) + vm.a = 2 waitForUpdate(() => { - expect(rerenderedText).toBe('2'); - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(rerenderedText).toBe('2') + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('should flush before render', (done) => { const vm = new Vue({ setup() { - const a = ref(1); + const a = ref(1) watch( a, (newVal, oldVal) => { - spy(newVal, oldVal); - expect(vm.$el.textContent).toBe('1'); + spy(newVal, oldVal) + expect(vm.$el.textContent).toBe('1') }, { lazy: true, flush: 'pre' } - ); + ) return { a, - }; + } }, render(h) { - return h('div', this.a); + return h('div', this.a) }, - }).$mount(); - vm.a = 2; + }).$mount() + vm.a = 2 waitForUpdate(() => { - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('should flush synchronously', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - watch(a, (n, o) => spy(n, o), { lazy: true, flush: 'sync' }); + const a = ref(1) + watch(a, (n, o) => spy(n, o), { lazy: true, flush: 'sync' }) return { a, - }; + } }, render(h) { - return h('div', this.a); + return h('div', this.a) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm.a = 2; - expect(spy).toHaveBeenLastCalledWith(2, 1); - vm.a = 3; - expect(spy).toHaveBeenLastCalledWith(3, 2); + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm.a = 2 + expect(spy).toHaveBeenLastCalledWith(2, 1) + vm.a = 3 + expect(spy).toHaveBeenLastCalledWith(3, 2) waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - }).then(done); - }); + expect(spy).toBeCalledTimes(2) + }).then(done) + }) it('should support watching unicode paths', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - watch(a, (n, o) => spy(n, o), { lazy: true }); + const a = ref(1) + watch(a, (n, o) => spy(n, o), { lazy: true }) return { 数据: a, - }; + } }, render(h) { - return h('div', this['数据']); + return h('div', this['数据']) }, - }).$mount(); - expect(spy).not.toHaveBeenCalled(); - vm['数据'] = 2; - expect(spy).not.toHaveBeenCalled(); + }).$mount() + expect(spy).not.toHaveBeenCalled() + vm['数据'] = 2 + expect(spy).not.toHaveBeenCalled() waitForUpdate(() => { - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('should allow to be triggered in setup', () => { new Vue({ setup() { - const count = ref(0); - watch(count, (n, o) => spy(n, o), { flush: 'sync', immediate: true }); - count.value++; + const count = ref(0) + watch(count, (n, o) => spy(n, o), { flush: 'sync', immediate: true }) + count.value++ }, - }); - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenNthCalledWith(1, 0, undefined); - expect(spy).toHaveBeenNthCalledWith(2, 1, 0); - }); + }) + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenNthCalledWith(1, 0, undefined) + expect(spy).toHaveBeenNthCalledWith(2, 1, 0) + }) it('should run in a expected order', (done) => { - const result = []; + const result = [] var vm = new Vue({ setup() { - const x = ref(0); + const x = ref(0) // prettier-ignore watchEffect(() => { void x.value; result.push('sync effect'); }, { flush: 'sync' }); @@ -336,15 +336,15 @@ describe('api/watch', () => { watch(x, () => { result.push('post callback') }, { flush: 'post', immediate: true }) const inc = () => { - result.push('before inc'); - x.value++; - result.push('after inc'); - }; + result.push('before inc') + x.value++ + result.push('after inc') + } - return { x, inc }; + return { x, inc } }, template: `
{{x}}
`, - }).$mount(); + }).$mount() expect(result).toEqual([ 'sync effect', 'pre effect', @@ -352,14 +352,14 @@ describe('api/watch', () => { 'sync callback', 'pre callback', 'post callback', - ]); - result.length = 0; + ]) + result.length = 0 waitForUpdate(() => { - expect(result).toEqual([]); - result.length = 0; + expect(result).toEqual([]) + result.length = 0 - vm.inc(); + vm.inc() }) .then(() => { expect(result).toEqual([ @@ -371,373 +371,378 @@ describe('api/watch', () => { 'pre callback', 'post effect', 'post callback', - ]); + ]) }) - .then(done); - }); + .then(done) + }) describe('simple effect', () => { it('should work', (done) => { - let onCleanup; - const onCleanupSpy = jest.fn(); + let onCleanup + const onCleanupSpy = jest.fn() const vm = new Vue({ setup() { - const count = ref(0); + const count = ref(0) watchEffect((_onCleanup) => { - onCleanup = _onCleanup; - _onCleanup(onCleanupSpy); - spy(count.value); - }); + onCleanup = _onCleanup + _onCleanup(onCleanupSpy) + spy(count.value) + }) return { count, - }; + } }, render(h) { - return h('div', this.count); + return h('div', this.count) }, - }).$mount(); - expect(spy).toHaveBeenCalled(); + }).$mount() + expect(spy).toHaveBeenCalled() waitForUpdate(() => { - expect(onCleanup).toEqual(anyFn); - expect(onCleanupSpy).toHaveBeenCalledTimes(0); - expect(spy).toHaveBeenLastCalledWith(0); - vm.count++; + expect(onCleanup).toEqual(anyFn) + expect(onCleanupSpy).toHaveBeenCalledTimes(0) + expect(spy).toHaveBeenLastCalledWith(0) + vm.count++ }) .then(() => { - expect(spy).toHaveBeenLastCalledWith(1); - expect(onCleanupSpy).toHaveBeenCalledTimes(1); - vm.$destroy(); + expect(spy).toHaveBeenLastCalledWith(1) + expect(onCleanupSpy).toHaveBeenCalledTimes(1) + vm.$destroy() }) .then(() => { - expect(onCleanupSpy).toHaveBeenCalledTimes(2); + expect(onCleanupSpy).toHaveBeenCalledTimes(2) }) - .then(done); - }); + .then(done) + }) it('sync=true', () => { const vm = new Vue({ setup() { - const count = ref(0); + const count = ref(0) watchEffect( () => { - spy(count.value); + spy(count.value) }, { flush: 'sync', } - ); + ) return { count, - }; + } }, - }); - expect(spy).toHaveBeenLastCalledWith(0); - vm.count++; - expect(spy).toHaveBeenLastCalledWith(1); - }); - }); + }) + expect(spy).toHaveBeenLastCalledWith(0) + vm.count++ + expect(spy).toHaveBeenLastCalledWith(1) + }) + }) describe('Multiple sources', () => { - let obj1, obj2; + let obj1, obj2 it('do not store the intermediate state', (done) => { new Vue({ setup() { - obj1 = reactive({ a: 1 }); - obj2 = reactive({ a: 2 }); - watch([() => obj1.a, () => obj2.a], (n, o) => spy(n, o), { immediate: true }); + obj1 = reactive({ a: 1 }) + obj2 = reactive({ a: 2 }) + watch([() => obj1.a, () => obj2.a], (n, o) => spy(n, o), { + immediate: true, + }) return { obj1, obj2, - }; + } }, template: `
{{obj1.a}} {{obj2.a}}
`, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith([1, 2], undefined); - obj1.a = 2; - obj2.a = 3; - - obj1.a = 3; - obj2.a = 4; + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith([1, 2], undefined) + obj1.a = 2 + obj2.a = 3 + + obj1.a = 3 + obj2.a = 4 waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith([3, 4], [1, 2]); - obj2.a = 5; - obj2.a = 6; + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith([3, 4], [1, 2]) + obj2.a = 5 + obj2.a = 6 }) .then(() => { - expect(spy).toBeCalledTimes(3); - expect(spy).toHaveBeenLastCalledWith([3, 6], [3, 4]); + expect(spy).toBeCalledTimes(3) + expect(spy).toHaveBeenLastCalledWith([3, 6], [3, 4]) }) - .then(done); - }); + .then(done) + }) it('basic usage(immediate=true, flush=none-sync)', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - const b = ref(1); - watch([a, b], (n, o) => spy(n, o), { flush: 'post', immediate: true }); + const a = ref(1) + const b = ref(1) + watch([a, b], (n, o) => spy(n, o), { flush: 'post', immediate: true }) return { a, b, - }; + } }, template: `
{{a}} {{b}}
`, - }).$mount(); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith([1, 1], undefined); - vm.a = 2; - expect(spy).toBeCalledTimes(1); + }).$mount() + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith([1, 1], undefined) + vm.a = 2 + expect(spy).toBeCalledTimes(1) waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]); - vm.a = 3; - vm.b = 3; + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]) + vm.a = 3 + vm.b = 3 }) .then(() => { - expect(spy).toBeCalledTimes(3); - expect(spy).toHaveBeenLastCalledWith([3, 3], [2, 1]); + expect(spy).toBeCalledTimes(3) + expect(spy).toHaveBeenLastCalledWith([3, 3], [2, 1]) }) - .then(done); - }); + .then(done) + }) it('basic usage(immediate=false, flush=none-sync)', (done) => { const vm = new Vue({ setup() { - const a = ref(1); - const b = ref(1); - watch([a, b], (n, o) => spy(n, o), { immediate: false, flush: 'post' }); + const a = ref(1) + const b = ref(1) + watch([a, b], (n, o) => spy(n, o), { + immediate: false, + flush: 'post', + }) return { a, b, - }; + } }, template: `
{{a}} {{b}}
`, - }).$mount(); - vm.a = 2; - expect(spy).not.toHaveBeenCalled(); + }).$mount() + vm.a = 2 + expect(spy).not.toHaveBeenCalled() waitForUpdate(() => { - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]); - vm.a = 3; - vm.b = 3; + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]) + vm.a = 3 + vm.b = 3 }) .then(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith([3, 3], [2, 1]); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith([3, 3], [2, 1]) }) - .then(done); - }); + .then(done) + }) it('basic usage(immediate=true, flush=sync)', () => { const vm = new Vue({ setup() { - const a = ref(1); - const b = ref(1); - watch([a, b], (n, o) => spy(n, o), { immediate: true, flush: 'sync' }); + const a = ref(1) + const b = ref(1) + watch([a, b], (n, o) => spy(n, o), { immediate: true, flush: 'sync' }) return { a, b, - }; + } }, - }); - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith([1, 1], undefined); - vm.a = 2; - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]); - vm.a = 3; - vm.b = 3; - expect(spy.mock.calls.length).toBe(4); - expect(spy).toHaveBeenNthCalledWith(3, [3, 1], [2, 1]); - expect(spy).toHaveBeenNthCalledWith(4, [3, 3], [3, 1]); - }); + }) + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith([1, 1], undefined) + vm.a = 2 + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]) + vm.a = 3 + vm.b = 3 + expect(spy.mock.calls.length).toBe(4) + expect(spy).toHaveBeenNthCalledWith(3, [3, 1], [2, 1]) + expect(spy).toHaveBeenNthCalledWith(4, [3, 3], [3, 1]) + }) it('basic usage(immediate=false, flush=sync)', () => { const vm = new Vue({ setup() { - const a = ref(1); - const b = ref(1); - watch([a, b], (n, o) => spy(n, o), { lazy: true, flush: 'sync' }); + const a = ref(1) + const b = ref(1) + watch([a, b], (n, o) => spy(n, o), { lazy: true, flush: 'sync' }) return { a, b, - }; + } }, - }); - expect(spy).not.toHaveBeenCalled(); - vm.a = 2; - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]); - vm.a = 3; - vm.b = 3; - expect(spy).toBeCalledTimes(3); - expect(spy).toHaveBeenNthCalledWith(2, [3, 1], [2, 1]); - expect(spy).toHaveBeenNthCalledWith(3, [3, 3], [3, 1]); - }); - }); + }) + expect(spy).not.toHaveBeenCalled() + vm.a = 2 + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith([2, 1], [1, 1]) + vm.a = 3 + vm.b = 3 + expect(spy).toBeCalledTimes(3) + expect(spy).toHaveBeenNthCalledWith(2, [3, 1], [2, 1]) + expect(spy).toHaveBeenNthCalledWith(3, [3, 3], [3, 1]) + }) + }) describe('Out of setup', () => { it('should work', (done) => { - const obj = reactive({ a: 1 }); + const obj = reactive({ a: 1 }) watch( () => obj.a, (n, o) => spy(n, o), { immediate: true } - ); - expect(spy).toHaveBeenLastCalledWith(1, undefined); - obj.a = 2; + ) + expect(spy).toHaveBeenLastCalledWith(1, undefined) + obj.a = 2 waitForUpdate(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2, 1); - }).then(done); - }); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2, 1) + }).then(done) + }) it('simple effect', (done) => { - const obj = reactive({ a: 1 }); - watchEffect(() => spy(obj.a)); - expect(spy).toHaveBeenCalled(); + const obj = reactive({ a: 1 }) + watchEffect(() => spy(obj.a)) + expect(spy).toHaveBeenCalled() waitForUpdate(() => { - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1); - obj.a = 2; + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1) + obj.a = 2 }) .then(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2); + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2) }) - .then(done); - }); - }); + .then(done) + }) + }) describe('cleanup', () => { function getAsyncValue(val) { - let handle; - let resolve; + let handle + let resolve const p = new Promise((_resolve) => { - resolve = _resolve; + resolve = _resolve handle = setTimeout(() => { - resolve(val); - }, 0); - }); + resolve(val) + }, 0) + }) p.cancel = () => { - clearTimeout(handle); - resolve('canceled'); - }; - return p; + clearTimeout(handle) + resolve('canceled') + } + return p } it('work with effect', (done) => { - const id = ref(1); - const promises = []; + const id = ref(1) + const promises = [] watchEffect((onCleanup) => { - const val = getAsyncValue(id.value); - promises.push(val); + const val = getAsyncValue(id.value) + promises.push(val) onCleanup(() => { - val.cancel(); - }); - }); + val.cancel() + }) + }) waitForUpdate(() => { - id.value = 2; + id.value = 2 }) .thenWaitFor(async (next) => { - const values = await Promise.all(promises); - expect(values).toEqual(['canceled', 2]); - next(); + const values = await Promise.all(promises) + expect(values).toEqual(['canceled', 2]) + next() }) - .then(done); - }); + .then(done) + }) it('run cleanup when watch stops (effect)', (done) => { - const spy = jest.fn(); - const cleanup = jest.fn(); + const spy = jest.fn() + const cleanup = jest.fn() const stop = watchEffect((onCleanup) => { - spy(); - onCleanup(cleanup); - }); + spy() + onCleanup(cleanup) + }) waitForUpdate(() => { - expect(spy).toHaveBeenCalled(); - stop(); + expect(spy).toHaveBeenCalled() + stop() }) .then(() => { - expect(cleanup).toHaveBeenCalled(); + expect(cleanup).toHaveBeenCalled() }) - .then(done); - }); + .then(done) + }) it('run cleanup when watch stops', () => { - const id = ref(1); - const spy = jest.fn(); - const cleanup = jest.fn(); + const id = ref(1) + const spy = jest.fn() + const cleanup = jest.fn() const stop = watch( id, (value, oldValue, onCleanup) => { - spy(value); - onCleanup(cleanup); + spy(value) + onCleanup(cleanup) }, { immediate: true } - ); + ) - expect(spy).toHaveBeenCalledWith(1); - stop(); - expect(cleanup).toHaveBeenCalled(); - }); + expect(spy).toHaveBeenCalledWith(1) + stop() + expect(cleanup).toHaveBeenCalled() + }) it('should not collect reactive in onCleanup', (done) => { - const ref1 = ref(1); - const ref2 = ref(1); + const ref1 = ref(1) + const ref2 = ref(1) watchEffect((onCleanup) => { - spy(ref1.value); + spy(ref1.value) onCleanup(() => { - ref2.value = ref2.value + 1; - }); - }); + ref2.value = ref2.value + 1 + }) + }) waitForUpdate(() => { - expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(1); - ref1.value++; + expect(spy).toBeCalledTimes(1) + expect(spy).toHaveBeenLastCalledWith(1) + ref1.value++ }) .then(() => { - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenLastCalledWith(2); - ref2.value = 10; + expect(spy).toBeCalledTimes(2) + expect(spy).toHaveBeenLastCalledWith(2) + ref2.value = 10 }) .then(() => { - expect(spy).toBeCalledTimes(2); + expect(spy).toBeCalledTimes(2) }) - .then(done); - }); + .then(done) + }) it('work with callback ', (done) => { - const id = ref(1); - const promises = []; + const id = ref(1) + const promises = [] watch( id, (newVal, oldVal, onCleanup) => { - const val = getAsyncValue(newVal); - promises.push(val); + const val = getAsyncValue(newVal) + promises.push(val) onCleanup(() => { - val.cancel(); - }); + val.cancel() + }) }, { immediate: true } - ); - id.value = 2; + ) + id.value = 2 waitForUpdate() .thenWaitFor(async (next) => { - const values = await Promise.all(promises); - expect(values).toEqual(['canceled', 2]); - next(); + const values = await Promise.all(promises) + expect(values).toEqual(['canceled', 2]) + next() }) - .then(done); - }); - }); -}); + .then(done) + }) + }) +}) diff --git a/test/helpers/mockWarn.ts b/test/helpers/mockWarn.ts index 773674c2..63d23728 100644 --- a/test/helpers/mockWarn.ts +++ b/test/helpers/mockWarn.ts @@ -1,100 +1,105 @@ declare global { namespace jest { interface Matchers { - toHaveBeenWarned(): R; - toHaveBeenWarnedLast(): R; - toHaveBeenWarnedTimes(n: number): R; + toHaveBeenWarned(): R + toHaveBeenWarnedLast(): R + toHaveBeenWarnedTimes(n: number): R } } } -export const mockError = () => mockWarn(true); +export const mockError = () => mockWarn(true) export function mockWarn(asError = false) { expect.extend({ toHaveBeenWarned(received: string) { - asserted.add(received); - const passed = warn.mock.calls.some((args) => args[0].indexOf(received) > -1); + asserted.add(received) + 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 - '); + 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) { - asserted.add(received); - const passed = warn.mock.calls[warn.mock.calls.length - 1][0].indexOf(received) > -1; + asserted.add(received) + 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 - '); + 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) { - asserted.add(received); - let found = 0; + asserted.add(received) + let found = 0 warn.mock.calls.forEach((args) => { if (args[0].indexOf(received) > -1) { - found++; + found++ } - }); + }) if (found === n) { return { pass: true, - message: () => `expected "${received}" to have been warned ${n} times.`, - }; + message: () => + `expected "${received}" to have been warned ${n} times.`, + } } else { return { pass: false, - message: () => `expected "${received}" to have been warned ${n} times but got ${found}.`, - }; + message: () => + `expected "${received}" to have been warned ${n} times but got ${found}.`, + } } }, - }); + }) - let warn: jest.SpyInstance; - const asserted: Set = new Set(); + let warn: jest.SpyInstance + const asserted: Set = new Set() beforeEach(() => { - asserted.clear(); - warn = jest.spyOn(console, asError ? 'error' : 'warn'); - warn.mockImplementation(() => {}); - }); + asserted.clear() + warn = jest.spyOn(console, asError ? 'error' : 'warn') + warn.mockImplementation(() => {}) + }) afterEach(() => { - const assertedArray = Array.from(asserted); + const assertedArray = Array.from(asserted) const nonAssertedWarnings = warn.mock.calls .map((args) => args[0]) .filter((received) => { return !assertedArray.some((assertedMsg) => { - return received.indexOf(assertedMsg) > -1; - }); - }); - warn.mockRestore(); + return received.indexOf(assertedMsg) > -1 + }) + }) + warn.mockRestore() if (nonAssertedWarnings.length) { nonAssertedWarnings.forEach((warning) => { - console.warn(warning); - }); - throw new Error(`test case threw unexpected warnings.`); + console.warn(warning) + }) + throw new Error(`test case threw unexpected warnings.`) } - }); + }) } diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index e1941e20..8d8f5d54 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -1,5 +1,5 @@ -const Vue = require('vue/dist/vue.common.js'); +const Vue = require('vue/dist/vue.common.js') export function nextTick(): Promise { - return Vue.nextTick(); + return Vue.nextTick() } diff --git a/test/helpers/wait-for-update.js b/test/helpers/wait-for-update.js index af4fd65c..f426afda 100644 --- a/test/helpers/wait-for-update.js +++ b/test/helpers/wait-for-update.js @@ -1,4 +1,4 @@ -const Vue = require('vue'); +const Vue = require('vue') // helper for async assertions. // Use like this: @@ -14,63 +14,63 @@ const Vue = require('vue'); // .then(done) window.waitForUpdate = (initialCb) => { - let end; - const queue = initialCb ? [initialCb] : []; + let end + const queue = initialCb ? [initialCb] : [] function shift() { - const job = queue.shift(); + const job = queue.shift() if (queue.length) { - let hasError = false; + let hasError = false try { - job.wait ? job(shift) : job(); + job.wait ? job(shift) : job() } catch (e) { - hasError = true; - const done = queue[queue.length - 1]; + hasError = true + const done = queue[queue.length - 1] if (done && done.fail) { - done.fail(e); + done.fail(e) } } if (!hasError && !job.wait) { if (queue.length) { - Vue.nextTick(shift); + Vue.nextTick(shift) } } } else if (job && (job.fail || job === end)) { - job(); // done + job() // done } } Vue.nextTick(() => { if (!queue.length || (!end && !queue[queue.length - 1].fail)) { - throw new Error('waitForUpdate chain is missing .then(done)'); + throw new Error('waitForUpdate chain is missing .then(done)') } - shift(); - }); + shift() + }) const chainer = { then: (nextCb) => { - queue.push(nextCb); - return chainer; + queue.push(nextCb) + return chainer }, thenWaitFor: (wait) => { if (typeof wait === 'number') { - wait = timeout(wait); + wait = timeout(wait) } - wait.wait = true; - queue.push(wait); - return chainer; + wait.wait = true + queue.push(wait) + return chainer }, end: (endFn) => { - queue.push(endFn); - end = endFn; + queue.push(endFn) + end = endFn }, - }; + } - return chainer; -}; + return chainer +} -exports.waitForUpdate = window.waitForUpdate; +exports.waitForUpdate = window.waitForUpdate function timeout(n) { - return (next) => setTimeout(next, n); + return (next) => setTimeout(next, n) } diff --git a/test/ssr/serverPrefetch.spec.js b/test/ssr/serverPrefetch.spec.js index 66984773..96730530 100644 --- a/test/ssr/serverPrefetch.spec.js +++ b/test/ssr/serverPrefetch.spec.js @@ -1,139 +1,145 @@ -const Vue = require('vue/dist/vue.common.js'); -const { createRenderer } = require('vue-server-renderer'); -const { ref, onServerPrefetch, getCurrentInstance, provide, inject } = require('../../src'); +const Vue = require('vue/dist/vue.common.js') +const { createRenderer } = require('vue-server-renderer') +const { + ref, + onServerPrefetch, + getCurrentInstance, + provide, + inject, +} = require('../../src') function fetch(result) { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(() => { - resolve(result); - }, 10); - }); + resolve(result) + }, 10) + }) } describe('serverPrefetch', () => { it('should prefetch async operations before rendering', async () => { const app = new Vue({ setup() { - const count = ref(0); + const count = ref(0) onServerPrefetch(async () => { - count.value = await fetch(42); - }); + count.value = await fetch(42) + }) return { count, - }; + } }, render(h) { - return h('div', this.count); + return h('div', this.count) }, - }); + }) - const serverRenderer = createRenderer(); - const html = await serverRenderer.renderToString(app); - expect(html).toBe('
42
'); - }); + const serverRenderer = createRenderer() + const html = await serverRenderer.renderToString(app) + expect(html).toBe('
42
') + }) it('should prefetch many async operations before rendering', async () => { const app = new Vue({ setup() { - const count = ref(0); - const label = ref(''); + const count = ref(0) + const label = ref('') onServerPrefetch(async () => { - count.value = await fetch(42); - }); + count.value = await fetch(42) + }) onServerPrefetch(async () => { - label.value = await fetch('meow'); - }); + label.value = await fetch('meow') + }) return { count, label, - }; + } }, render(h) { - return h('div', [this.count, this.label]); + return h('div', [this.count, this.label]) }, - }); + }) - const serverRenderer = createRenderer(); - const html = await serverRenderer.renderToString(app); - expect(html).toBe('
42meow
'); - }); + const serverRenderer = createRenderer() + const html = await serverRenderer.renderToString(app) + expect(html).toBe('
42meow
') + }) it('should pass ssrContext', async () => { const child = { setup(props, { ssrContext }) { - const content = ref(); + const content = ref() - expect(ssrContext.foo).toBe('bar'); + expect(ssrContext.foo).toBe('bar') onServerPrefetch(async () => { - content.value = await fetch(ssrContext.foo); - }); + content.value = await fetch(ssrContext.foo) + }) return { content, - }; + } }, render(h) { - return h('div', this.content); + return h('div', this.content) }, - }; + } const app = new Vue({ components: { child, }, render(h) { - return h('child'); + return h('child') }, - }); + }) - const serverRenderer = createRenderer(); - const html = await serverRenderer.renderToString(app, { foo: 'bar' }); - expect(html).toBe('
bar
'); - }); + const serverRenderer = createRenderer() + const html = await serverRenderer.renderToString(app, { foo: 'bar' }) + expect(html).toBe('
bar
') + }) it('should not share context', async () => { - const instances = []; + const instances = [] function createApp(context) { return new Vue({ setup() { - const count = ref(0); + const count = ref(0) onServerPrefetch(async () => { - count.value = await fetch(context.result); - }); + count.value = await fetch(context.result) + }) - instances.push(getCurrentInstance()); + instances.push(getCurrentInstance()) return { count, - }; + } }, render(h) { - return h('div', this.count); + return h('div', this.count) }, - }); + }) } - const serverRenderer = createRenderer(); - const promises = []; + const serverRenderer = createRenderer() + const promises = [] // Parallel requests for (let i = 1; i < 3; i++) { promises.push( - new Promise(async resolve => { - const app = createApp({ result: i }); - const html = await serverRenderer.renderToString(app); - expect(html).toBe(`
${i}
`); - resolve(); + new Promise(async (resolve) => { + const app = createApp({ result: i }) + const html = await serverRenderer.renderToString(app) + expect(html).toBe(`
${i}
`) + resolve() }) - ); + ) } - await Promise.all(promises); - expect((instances[0] === instances[1]) === instances[2]).toBe(false); - }); -}); + await Promise.all(promises) + expect((instances[0] === instances[1]) === instances[2]).toBe(false) + }) +}) diff --git a/test/types/defineComponent.spec.ts b/test/types/defineComponent.spec.ts index 6f1c8008..d4fe4b31 100644 --- a/test/types/defineComponent.spec.ts +++ b/test/types/defineComponent.spec.ts @@ -5,36 +5,40 @@ import { ref, SetupContext, PropType, -} from '../../src'; -import Router from 'vue-router'; +} from '../../src' +import Router from 'vue-router' -const Vue = require('vue/dist/vue.common.js'); +const Vue = require('vue/dist/vue.common.js') -type Equal = (() => U extends Left ? 1 : 0) extends () => U extends Right ? 1 : 0 +type Equal = (() => U extends Left ? 1 : 0) extends < + U +>() => U extends Right ? 1 : 0 ? true - : false; + : false const isTypeEqual = (shouldBeEqual: Equal) => { - void shouldBeEqual; - expect(true).toBe(true); -}; -const isSubType = (shouldBeEqual: SubType extends SuperType ? true : false) => { - void shouldBeEqual; - expect(true).toBe(true); -}; + void shouldBeEqual + expect(true).toBe(true) +} +const isSubType = ( + shouldBeEqual: SubType extends SuperType ? true : false +) => { + void shouldBeEqual + expect(true).toBe(true) +} describe('defineComponent', () => { it('should work', () => { const Child = defineComponent({ props: { msg: String }, setup(props) { - return () => h('span', props.msg); + return () => h('span', props.msg) }, - }); + }) const App = defineComponent({ setup() { - const msg = ref('hello'); + const msg = ref('hello') return () => h('div', [ h(Child, { @@ -42,12 +46,12 @@ describe('defineComponent', () => { msg: msg.value, }, }), - ]); + ]) }, - }); - const vm = new Vue(App).$mount(); - expect(vm.$el.querySelector('span').textContent).toBe('hello'); - }); + }) + const vm = new Vue(App).$mount() + expect(vm.$el.querySelector('span').textContent).toBe('hello') + }) it('should infer props type', () => { const App = defineComponent({ @@ -59,60 +63,60 @@ describe('defineComponent', () => { b: String, }, setup(props, ctx) { - type PropsType = typeof props; - isTypeEqual(true); - isSubType(true); - isSubType<{ readonly b?: string; readonly a: number }, PropsType>(true); - return () => null; + type PropsType = typeof props + isTypeEqual(true) + isSubType(true) + isSubType<{ readonly b?: string; readonly a: number }, PropsType>(true) + return () => null }, - }); - new Vue(App); - expect.assertions(3); - }); + }) + new Vue(App) + expect.assertions(3) + }) it('custom props interface', () => { interface IPropsType { - b: string; + b: string } const App = defineComponent({ props: { b: {}, }, setup(props, ctx) { - type PropsType = typeof props; - isTypeEqual(true); - isSubType(true); - isSubType<{ b: string }, PropsType>(true); - return () => null; + type PropsType = typeof props + isTypeEqual(true) + isSubType(true) + isSubType<{ b: string }, PropsType>(true) + return () => null }, - }); - new Vue(App); - expect.assertions(3); - }); + }) + new Vue(App) + expect.assertions(3) + }) it('custom props type function', () => { interface IPropsTypeFunction { - fn: (arg: boolean) => void; + fn: (arg: boolean) => void } const App = defineComponent({ props: { fn: Function as PropType<(arg: boolean) => void>, }, setup(props, ctx) { - type PropsType = typeof props; - isTypeEqual(true); - isSubType void }>(true); - isSubType<{ fn: (arg: boolean) => void }, PropsType>(true); - return () => null; + type PropsType = typeof props + isTypeEqual(true) + isSubType void }>(true) + isSubType<{ fn: (arg: boolean) => void }, PropsType>(true) + return () => null }, - }); - new Vue(App); - expect.assertions(3); - }); + }) + new Vue(App) + expect.assertions(3) + }) it('custom props type inferred from PropType', () => { interface User { - name: string; + name: string } const App = defineComponent({ props: { @@ -121,44 +125,48 @@ describe('defineComponent', () => { userFunc: Function as PropType<(u: User) => User>, }, setup(props) { - type PropsType = typeof props; - isSubType<{ user?: User; func?: () => boolean; userFunc?: (u: User) => User }, PropsType>(true); - isSubType boolean; userFunc?: (u: User) => User }>( - true - ); + type PropsType = typeof props + isSubType< + { user?: User; func?: () => boolean; userFunc?: (u: User) => User }, + PropsType + >(true) + isSubType< + PropsType, + { user?: User; func?: () => boolean; userFunc?: (u: User) => User } + >(true) }, - }); - new Vue(App); - expect.assertions(2); - }); + }) + new Vue(App) + expect.assertions(2) + }) it('no props', () => { const App = defineComponent({ setup(props, ctx) { - isTypeEqual(true); - isTypeEqual(true); - return () => null; + isTypeEqual(true) + isTypeEqual(true) + return () => null }, - }); - new Vue(App); - expect.assertions(2); - }); + }) + new Vue(App) + expect.assertions(2) + }) it('should accept tuple props', () => { const App = defineComponent({ props: ['p1', 'p2'], setup(props) { - props.p1; - props.p2; - type PropsType = typeof props; - type Expected = { readonly p1?: any; readonly p2?: any }; - isSubType(true); - isSubType(true); + props.p1 + props.p2 + type PropsType = typeof props + type Expected = { readonly p1?: any; readonly p2?: any } + isSubType(true) + isSubType(true) }, - }); - new Vue(App); - expect.assertions(2); - }); + }) + new Vue(App) + expect.assertions(2) + }) it('infer the required prop', () => { const App = defineComponent({ @@ -180,19 +188,21 @@ describe('defineComponent', () => { foo: 'foo', }, setup(props) { - type PropsType = typeof props; - isSubType<{ readonly foo: string; readonly bar: string; readonly zoo?: string }, PropsType>( - true - ); - isSubType( - true - ); - return () => null; + type PropsType = typeof props + isSubType< + { readonly foo: string; readonly bar: string; readonly zoo?: string }, + PropsType + >(true) + isSubType< + PropsType, + { readonly foo: string; readonly bar: string; readonly zoo?: string } + >(true) + return () => null }, - }); - new Vue(App); - expect.assertions(2); - }); + }) + new Vue(App) + expect.assertions(2) + }) describe('compatible with vue router', () => { it('RouteConfig.component', () => { @@ -204,23 +214,25 @@ describe('defineComponent', () => { component: defineComponent({}), }, ], - }); - }); - }); + }) + }) + }) describe('retro-compatible with createComponent', () => { it('should still work and warn', () => { - const warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); + const warn = jest + .spyOn(global.console, 'error') + .mockImplementation(() => null) const Child = createComponent({ props: { msg: String }, setup(props) { - return () => h('span', props.msg); + return () => h('span', props.msg) }, - }); + }) const App = createComponent({ setup() { - const msg = ref('hello'); + const msg = ref('hello') return () => h('div', [ h(Child, { @@ -228,15 +240,15 @@ describe('defineComponent', () => { msg: msg.value, }, }), - ]); + ]) }, - }); - const vm = new Vue(App).$mount(); - expect(vm.$el.querySelector('span').textContent).toBe('hello'); + }) + const vm = new Vue(App).$mount() + expect(vm.$el.querySelector('span').textContent).toBe('hello') expect(warn.mock.calls[0][0]).toMatch( '[Vue warn]: `createComponent` has been renamed to `defineComponent`.' - ); - warn.mockRestore(); - }); - }); -}); + ) + warn.mockRestore() + }) + }) +}) diff --git a/test/v3/reactivity/computed.spec.ts b/test/v3/reactivity/computed.spec.ts index 933451b0..999b07d2 100644 --- a/test/v3/reactivity/computed.spec.ts +++ b/test/v3/reactivity/computed.spec.ts @@ -1,127 +1,127 @@ -import { computed, reactive, ref, watchEffect } from '../../../src'; -import { mockWarn } from '../../helpers/mockWarn'; -import { nextTick } from '../../helpers/utils'; +import { computed, reactive, ref, watchEffect } from '../../../src' +import { mockWarn } from '../../helpers/mockWarn' +import { nextTick } from '../../helpers/utils' describe('reactivity/computed', () => { - mockWarn(); + mockWarn() it('should return updated value', async () => { - const value = reactive<{ foo?: number }>({ foo: undefined }); - const cValue = computed(() => value.foo); - expect(cValue.value).toBe(undefined); - value.foo = 1; - await nextTick(); + const value = reactive<{ foo?: number }>({ foo: undefined }) + const cValue = computed(() => value.foo) + expect(cValue.value).toBe(undefined) + value.foo = 1 + await nextTick() - expect(cValue.value).toBe(1); - }); + expect(cValue.value).toBe(1) + }) it('should compute lazily', () => { - const value = reactive<{ foo?: number }>({ foo: undefined }); - const getter = jest.fn(() => value.foo); - const cValue = computed(getter); + const value = reactive<{ foo?: number }>({ foo: undefined }) + const getter = jest.fn(() => value.foo) + const cValue = computed(getter) // lazy - expect(getter).not.toHaveBeenCalled(); + expect(getter).not.toHaveBeenCalled() - expect(cValue.value).toBe(undefined); - expect(getter).toHaveBeenCalledTimes(1); + expect(cValue.value).toBe(undefined) + expect(getter).toHaveBeenCalledTimes(1) // should not compute again - cValue.value; - expect(getter).toHaveBeenCalledTimes(1); + cValue.value + expect(getter).toHaveBeenCalledTimes(1) // should not compute until needed - value.foo = 1; - expect(getter).toHaveBeenCalledTimes(1); + value.foo = 1 + expect(getter).toHaveBeenCalledTimes(1) // now it should compute - expect(cValue.value).toBe(1); - expect(getter).toHaveBeenCalledTimes(2); + expect(cValue.value).toBe(1) + expect(getter).toHaveBeenCalledTimes(2) // should not compute again - cValue.value; - expect(getter).toHaveBeenCalledTimes(2); - }); + cValue.value + expect(getter).toHaveBeenCalledTimes(2) + }) it('should trigger effect', () => { - const value = reactive<{ foo?: number }>({ foo: undefined }); - const cValue = computed(() => value.foo); - let dummy; + const value = reactive<{ foo?: number }>({ foo: undefined }) + const cValue = computed(() => value.foo) + let dummy watchEffect( () => { - dummy = cValue.value; + dummy = cValue.value }, { flush: 'sync' } - ); - expect(dummy).toBe(undefined); - value.foo = 1; - expect(dummy).toBe(1); - }); + ) + expect(dummy).toBe(undefined) + value.foo = 1 + expect(dummy).toBe(1) + }) it('should work when chained', () => { - const value = reactive({ foo: 0 }); - const c1 = computed(() => value.foo); - const c2 = computed(() => c1.value + 1); - expect(c2.value).toBe(1); - expect(c1.value).toBe(0); - value.foo++; - expect(c2.value).toBe(2); - expect(c1.value).toBe(1); - }); + const value = reactive({ foo: 0 }) + const c1 = computed(() => value.foo) + const c2 = computed(() => c1.value + 1) + expect(c2.value).toBe(1) + expect(c1.value).toBe(0) + value.foo++ + expect(c2.value).toBe(2) + expect(c1.value).toBe(1) + }) it('should trigger effect when chained', () => { - const value = reactive({ foo: 0 }); - const getter1 = jest.fn(() => value.foo); + const value = reactive({ foo: 0 }) + const getter1 = jest.fn(() => value.foo) const getter2 = jest.fn(() => { - return c1.value + 1; - }); - const c1 = computed(getter1); - const c2 = computed(getter2); + return c1.value + 1 + }) + const c1 = computed(getter1) + const c2 = computed(getter2) - let dummy; + let dummy watchEffect( () => { - dummy = c2.value; + dummy = c2.value }, { flush: 'sync' } - ); - expect(dummy).toBe(1); - expect(getter1).toHaveBeenCalledTimes(1); - expect(getter2).toHaveBeenCalledTimes(1); - value.foo++; - expect(dummy).toBe(2); + ) + expect(dummy).toBe(1) + expect(getter1).toHaveBeenCalledTimes(1) + expect(getter2).toHaveBeenCalledTimes(1) + value.foo++ + expect(dummy).toBe(2) // should not result in duplicate calls - expect(getter1).toHaveBeenCalledTimes(2); - expect(getter2).toHaveBeenCalledTimes(2); - }); + expect(getter1).toHaveBeenCalledTimes(2) + expect(getter2).toHaveBeenCalledTimes(2) + }) it('should trigger effect when chained (mixed invocations)', async () => { - const value = reactive({ foo: 0 }); - const getter1 = jest.fn(() => value.foo); + const value = reactive({ foo: 0 }) + const getter1 = jest.fn(() => value.foo) const getter2 = jest.fn(() => { - return c1.value + 1; - }); - const c1 = computed(getter1); - const c2 = computed(getter2); + return c1.value + 1 + }) + const c1 = computed(getter1) + const c2 = computed(getter2) - let dummy; + let dummy watchEffect(() => { - dummy = c1.value + c2.value; - }); - await nextTick(); - expect(dummy).toBe(1); + dummy = c1.value + c2.value + }) + await nextTick() + expect(dummy).toBe(1) - expect(getter1).toHaveBeenCalledTimes(1); - expect(getter2).toHaveBeenCalledTimes(1); - value.foo++; + expect(getter1).toHaveBeenCalledTimes(1) + expect(getter2).toHaveBeenCalledTimes(1) + value.foo++ - await nextTick(); + await nextTick() - expect(dummy).toBe(3); + expect(dummy).toBe(3) // should not result in duplicate calls - expect(getter1).toHaveBeenCalledTimes(2); - expect(getter2).toHaveBeenCalledTimes(2); - }); + expect(getter1).toHaveBeenCalledTimes(2) + expect(getter2).toHaveBeenCalledTimes(2) + }) // it('should no longer update when stopped', () => { // const value = reactive<{ foo?: number }>({}); @@ -139,41 +139,41 @@ describe('reactivity/computed', () => { // }); it('should support setter', () => { - const n = ref(1); + const n = ref(1) const plusOne = computed({ get: () => n.value + 1, set: (val) => { - n.value = val - 1; + n.value = val - 1 }, - }); + }) - expect(plusOne.value).toBe(2); - n.value++; - expect(plusOne.value).toBe(3); + expect(plusOne.value).toBe(2) + n.value++ + expect(plusOne.value).toBe(3) - plusOne.value = 0; - expect(n.value).toBe(-1); - }); + plusOne.value = 0 + expect(n.value).toBe(-1) + }) it('should trigger effect w/ setter', async () => { - const n = ref(1); + const n = ref(1) const plusOne = computed({ get: () => n.value + 1, set: (val) => { - n.value = val - 1; + n.value = val - 1 }, - }); + }) - let dummy; + let dummy watchEffect(() => { - dummy = n.value; - }); - expect(dummy).toBe(1); + dummy = n.value + }) + expect(dummy).toBe(1) - plusOne.value = 0; - await nextTick(); - expect(dummy).toBe(-1); - }); + plusOne.value = 0 + await nextTick() + expect(dummy).toBe(-1) + }) // it('should warn if trying to set a readonly computed', async () => { // const n = ref(1); @@ -183,4 +183,4 @@ describe('reactivity/computed', () => { // expect('Write operation failed: computed value is readonly').toHaveBeenWarnedLast(); // }); -}); +}) diff --git a/test/v3/reactivity/reactive.spec.ts b/test/v3/reactivity/reactive.spec.ts index f86748c3..251d638c 100644 --- a/test/v3/reactivity/reactive.spec.ts +++ b/test/v3/reactivity/reactive.spec.ts @@ -8,200 +8,202 @@ import { shallowReactive, set, markRaw, -} from '../../../src'; +} from '../../../src' describe('reactivity/reactive', () => { - let warn: jest.SpyInstance; + let warn: jest.SpyInstance beforeEach(() => { - warn = jest.spyOn(global.console, 'error').mockImplementation(() => null); - warn.mockReset(); - }); + warn = jest.spyOn(global.console, 'error').mockImplementation(() => null) + warn.mockReset() + }) afterEach(() => { - expect(warn).not.toBeCalled(); - warn.mockRestore(); - }); + expect(warn).not.toBeCalled() + warn.mockRestore() + }) test('Object', () => { - const original = { foo: 1 }; - const observed = reactive(original); - expect(observed).toBe(original); - expect(isReactive(observed)).toBe(true); - expect(isReactive(original)).toBe(true); // this is false in v3 but true in v2 + const original = { foo: 1 } + const observed = reactive(original) + expect(observed).toBe(original) + expect(isReactive(observed)).toBe(true) + expect(isReactive(original)).toBe(true) // this is false in v3 but true in v2 // get - expect(observed.foo).toBe(1); + expect(observed.foo).toBe(1) // has - expect('foo' in observed).toBe(true); + expect('foo' in observed).toBe(true) // ownKeys - expect(Object.keys(observed)).toEqual(['foo']); - }); + expect(Object.keys(observed)).toEqual(['foo']) + }) test('proto', () => { - const obj = {}; - const reactiveObj = reactive(obj); - expect(isReactive(reactiveObj)).toBe(true); + const obj = {} + const reactiveObj = reactive(obj) + expect(isReactive(reactiveObj)).toBe(true) // read prop of reactiveObject will cause reactiveObj[prop] to be reactive // @ts-ignore - const prototype = reactiveObj['__proto__']; - const otherObj = { data: ['a'] }; - expect(isReactive(otherObj)).toBe(false); - const reactiveOther = reactive(otherObj); - expect(isReactive(reactiveOther)).toBe(true); - expect(reactiveOther.data[0]).toBe('a'); - }); + const prototype = reactiveObj['__proto__'] + const otherObj = { data: ['a'] } + expect(isReactive(otherObj)).toBe(false) + const reactiveOther = reactive(otherObj) + expect(isReactive(reactiveOther)).toBe(true) + expect(reactiveOther.data[0]).toBe('a') + }) test('nested reactives', () => { const original = { nested: { foo: 1, }, array: [{ bar: 2 }], - }; - const observed = reactive(original); - expect(isReactive(observed.nested)).toBe(true); + } + const observed = reactive(original) + expect(isReactive(observed.nested)).toBe(true) // expect(isReactive(observed.array)).toBe(true); //not supported by vue2 // expect(isReactive(observed.array[0])).toBe(true); //not supported by vue2 - }); + }) test('observed value should proxy mutations to original (Object)', () => { - const original: any = { foo: 1 }; - const observed = reactive(original); + const original: any = { foo: 1 } + const observed = reactive(original) // set - observed.bar = 1; - expect(observed.bar).toBe(1); - expect(original.bar).toBe(1); + observed.bar = 1 + expect(observed.bar).toBe(1) + expect(original.bar).toBe(1) // delete - delete observed.foo; - expect('foo' in observed).toBe(false); - expect('foo' in original).toBe(false); - }); + delete observed.foo + expect('foo' in observed).toBe(false) + expect('foo' in original).toBe(false) + }) test('setting a property with an unobserved value should wrap with reactive', () => { - const observed = reactive<{ foo?: object }>({}); - const raw = {}; - set(observed, 'foo', raw); // v2 limitation + const observed = reactive<{ foo?: object }>({}) + const raw = {} + set(observed, 'foo', raw) // v2 limitation - expect(observed.foo).toBe(raw); // v2 limitation - expect(isReactive(observed.foo)).toBe(true); - }); + expect(observed.foo).toBe(raw) // v2 limitation + expect(isReactive(observed.foo)).toBe(true) + }) test('observing already observed value should return same Proxy', () => { - const original = { foo: 1 }; - const observed = reactive(original); - const observed2 = reactive(observed); - expect(observed2).toBe(observed); - }); + const original = { foo: 1 } + const observed = reactive(original) + const observed2 = reactive(observed) + expect(observed2).toBe(observed) + }) test('observing the same value multiple times should return same Proxy', () => { - const original = { foo: 1 }; - const observed = reactive(original); - const observed2 = reactive(original); - expect(observed2).toBe(observed); - }); + const original = { foo: 1 } + const observed = reactive(original) + const observed2 = reactive(original) + expect(observed2).toBe(observed) + }) test('should not pollute original object with Proxies', () => { - const original: any = { foo: 1 }; - const original2 = { bar: 2 }; - const observed = reactive(original); - const observed2 = reactive(original2); - observed.bar = observed2; - expect(observed.bar).toBe(observed2); - expect(original.bar).toBe(original2); - }); + const original: any = { foo: 1 } + const original2 = { bar: 2 } + const observed = reactive(original) + const observed2 = reactive(original2) + observed.bar = observed2 + expect(observed.bar).toBe(observed2) + expect(original.bar).toBe(original2) + }) test('unwrap', () => { // vue2 mutates the original object - const original = { foo: 1 }; - const observed = reactive(original); - expect(toRaw(observed)).toBe(original); - expect(toRaw(original)).toBe(original); - }); + const original = { foo: 1 } + const observed = reactive(original) + expect(toRaw(observed)).toBe(original) + expect(toRaw(original)).toBe(original) + }) test('should not unwrap Ref', () => { - const observedNumberRef = reactive(ref(1)); - const observedObjectRef = reactive(ref({ foo: 1 })); + const observedNumberRef = reactive(ref(1)) + const observedObjectRef = reactive(ref({ foo: 1 })) - expect(isRef(observedNumberRef)).toBe(true); - expect(isRef(observedObjectRef)).toBe(true); - }); + expect(isRef(observedNumberRef)).toBe(true) + expect(isRef(observedObjectRef)).toBe(true) + }) test('should unwrap computed refs', () => { // readonly - const a = computed(() => 1); + const a = computed(() => 1) // writable const b = computed({ get: () => 1, set: () => {}, - }); - const obj = reactive({ a, b }); + }) + const obj = reactive({ a, b }) // check type - obj.a + 1; - obj.b + 1; - expect(typeof obj.a).toBe(`number`); - expect(typeof obj.b).toBe(`number`); - }); + obj.a + 1 + obj.b + 1 + expect(typeof obj.a).toBe(`number`) + expect(typeof obj.b).toBe(`number`) + }) test('non-observable values', () => { const assertValue = (value: any) => { - expect(isReactive(reactive(value))).toBe(false); + expect(isReactive(reactive(value))).toBe(false) // expect(warnSpy).toHaveBeenLastCalledWith(`value cannot be made reactive: ${String(value)}`); - }; + } // number - assertValue(1); + assertValue(1) // string - assertValue('foo'); + assertValue('foo') // boolean - assertValue(false); + assertValue(false) // null - assertValue(null); + assertValue(null) // undefined - assertValue(undefined); + assertValue(undefined) // symbol - const s = Symbol(); - assertValue(s); + const s = Symbol() + assertValue(s) // built-ins should work and return same value - const p = Promise.resolve(); - expect(reactive(p)).toBe(p); - const r = new RegExp(''); - expect(reactive(r)).toBe(r); - const d = new Date(); - expect(reactive(d)).toBe(d); - - expect(warn).toBeCalledTimes(3); + const p = Promise.resolve() + expect(reactive(p)).toBe(p) + const r = new RegExp('') + expect(reactive(r)).toBe(r) + const d = new Date() + expect(reactive(d)).toBe(d) + + expect(warn).toBeCalledTimes(3) expect( warn.mock.calls.map((call) => { - expect(call[0]).toBe('[Vue warn]: "reactive()" is called without provide an "object".'); + expect(call[0]).toBe( + '[Vue warn]: "reactive()" is called without provide an "object".' + ) }) - ); - warn.mockReset(); - }); + ) + warn.mockReset() + }) test('markRaw', () => { const obj = reactive({ foo: { a: 1 }, bar: markRaw({ b: 2 }), - }); - expect(isReactive(obj.foo)).toBe(true); - expect(isReactive(obj.bar)).toBe(false); - }); + }) + expect(isReactive(obj.foo)).toBe(true) + expect(isReactive(obj.bar)).toBe(false) + }) test('should not observe frozen objects', () => { const obj = reactive({ foo: Object.freeze({ a: 1 }), - }); - expect(isReactive(obj.foo)).toBe(false); - }); + }) + expect(isReactive(obj.foo)).toBe(false) + }) describe('shallowReactive', () => { test('should not make non-reactive properties reactive', () => { - const props = shallowReactive({ n: { foo: 1 } }); - expect(isReactive(props.n)).toBe(false); - }); + const props = shallowReactive({ n: { foo: 1 } }) + expect(isReactive(props.n)).toBe(false) + }) test('should keep reactive properties reactive', () => { - const props: any = shallowReactive({ n: reactive({ foo: 1 }) }); - props.n = reactive({ foo: 2 }); - expect(isReactive(props.n)).toBe(true); - }); - }); -}); + const props: any = shallowReactive({ n: reactive({ foo: 1 }) }) + props.n = reactive({ foo: 2 }) + expect(isReactive(props.n)).toBe(true) + }) + }) +}) diff --git a/test/v3/reactivity/ref.spec.ts b/test/v3/reactivity/ref.spec.ts index 9c1b6932..6abeb985 100644 --- a/test/v3/reactivity/ref.spec.ts +++ b/test/v3/reactivity/ref.spec.ts @@ -11,135 +11,135 @@ import { unref, isReactive, shallowRef, -} from '../../../src'; +} from '../../../src' describe('reactivity/ref', () => { it('should hold a value', () => { - const a = ref(1); - expect(a.value).toBe(1); - a.value = 2; - expect(a.value).toBe(2); - }); + const a = ref(1) + expect(a.value).toBe(1) + a.value = 2 + expect(a.value).toBe(2) + }) it('should be reactive', () => { - const a = ref(1); - let dummy; - let calls = 0; + const a = ref(1) + let dummy + let calls = 0 watchEffect( () => { - calls++; - dummy = a.value; + calls++ + dummy = a.value }, { flush: 'sync' } - ); - expect(calls).toBe(1); - expect(dummy).toBe(1); + ) + expect(calls).toBe(1) + expect(dummy).toBe(1) - a.value = 2; - expect(calls).toBe(2); - expect(dummy).toBe(2); + a.value = 2 + expect(calls).toBe(2) + expect(dummy).toBe(2) // same value should not trigger - a.value = 2; - expect(calls).toBe(2); - expect(dummy).toBe(2); - }); + a.value = 2 + expect(calls).toBe(2) + expect(dummy).toBe(2) + }) it('should make nested properties reactive', () => { const a = ref({ count: 1, - }); - let dummy; + }) + let dummy watchEffect( () => { - dummy = a.value.count; + dummy = a.value.count }, { flush: 'sync' } - ); - expect(dummy).toBe(1); - a.value.count = 2; - expect(dummy).toBe(2); - }); + ) + expect(dummy).toBe(1) + a.value.count = 2 + expect(dummy).toBe(2) + }) it('should work without initial value', () => { - const a = ref(); - let dummy; + const a = ref() + let dummy watchEffect( () => { - dummy = a.value; + dummy = a.value }, { flush: 'sync' } - ); - expect(dummy).toBe(undefined); - a.value = 2; - expect(dummy).toBe(2); - }); + ) + expect(dummy).toBe(undefined) + a.value = 2 + expect(dummy).toBe(2) + }) it('should work like a normal property when nested in a reactive object', () => { - const a = ref(1); + const a = ref(1) const obj = reactive({ a, b: { c: a, }, - }); + }) - let dummy1: number; - let dummy2: number; + let dummy1: number + let dummy2: number watchEffect( () => { - dummy1 = obj.a; - dummy2 = obj.b.c; + dummy1 = obj.a + dummy2 = obj.b.c }, { flush: 'sync' } - ); + ) const assertDummiesEqualTo = (val: number) => - [dummy1, dummy2].forEach((dummy) => expect(dummy).toBe(val)); + [dummy1, dummy2].forEach((dummy) => expect(dummy).toBe(val)) - assertDummiesEqualTo(1); - a.value++; - assertDummiesEqualTo(2); - obj.a++; - assertDummiesEqualTo(3); - obj.b.c++; - assertDummiesEqualTo(4); - }); + assertDummiesEqualTo(1) + a.value++ + assertDummiesEqualTo(2) + obj.a++ + assertDummiesEqualTo(3) + obj.b.c++ + assertDummiesEqualTo(4) + }) it('should unwrap nested ref in types', () => { - const a = ref(0); - const b = ref(a); + const a = ref(0) + const b = ref(a) - expect(typeof (b.value + 1)).toBe('number'); - }); + expect(typeof (b.value + 1)).toBe('number') + }) it('should unwrap nested values in types', () => { const a = { b: ref(0), - }; + } - const c = ref(a); + const c = ref(a) - expect(typeof (c.value.b + 1)).toBe('number'); - }); + expect(typeof (c.value.b + 1)).toBe('number') + }) it('should NOT unwrap ref types nested inside arrays', () => { - const arr = ref([1, ref(1)]).value; - (arr[0] as number)++; - (arr[1] as Ref).value++; + const arr = ref([1, ref(1)]).value + ;(arr[0] as number)++ + ;(arr[1] as Ref).value++ - const arr2 = ref([1, new Map(), ref('1')]).value; - const value = arr2[0]; + const arr2 = ref([1, new Map(), ref('1')]).value + const value = arr2[0] if (isRef(value)) { - value + 'foo'; + value + 'foo' } else if (typeof value === 'number') { - value + 1; + value + 1 } else { // should narrow down to Map type // and not contain any Ref type - value.has('foo'); + value.has('foo') } - }); + }) it('should keep tuple types', () => { const tuple: [number, string, { a: number }, () => number, Ref] = [ @@ -148,165 +148,165 @@ describe('reactivity/ref', () => { { a: 1 }, () => 0, ref(0), - ]; - const tupleRef = ref(tuple); - - tupleRef.value[0]++; - expect(tupleRef.value[0]).toBe(1); - tupleRef.value[1] += '1'; - expect(tupleRef.value[1]).toBe('11'); - tupleRef.value[2].a++; - expect(tupleRef.value[2].a).toBe(2); - expect(tupleRef.value[3]()).toBe(0); - tupleRef.value[4].value++; - expect(tupleRef.value[4].value).toBe(1); - }); + ] + const tupleRef = ref(tuple) + + tupleRef.value[0]++ + expect(tupleRef.value[0]).toBe(1) + tupleRef.value[1] += '1' + expect(tupleRef.value[1]).toBe('11') + tupleRef.value[2].a++ + expect(tupleRef.value[2].a).toBe(2) + expect(tupleRef.value[3]()).toBe(0) + tupleRef.value[4].value++ + expect(tupleRef.value[4].value).toBe(1) + }) it('should keep symbols', () => { - const customSymbol = Symbol(); + const customSymbol = Symbol() const obj = { [Symbol.asyncIterator]: { a: 1 }, [Symbol.unscopables]: { b: '1' }, [customSymbol]: { c: [1, 2, 3] }, - }; + } - const objRef = ref(obj); + const objRef = ref(obj) - expect(objRef.value[Symbol.asyncIterator]).toBe(obj[Symbol.asyncIterator]); - expect(objRef.value[Symbol.unscopables]).toBe(obj[Symbol.unscopables]); - expect(objRef.value[customSymbol]).toStrictEqual(obj[customSymbol]); - }); + expect(objRef.value[Symbol.asyncIterator]).toBe(obj[Symbol.asyncIterator]) + expect(objRef.value[Symbol.unscopables]).toBe(obj[Symbol.unscopables]) + expect(objRef.value[customSymbol]).toStrictEqual(obj[customSymbol]) + }) test('unref', () => { - expect(unref(1)).toBe(1); - expect(unref(ref(1))).toBe(1); - }); + expect(unref(1)).toBe(1) + expect(unref(ref(1))).toBe(1) + }) test('shallowRef', () => { - const sref = shallowRef({ a: 1 }); - expect(isReactive(sref.value)).toBe(false); + const sref = shallowRef({ a: 1 }) + expect(isReactive(sref.value)).toBe(false) - let dummy; + let dummy watchEffect( () => { - dummy = sref.value.a; + dummy = sref.value.a }, { flush: 'sync' } - ); - expect(dummy).toBe(1); + ) + expect(dummy).toBe(1) - sref.value = { a: 2 }; - expect(isReactive(sref.value)).toBe(false); - expect(dummy).toBe(2); + sref.value = { a: 2 } + expect(isReactive(sref.value)).toBe(false) + expect(dummy).toBe(2) - sref.value.a = 3; - expect(dummy).toBe(2); - }); + sref.value.a = 3 + expect(dummy).toBe(2) + }) test('shallowRef force trigger', () => { - const sref = shallowRef({ a: 1 }); - let dummy; + const sref = shallowRef({ a: 1 }) + let dummy watchEffect( () => { - dummy = sref.value.a; + dummy = sref.value.a }, { flush: 'sync' } - ); - expect(dummy).toBe(1); + ) + expect(dummy).toBe(1) - sref.value.a = 2; - expect(dummy).toBe(1); // should not trigger yet + sref.value.a = 2 + expect(dummy).toBe(1) // should not trigger yet // force trigger // sref.value = sref.value; - triggerRef(sref); - expect(dummy).toBe(2); - }); + triggerRef(sref) + expect(dummy).toBe(2) + }) test('isRef', () => { - expect(isRef(ref(1))).toBe(true); - expect(isRef(computed(() => 1))).toBe(true); + expect(isRef(ref(1))).toBe(true) + expect(isRef(computed(() => 1))).toBe(true) - expect(isRef(0)).toBe(false); - expect(isRef(1)).toBe(false); + expect(isRef(0)).toBe(false) + expect(isRef(1)).toBe(false) // an object that looks like a ref isn't necessarily a ref - expect(isRef({ value: 0 })).toBe(false); - }); + expect(isRef({ value: 0 })).toBe(false) + }) test('toRef', () => { const a = reactive({ x: 1, - }); - const x = toRef(a, 'x'); - expect(isRef(x)).toBe(true); - expect(x.value).toBe(1); + }) + const x = toRef(a, 'x') + expect(isRef(x)).toBe(true) + expect(x.value).toBe(1) // source -> proxy - a.x = 2; - expect(x.value).toBe(2); + a.x = 2 + expect(x.value).toBe(2) // proxy -> source - x.value = 3; - expect(a.x).toBe(3); + x.value = 3 + expect(a.x).toBe(3) // reactivity - let dummyX; + let dummyX watchEffect( () => { - dummyX = x.value; + dummyX = x.value }, { flush: 'sync' } - ); - expect(dummyX).toBe(x.value); + ) + expect(dummyX).toBe(x.value) // mutating source should trigger watchEffect using the proxy refs - a.x = 4; - expect(dummyX).toBe(4); - }); + a.x = 4 + expect(dummyX).toBe(4) + }) test('toRefs', () => { const a = reactive({ x: 1, y: 2, - }); + }) - const { x, y } = toRefs(a); + const { x, y } = toRefs(a) - expect(isRef(x)).toBe(true); - expect(isRef(y)).toBe(true); - expect(x.value).toBe(1); - expect(y.value).toBe(2); + expect(isRef(x)).toBe(true) + expect(isRef(y)).toBe(true) + expect(x.value).toBe(1) + expect(y.value).toBe(2) // source -> proxy - a.x = 2; - a.y = 3; - expect(x.value).toBe(2); - expect(y.value).toBe(3); + a.x = 2 + a.y = 3 + expect(x.value).toBe(2) + expect(y.value).toBe(3) // proxy -> source - x.value = 3; - y.value = 4; - expect(a.x).toBe(3); - expect(a.y).toBe(4); + x.value = 3 + y.value = 4 + expect(a.x).toBe(3) + expect(a.y).toBe(4) // reactivity - let dummyX, dummyY; + let dummyX, dummyY watchEffect( () => { - dummyX = x.value; - dummyY = y.value; + dummyX = x.value + dummyY = y.value }, { flush: 'sync' } - ); - expect(dummyX).toBe(x.value); - expect(dummyY).toBe(y.value); + ) + expect(dummyX).toBe(x.value) + expect(dummyY).toBe(y.value) // mutating source should trigger watchEffect using the proxy refs - a.x = 4; - a.y = 5; - expect(dummyX).toBe(4); - expect(dummyY).toBe(5); - }); + a.x = 4 + a.y = 5 + expect(dummyX).toBe(4) + expect(dummyY).toBe(5) + }) // test('customRef', () => { // let value = 1; @@ -338,4 +338,4 @@ describe('reactivity/ref', () => { // _trigger!(); // expect(dummy).toBe(2); // }); -}); +}) diff --git a/test/v3/runtime-core/apiLifecycle.spec.ts b/test/v3/runtime-core/apiLifecycle.spec.ts index b7a9629c..f952bdc4 100644 --- a/test/v3/runtime-core/apiLifecycle.spec.ts +++ b/test/v3/runtime-core/apiLifecycle.spec.ts @@ -1,5 +1,5 @@ -import { VueConstructor } from 'vue'; -const Vue = require('vue/dist/vue.common.js') as VueConstructor; +import { VueConstructor } from 'vue' +const Vue = require('vue/dist/vue.common.js') as VueConstructor import { onBeforeMount, onMounted, @@ -9,91 +9,91 @@ import { onUpdated, onBeforeUnmount, onUnmounted, -} from '../../../src'; -import { nextTick } from '../../helpers/utils'; +} from '../../../src' +import { nextTick } from '../../helpers/utils' // reference: https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks describe('api: lifecycle hooks', () => { it('onBeforeMount', () => { - const root = document.createElement('div'); + const root = document.createElement('div') const fn = jest.fn(() => { // should be called before inner div is rendered - expect(root.innerHTML).toBe(``); - }); + expect(root.innerHTML).toBe(``) + }) const Comp = { setup() { - onBeforeMount(fn); - return () => h('div'); + onBeforeMount(fn) + return () => h('div') }, - }; - new Vue(Comp).$mount(root); + } + new Vue(Comp).$mount(root) //render(h(Comp), root); - expect(fn).toHaveBeenCalledTimes(1); - }); + expect(fn).toHaveBeenCalledTimes(1) + }) it('onMounted', () => { - const root = document.createElement('div'); + const root = document.createElement('div') const fn = jest.fn(() => { // should be called after inner div is rendered - expect(root.outerHTML).toBe(`
`); - }); + expect(root.outerHTML).toBe(`
`) + }) const Comp = { setup() { - onMounted(fn); - return () => h('div'); + onMounted(fn) + return () => h('div') }, - }; - new Vue(Comp).$mount(root); + } + new Vue(Comp).$mount(root) //render(h(Comp), root); - expect(fn).toHaveBeenCalledTimes(1); - }); + expect(fn).toHaveBeenCalledTimes(1) + }) it('onBeforeUpdate', async () => { - const count = ref(0); + const count = ref(0) // const root = document.createElement('div'); const fn = jest.fn(() => { // should be called before inner div is updated - expect(vm.$el.outerHTML).toBe(`
0
`); - }); + expect(vm.$el.outerHTML).toBe(`
0
`) + }) const Comp = { setup() { - onBeforeUpdate(fn); - return () => h('div', (count.value as unknown) as string); + onBeforeUpdate(fn) + return () => h('div', (count.value as unknown) as string) }, - }; - const vm = new Vue(Comp).$mount(); + } + const vm = new Vue(Comp).$mount() //render(h(Comp), root); - count.value = 1; - await nextTick(); - expect(fn).toHaveBeenCalledTimes(1); - }); + count.value = 1 + await nextTick() + expect(fn).toHaveBeenCalledTimes(1) + }) it('onUpdated', async () => { - const count = ref(0); + const count = ref(0) // const root = document.createElement('div'); const fn = jest.fn(() => { // should be called after inner div is updated - expect(vm.$el.outerHTML).toBe(`
1
`); - }); + expect(vm.$el.outerHTML).toBe(`
1
`) + }) const Comp = { setup() { - onUpdated(fn); - return () => h('div', (count.value as unknown) as string); + onUpdated(fn) + return () => h('div', (count.value as unknown) as string) }, - }; - const vm = new Vue(Comp).$mount(); + } + const vm = new Vue(Comp).$mount() //render(h(Comp), root); - count.value++; - await nextTick(); - expect(fn).toHaveBeenCalledTimes(1); - }); + count.value++ + await nextTick() + expect(fn).toHaveBeenCalledTimes(1) + }) // it('onBeforeUnmount', async () => { // const toggle = ref(true); @@ -154,85 +154,85 @@ describe('api: lifecycle hooks', () => { // }); it('onBeforeUnmount in onMounted', async () => { - const toggle = ref(true); - const root = document.createElement('div'); + const toggle = ref(true) + const root = document.createElement('div') const fn = jest.fn(() => { // should be called before inner div is removed - expect(root.outerHTML).toBe(`
`); - }); + expect(root.outerHTML).toBe(`
`) + }) const Comp = { setup() { - return () => (toggle.value ? h(Child) : null); + return () => (toggle.value ? h(Child) : null) }, - }; + } const Child = { setup() { onMounted(() => { - onBeforeUnmount(fn); - }); - return () => h('div'); + onBeforeUnmount(fn) + }) + return () => h('div') }, - }; + } - new Vue(Comp).$mount(root); + new Vue(Comp).$mount(root) //render(h(Comp), root); - toggle.value = false; - await nextTick(); - expect(fn).toHaveBeenCalledTimes(1); - }); + toggle.value = false + await nextTick() + expect(fn).toHaveBeenCalledTimes(1) + }) it('lifecycle call order', async () => { - const count = ref(0); - const calls: string[] = []; + const count = ref(0) + const calls: string[] = [] const Root = { setup() { - onBeforeMount(() => calls.push('root onBeforeMount')); - onMounted(() => calls.push('root onMounted')); - onBeforeUpdate(() => calls.push('root onBeforeUpdate')); - onUpdated(() => calls.push('root onUpdated')); - onBeforeUnmount(() => calls.push('root onBeforeUnmount')); - onUnmounted(() => calls.push('root onUnmounted')); - return () => h(Mid, { props: { count: count.value } }); + onBeforeMount(() => calls.push('root onBeforeMount')) + onMounted(() => calls.push('root onMounted')) + onBeforeUpdate(() => calls.push('root onBeforeUpdate')) + onUpdated(() => calls.push('root onUpdated')) + onBeforeUnmount(() => calls.push('root onBeforeUnmount')) + onUnmounted(() => calls.push('root onUnmounted')) + return () => h(Mid, { props: { count: count.value } }) }, - }; + } const Mid = { props: { count: Number, }, setup(props: any) { - onBeforeMount(() => calls.push('mid onBeforeMount')); - onMounted(() => calls.push('mid onMounted')); - onBeforeUpdate(() => calls.push('mid onBeforeUpdate')); - onUpdated(() => calls.push('mid onUpdated')); - onBeforeUnmount(() => calls.push('mid onBeforeUnmount')); - onUnmounted(() => calls.push('mid onUnmounted')); - return () => h(Child, { props: { count: props.count } }); + onBeforeMount(() => calls.push('mid onBeforeMount')) + onMounted(() => calls.push('mid onMounted')) + onBeforeUpdate(() => calls.push('mid onBeforeUpdate')) + onUpdated(() => calls.push('mid onUpdated')) + onBeforeUnmount(() => calls.push('mid onBeforeUnmount')) + onUnmounted(() => calls.push('mid onUnmounted')) + return () => h(Child, { props: { count: props.count } }) }, - }; + } const Child = { props: { count: Number, }, setup(props: any) { - onBeforeMount(() => calls.push('child onBeforeMount')); - onMounted(() => calls.push('child onMounted')); - onBeforeUpdate(() => calls.push('child onBeforeUpdate')); - onUpdated(() => calls.push('child onUpdated')); - onBeforeUnmount(() => calls.push('child onBeforeUnmount')); - onUnmounted(() => calls.push('child onUnmounted')); - return () => h('div', props.count); + onBeforeMount(() => calls.push('child onBeforeMount')) + onMounted(() => calls.push('child onMounted')) + onBeforeUpdate(() => calls.push('child onBeforeUpdate')) + onUpdated(() => calls.push('child onUpdated')) + onBeforeUnmount(() => calls.push('child onBeforeUnmount')) + onUnmounted(() => calls.push('child onUnmounted')) + return () => h('div', props.count) }, - }; + } // mount // render(h(Root), root); - const vm = new Vue(Root); - vm.$mount(); + const vm = new Vue(Root) + vm.$mount() expect(calls).toEqual([ 'root onBeforeMount', 'mid onBeforeMount', @@ -240,15 +240,15 @@ describe('api: lifecycle hooks', () => { 'child onMounted', 'mid onMounted', 'root onMounted', - ]); + ]) - calls.length = 0; + calls.length = 0 // update - count.value++; - await nextTick(); - await nextTick(); - await nextTick(); + count.value++ + await nextTick() + await nextTick() + await nextTick() expect(calls).toEqual([ 'root onBeforeUpdate', 'mid onBeforeUpdate', @@ -256,13 +256,13 @@ describe('api: lifecycle hooks', () => { 'child onUpdated', 'mid onUpdated', 'root onUpdated', - ]); + ]) - calls.length = 0; + calls.length = 0 // unmount // render(null, root); - vm.$destroy(); + vm.$destroy() expect(calls).toEqual([ 'root onBeforeUnmount', 'mid onBeforeUnmount', @@ -270,8 +270,8 @@ describe('api: lifecycle hooks', () => { 'child onUnmounted', 'mid onUnmounted', 'root onUnmounted', - ]); - }); + ]) + }) // it('onRenderTracked', () => { // const events: DebuggerEvent[] = []; @@ -351,4 +351,4 @@ describe('api: lifecycle hooks', () => { // newValue: 3, // }); // }); -}); +}) diff --git a/test/v3/runtime-core/apiWatch.spec.ts b/test/v3/runtime-core/apiWatch.spec.ts index ec16a411..5a7e0efe 100644 --- a/test/v3/runtime-core/apiWatch.spec.ts +++ b/test/v3/runtime-core/apiWatch.spec.ts @@ -1,243 +1,251 @@ -import { watch, watchEffect, computed, reactive, ref, set, shallowReactive } from '../../../src'; -import { nextTick } from '../../helpers/utils'; -import Vue from 'vue'; +import { + watch, + watchEffect, + computed, + reactive, + ref, + set, + shallowReactive, +} from '../../../src' +import { nextTick } from '../../helpers/utils' +import Vue from 'vue' // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch describe('api: watch', () => { // const warnSpy = jest.spyOn(console, 'warn'); - const warnSpy = jest.spyOn((Vue as any).util, 'warn'); + const warnSpy = jest.spyOn((Vue as any).util, 'warn') beforeEach(() => { - warnSpy.mockReset(); - }); + warnSpy.mockReset() + }) it('effect', async () => { - const state = reactive({ count: 0 }); - let dummy; + const state = reactive({ count: 0 }) + let dummy watchEffect(() => { - dummy = state.count; - }); - expect(dummy).toBe(0); + dummy = state.count + }) + expect(dummy).toBe(0) - state.count++; - await nextTick(); - expect(dummy).toBe(1); - }); + state.count++ + await nextTick() + expect(dummy).toBe(1) + }) it('watching single source: getter', async () => { - const state = reactive({ count: 0 }); - let dummy; + const state = reactive({ count: 0 }) + let dummy watch( () => state.count, (count, prevCount) => { - dummy = [count, prevCount]; + dummy = [count, prevCount] // assert types - count + 1; + count + 1 if (prevCount) { - prevCount + 1; + prevCount + 1 } } - ); - state.count++; - await nextTick(); - expect(dummy).toMatchObject([1, 0]); - }); + ) + state.count++ + await nextTick() + expect(dummy).toMatchObject([1, 0]) + }) it('watching single source: ref', async () => { - const count = ref(0); - let dummy; + const count = ref(0) + let dummy watch(count, (count, prevCount) => { - dummy = [count, prevCount]; + dummy = [count, prevCount] // assert types - count + 1; + count + 1 if (prevCount) { - prevCount + 1; + prevCount + 1 } - }); - count.value++; - await nextTick(); - expect(dummy).toMatchObject([1, 0]); - }); + }) + count.value++ + await nextTick() + expect(dummy).toMatchObject([1, 0]) + }) it('watching single source: computed ref', async () => { - const count = ref(0); - const plus = computed(() => count.value + 1); - let dummy; + const count = ref(0) + const plus = computed(() => count.value + 1) + let dummy watch(plus, (count, prevCount) => { - dummy = [count, prevCount]; + dummy = [count, prevCount] // assert types - count + 1; + count + 1 if (prevCount) { - prevCount + 1; + prevCount + 1 } - }); - count.value++; - await nextTick(); - expect(dummy).toMatchObject([2, 1]); - }); + }) + count.value++ + await nextTick() + expect(dummy).toMatchObject([2, 1]) + }) it('watching primitive with deep: true', async () => { - const count = ref(0); - let dummy; + const count = ref(0) + let dummy watch( count, (c, prevCount) => { - dummy = [c, prevCount]; + dummy = [c, prevCount] }, { deep: true, } - ); - count.value++; - await nextTick(); - expect(dummy).toMatchObject([1, 0]); - }); + ) + count.value++ + await nextTick() + expect(dummy).toMatchObject([1, 0]) + }) it('directly watching reactive object (with automatic deep: true)', async () => { const src = reactive({ count: 0, - }); - let dummy; + }) + let dummy watch(src, ({ count }) => { - dummy = count; - }); - src.count++; - await nextTick(); - expect(dummy).toBe(1); - }); + dummy = count + }) + src.count++ + await nextTick() + expect(dummy).toBe(1) + }) it('watching multiple sources', async () => { - const state = reactive({ count: 1 }); - const count = ref(1); - const plus = computed(() => count.value + 1); + const state = reactive({ count: 1 }) + const count = ref(1) + const plus = computed(() => count.value + 1) - let dummy; + let dummy watch([() => state.count, count, plus], (vals, oldVals) => { - dummy = [vals, oldVals]; + dummy = [vals, oldVals] // assert types - vals.concat(1); - oldVals.concat(1); - }); + vals.concat(1) + oldVals.concat(1) + }) - state.count++; - count.value++; - await nextTick(); + state.count++ + count.value++ + await nextTick() expect(dummy).toMatchObject([ [2, 2, 3], [1, 1, 2], - ]); - }); + ]) + }) it('watching multiple sources: readonly array', async () => { - const state = reactive({ count: 1 }); - const status = ref(false); + const state = reactive({ count: 1 }) + const status = ref(false) - let dummy; + let dummy watch([() => state.count, status] as const, (vals, oldVals) => { - dummy = [vals, oldVals]; - const [count] = vals; - const [, oldStatus] = oldVals; + dummy = [vals, oldVals] + const [count] = vals + const [, oldStatus] = oldVals // assert types - count + 1; - oldStatus === true; - }); + count + 1 + oldStatus === true + }) - state.count++; - status.value = true; - await nextTick(); + state.count++ + status.value = true + await nextTick() expect(dummy).toMatchObject([ [2, true], [1, false], - ]); - }); + ]) + }) it('warn invalid watch source', () => { // @ts-ignore - watch(1, () => {}); + watch(1, () => {}) expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining('Invalid watch source'), expect.anything() - ); - }); + ) + }) it('stopping the watcher (effect)', async () => { - const state = reactive({ count: 0 }); - let dummy; + const state = reactive({ count: 0 }) + let dummy const stop = watchEffect(() => { - dummy = state.count; - }); - expect(dummy).toBe(0); + dummy = state.count + }) + expect(dummy).toBe(0) - stop(); - state.count++; - await nextTick(); + stop() + state.count++ + await nextTick() // should not update - expect(dummy).toBe(0); - }); + expect(dummy).toBe(0) + }) it('stopping the watcher (with source)', async () => { - const state = reactive({ count: 0 }); - let dummy; + const state = reactive({ count: 0 }) + let dummy const stop = watch( () => state.count, (count) => { - dummy = count; + dummy = count } - ); + ) - state.count++; - await nextTick(); - expect(dummy).toBe(1); + state.count++ + await nextTick() + expect(dummy).toBe(1) - stop(); - state.count++; - await nextTick(); + stop() + state.count++ + await nextTick() // should not update - expect(dummy).toBe(1); - }); + expect(dummy).toBe(1) + }) it('cleanup registration (effect)', async () => { - const state = reactive({ count: 0 }); - const cleanup = jest.fn(); - let dummy; + const state = reactive({ count: 0 }) + const cleanup = jest.fn() + let dummy const stop = watchEffect((onCleanup) => { - onCleanup(cleanup); - dummy = state.count; - }); - expect(dummy).toBe(0); + onCleanup(cleanup) + dummy = state.count + }) + expect(dummy).toBe(0) - state.count++; - await nextTick(); - expect(cleanup).toHaveBeenCalledTimes(1); - expect(dummy).toBe(1); + state.count++ + await nextTick() + expect(cleanup).toHaveBeenCalledTimes(1) + expect(dummy).toBe(1) - stop(); - expect(cleanup).toHaveBeenCalledTimes(2); - }); + stop() + expect(cleanup).toHaveBeenCalledTimes(2) + }) it('cleanup registration (with source)', async () => { - const count = ref(0); - const cleanup = jest.fn(); - let dummy; + const count = ref(0) + const cleanup = jest.fn() + let dummy const stop = watch(count, (count, prevCount, onCleanup) => { - onCleanup(cleanup); - dummy = count; - }); + onCleanup(cleanup) + dummy = count + }) - count.value++; - await nextTick(); - expect(cleanup).toHaveBeenCalledTimes(0); - expect(dummy).toBe(1); + count.value++ + await nextTick() + expect(cleanup).toHaveBeenCalledTimes(0) + expect(dummy).toBe(1) - count.value++; - await nextTick(); - expect(cleanup).toHaveBeenCalledTimes(1); - expect(dummy).toBe(2); + count.value++ + await nextTick() + expect(cleanup).toHaveBeenCalledTimes(1) + expect(dummy).toBe(2) - stop(); - expect(cleanup).toHaveBeenCalledTimes(2); - }); + stop() + expect(cleanup).toHaveBeenCalledTimes(2) + }) // it('flush timing: post (default)', async () => { // const count = ref(0); @@ -360,25 +368,30 @@ describe('api: watch', () => { ['b', 2], ]), set: new Set([1, 2, 3]), - }); + }) - let dummy; + let dummy watch( () => state, (state) => { - dummy = [state.nested.count, state.array[0], state.map.get('a'), state.set.has(1)]; + dummy = [ + state.nested.count, + state.array[0], + state.map.get('a'), + state.set.has(1), + ] }, { deep: true } - ); + ) - state.nested.count++; - await nextTick(); - expect(dummy).toEqual([1, 1, 1, true]); + state.nested.count++ + await nextTick() + expect(dummy).toEqual([1, 1, 1, true]) // nested array mutation - set(state.array, '0', 2); - await nextTick(); - expect(dummy).toEqual([1, 2, 1, true]); + set(state.array, '0', 2) + await nextTick() + expect(dummy).toEqual([1, 2, 1, true]) // NOT supported by Vue.observe :( // // nested map mutation @@ -390,81 +403,81 @@ describe('api: watch', () => { // state.set.delete(1); // await nextTick(); // expect(dummy).toEqual([1, 2, 2, false]); - }); + }) it('immediate', async () => { - const count = ref(0); - const cb = jest.fn(); - watch(count, cb, { immediate: true }); - expect(cb).toHaveBeenCalledTimes(1); - count.value++; - await nextTick(); - expect(cb).toHaveBeenCalledTimes(2); - }); + const count = ref(0) + const cb = jest.fn() + watch(count, cb, { immediate: true }) + expect(cb).toHaveBeenCalledTimes(1) + count.value++ + await nextTick() + expect(cb).toHaveBeenCalledTimes(2) + }) it('immediate: triggers when initial value is null', async () => { - const state = ref(null); - const spy = jest.fn(); - watch(() => state.value, spy, { immediate: true }); - expect(spy).toHaveBeenCalled(); - }); + const state = ref(null) + const spy = jest.fn() + watch(() => state.value, spy, { immediate: true }) + expect(spy).toHaveBeenCalled() + }) it('immediate: triggers when initial value is undefined', async () => { - const state = ref(); - const spy = jest.fn(); - watch(() => state.value, spy, { immediate: true }); - expect(spy).toHaveBeenCalled(); - state.value = 3; - await nextTick(); - expect(spy).toHaveBeenCalledTimes(2); + const state = ref() + const spy = jest.fn() + watch(() => state.value, spy, { immediate: true }) + expect(spy).toHaveBeenCalled() + state.value = 3 + await nextTick() + expect(spy).toHaveBeenCalledTimes(2) // testing if undefined can trigger the watcher - state.value = undefined; - await nextTick(); - expect(spy).toHaveBeenCalledTimes(3); + state.value = undefined + await nextTick() + expect(spy).toHaveBeenCalledTimes(3) // it shouldn't trigger if the same value is set - state.value = undefined; - await nextTick(); - expect(spy).toHaveBeenCalledTimes(3); - }); + state.value = undefined + await nextTick() + expect(spy).toHaveBeenCalledTimes(3) + }) it('shallow reactive effect', async () => { - const state = shallowReactive({ count: 0 }); - let dummy; + const state = shallowReactive({ count: 0 }) + let dummy watch( () => state.count, () => { - dummy = state.count; + dummy = state.count }, { immediate: true } - ); - expect(dummy).toBe(0); + ) + expect(dummy).toBe(0) - state.count++; - await nextTick(); - expect(dummy).toBe(1); - }); + state.count++ + await nextTick() + expect(dummy).toBe(1) + }) it('shallow reactive object', async () => { - const state = shallowReactive({ a: { count: 0 } }); - let dummy; + const state = shallowReactive({ a: { count: 0 } }) + let dummy watch( () => state.a, () => { - dummy = state.a.count; + dummy = state.a.count }, { immediate: true } - ); - expect(dummy).toBe(0); + ) + expect(dummy).toBe(0) - state.a.count++; - await nextTick(); - expect(dummy).toBe(0); + state.a.count++ + await nextTick() + expect(dummy).toBe(0) - state.a = { count: 5 }; - await nextTick(); + state.a = { count: 5 } + await nextTick() - expect(dummy).toBe(5); - }); + expect(dummy).toBe(5) + }) // it('warn immediate option when using effect', async () => { // const count = ref(0); @@ -501,4 +514,4 @@ describe('api: watch', () => { // expect(spy).toHaveBeenCalledTimes(1); // expect(warnSpy).toHaveBeenCalledWith(`"deep" option is only respected`); // }); -}); +})