From: Evan You Date: Mon, 16 Dec 2019 22:57:34 +0000 (-0500) Subject: test: test hot module replacement X-Git-Tag: v3.0.0-alpha.0~30 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8ea210155399f3ae8753773dbe1bb440a70db5ce;p=thirdparty%2Fvuejs%2Fcore.git test: test hot module replacement --- diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts new file mode 100644 index 0000000000..4da8d0db4b --- /dev/null +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -0,0 +1,150 @@ +import { HMRRuntime } from '../src/hmr' +import '../src/hmr' +import { ComponentOptions, RenderFunction } from '../src/component' +import { + render, + nodeOps, + h, + serializeInner, + triggerEvent, + TestElement, + nextTick +} from '@vue/runtime-test' +import * as runtimeTest from '@vue/runtime-test' +import { baseCompile } from '@vue/compiler-core' + +declare var __VUE_HMR_RUNTIME__: HMRRuntime +const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__ + +function compileToFunction(template: string) { + const { code } = baseCompile(template) + const render = new Function('Vue', code)(runtimeTest) as RenderFunction + render.isRuntimeCompiled = true + return render +} + +describe('hot module replacement', () => { + test('inject global runtime', () => { + expect(createRecord).toBeDefined() + expect(rerender).toBeDefined() + expect(reload).toBeDefined() + }) + + test('createRecord', () => { + expect(createRecord('test1', {})).toBe(true) + // if id has already been created, should return false + expect(createRecord('test1', {})).toBe(false) + }) + + test('rerender', async () => { + const root = nodeOps.createElement('div') + const parentId = 'test2-parent' + const childId = 'test2-child' + + const Child: ComponentOptions = { + __hmrId: childId, + render: compileToFunction(``) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + __hmrId: parentId, + data() { + return { count: 0 } + }, + components: { Child }, + render: compileToFunction( + `
{{ count }}{{ count }}
` + ) + } + createRecord(parentId, Parent) + + render(h(Parent), root) + expect(serializeInner(root)).toBe(`
00
`) + + // Perform some state change. This change should be preserved after the + // re-render! + triggerEvent(root.children[0] as TestElement, 'click') + await nextTick() + expect(serializeInner(root)).toBe(`
11
`) + + // Update text while preserving state + rerender( + parentId, + compileToFunction( + `
{{ count }}!{{ count }}
` + ) + ) + expect(serializeInner(root)).toBe(`
1!1
`) + + // Should force child update on slot content change + rerender( + parentId, + compileToFunction( + `
{{ count }}!{{ count }}!
` + ) + ) + expect(serializeInner(root)).toBe(`
1!1!
`) + + // Should force update element children despite block optimization + rerender( + parentId, + compileToFunction( + `
{{ count }}{{ count }} + {{ count }}! +
` + ) + ) + expect(serializeInner(root)).toBe( + `
111!
` + ) + + // Should force update child slot elements + rerender( + parentId, + compileToFunction( + `
+ {{ count }} +
` + ) + ) + expect(serializeInner(root)).toBe(`
1
`) + }) + + test('reload', async () => { + const root = nodeOps.createElement('div') + const childId = 'test3-child' + const unmoutSpy = jest.fn() + const mountSpy = jest.fn() + + const Child: ComponentOptions = { + __hmrId: childId, + data() { + return { count: 0 } + }, + unmounted: unmoutSpy, + render: compileToFunction(`
{{ count }}
`) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + render: () => h(Child) + } + + render(h(Parent), root) + expect(serializeInner(root)).toBe(`
0
`) + + reload(childId, { + __hmrId: childId, + data() { + return { count: 1 } + }, + mounted: mountSpy, + render: compileToFunction(`
{{ count }}
`) + }) + await nextTick() + expect(serializeInner(root)).toBe(`
1
`) + expect(unmoutSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 7c10defbab..940f01d028 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -5,6 +5,12 @@ import { } from './component' import { queueJob, queuePostFlushCb } from './scheduler' +export interface HMRRuntime { + createRecord: typeof createRecord + rerender: typeof rerender + reload: typeof reload +} + // Expose the HMR runtime on the global object // This makes it entirely tree-shakable without polluting the exports and makes // it easier to be used in toolings like vue-loader @@ -24,7 +30,7 @@ if (__BUNDLER__ && __DEV__) { createRecord: tryWrap(createRecord), rerender: tryWrap(rerender), reload: tryWrap(reload) - } + } as HMRRuntime } interface HMRRecord { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index aa624d5ef7..dbf89cba74 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -130,3 +130,4 @@ export { DirectiveArguments } from './directives' export { SuspenseBoundary } from './components/Suspense' +export { HMRRuntime } from './hmr' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 8543e6ae76..133a21c246 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -475,6 +475,7 @@ export function createRenderer< // HMR updated, force full diff patchFlag = 0 optimized = false + dynamicChildren = null } if (patchFlag > 0) { @@ -593,6 +594,9 @@ export function createRenderer< ) { for (let i = 0; i < newChildren.length; i++) { const oldVNode = oldChildren[i] + if (!oldVNode) { + debugger + } patch( oldVNode, newChildren[i], @@ -682,7 +686,7 @@ export function createRenderer< ? n1.anchor : hostCreateComment(showID ? `fragment-${devFragmentID}-end` : ''))! - let { patchFlag } = n2 + let { patchFlag, dynamicChildren } = n2 if (patchFlag > 0) { optimized = true } @@ -691,6 +695,7 @@ export function createRenderer< // HMR updated, force full diff patchFlag = 0 optimized = false + dynamicChildren = null } if (n1 == null) { @@ -712,12 +717,12 @@ export function createRenderer< optimized ) } else { - if (patchFlag & PatchFlags.STABLE_FRAGMENT && n2.dynamicChildren) { + if (patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren != null) { // a stable fragment (template root or