diff --git a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts index 1b259f7ca68..887d60239dc 100644 --- a/packages/compiler-core/__tests__/transforms/vMemo.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vMemo.spec.ts @@ -1,56 +1,75 @@ -import { baseCompile } from '../../src' - -describe('compiler: v-memo transform', () => { - function compile(content: string) { - return baseCompile(`
${content}
`, { - mode: 'module', - prefixIdentifiers: true - }).code - } - - test('on root element', () => { - expect( - baseCompile(`
`, { - mode: 'module', - prefixIdentifiers: true - }).code - ).toMatchSnapshot() - }) +import { baseCompile, CompilerOptions, ErrorCodes } from '../../src' - test('on normal element', () => { - expect(compile(`
`)).toMatchSnapshot() - }) +function compile(content: string, options: CompilerOptions = {}) { + return baseCompile(`
${content}
`, { + mode: 'module', + prefixIdentifiers: true, + ...options + }).code +} - test('on component', () => { - expect(compile(``)).toMatchSnapshot() - }) +describe('compiler: v-memo', () => { + describe('transform', () => { + test('on root element', () => { + expect( + baseCompile(`
`, { + mode: 'module', + prefixIdentifiers: true + }).code + ).toMatchSnapshot() + }) - test('on v-if', () => { - expect( - compile( - `
foobar
- ` - ) - ).toMatchSnapshot() - }) + test('on normal element', () => { + expect(compile(`
`)).toMatchSnapshot() + }) - test('on v-for', () => { - expect( - compile( - `
- foobar -
` - ) - ).toMatchSnapshot() + test('on component', () => { + expect(compile(``)).toMatchSnapshot() + }) + + test('on v-if', () => { + expect( + compile( + `
foobar
+ ` + ) + ).toMatchSnapshot() + }) + + test('on v-for', () => { + expect( + compile( + `
+ foobar +
` + ) + ).toMatchSnapshot() + }) + + test('on template v-for', () => { + expect( + compile( + `` + ) + ).toMatchSnapshot() + }) }) - test('on template v-for', () => { - expect( - compile( - `` + describe('errors', () => { + test('inside v-for', () => { + const onError = jest.fn() + compile(`
`, { + onError + }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_V_MEMO_INSIDE_FOR + }) ) - ).toMatchSnapshot() + }) }) }) diff --git a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts index 553ac1dfac4..3dba263387d 100644 --- a/packages/compiler-core/__tests__/transforms/vOnce.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vOnce.spec.ts @@ -4,7 +4,8 @@ import { NodeTypes, generate, CompilerOptions, - getBaseTransformPreset + getBaseTransformPreset, + ErrorCodes } from '../../src' import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers' @@ -19,127 +20,145 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) { return ast } -describe('compiler: v-once transform', () => { - test('as root node', () => { - const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect(root.codegenNode).toMatchObject({ - type: NodeTypes.JS_CACHE_EXPRESSION, - index: 0, - value: { - type: NodeTypes.VNODE_CALL, - tag: `"div"` - } +describe('compiler: v-once', () => { + describe('transform', () => { + test('as root node', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `"div"` + } + }) + expect(generate(root).code).toMatchSnapshot() }) - expect(generate(root).code).toMatchSnapshot() - }) - test('on nested plain element', () => { - const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ - type: NodeTypes.JS_CACHE_EXPRESSION, - index: 0, - value: { - type: NodeTypes.VNODE_CALL, - tag: `"div"` - } + test('on nested plain element', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `"div"` + } + }) + expect(generate(root).code).toMatchSnapshot() }) - expect(generate(root).code).toMatchSnapshot() - }) - test('on component', () => { - const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ - type: NodeTypes.JS_CACHE_EXPRESSION, - index: 0, - value: { - type: NodeTypes.VNODE_CALL, - tag: `_component_Comp` - } + test('on component', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.VNODE_CALL, + tag: `_component_Comp` + } + }) + expect(generate(root).code).toMatchSnapshot() }) - expect(generate(root).code).toMatchSnapshot() - }) - test('on slot outlet', () => { - const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ - type: NodeTypes.JS_CACHE_EXPRESSION, - index: 0, - value: { - type: NodeTypes.JS_CALL_EXPRESSION, - callee: RENDER_SLOT - } + test('on slot outlet', () => { + const root = transformWithOnce(`
`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ + type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, + value: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: RENDER_SLOT + } + }) + expect(generate(root).code).toMatchSnapshot() }) - expect(generate(root).code).toMatchSnapshot() - }) - - // v-once inside v-once should not be cached - test('inside v-once', () => { - const root = transformWithOnce(`
`) - expect(root.cached).not.toBe(2) - expect(root.cached).toBe(1) - }) - // cached nodes should be ignored by hoistStatic transform - test('with hoistStatic: true', () => { - const root = transformWithOnce(`
`, { - hoistStatic: true + // v-once inside v-once should not be cached + test('inside v-once', () => { + const root = transformWithOnce(`
`) + expect(root.cached).not.toBe(2) + expect(root.cached).toBe(1) }) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect(root.hoists.length).toBe(0) - expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ - type: NodeTypes.JS_CACHE_EXPRESSION, - index: 0, - value: { - type: NodeTypes.VNODE_CALL, - tag: `"div"` - } - }) - expect(generate(root).code).toMatchSnapshot() - }) - test('with v-if/else', () => { - const root = transformWithOnce(`

`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect(root.children[0]).toMatchObject({ - type: NodeTypes.IF, - // should cache the entire v-if/else-if/else expression, not just a single branch - codegenNode: { + // cached nodes should be ignored by hoistStatic transform + test('with hoistStatic: true', () => { + const root = transformWithOnce(`

`, { + hoistStatic: true + }) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.hoists.length).toBe(0) + expect((root.children[0] as any).children[0].codegenNode).toMatchObject({ type: NodeTypes.JS_CACHE_EXPRESSION, + index: 0, value: { - type: NodeTypes.JS_CONDITIONAL_EXPRESSION, - consequent: { - type: NodeTypes.VNODE_CALL, - tag: `"div"` - }, - alternate: { - type: NodeTypes.VNODE_CALL, - tag: `"p"` + type: NodeTypes.VNODE_CALL, + tag: `"div"` + } + }) + expect(generate(root).code).toMatchSnapshot() + }) + + test('with v-if/else', () => { + const root = transformWithOnce(`

`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.IF, + // should cache the entire v-if/else-if/else expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION, + value: { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION, + consequent: { + type: NodeTypes.VNODE_CALL, + tag: `"div"` + }, + alternate: { + type: NodeTypes.VNODE_CALL, + tag: `"p"` + } } } - } + }) + }) + + test('with v-for', () => { + const root = transformWithOnce(`

`) + expect(root.cached).toBe(1) + expect(root.helpers).toContain(SET_BLOCK_TRACKING) + expect(root.children[0]).toMatchObject({ + type: NodeTypes.FOR, + // should cache the entire v-for expression, not just a single branch + codegenNode: { + type: NodeTypes.JS_CACHE_EXPRESSION + } + }) }) }) - test('with v-for', () => { - const root = transformWithOnce(`
`) - expect(root.cached).toBe(1) - expect(root.helpers).toContain(SET_BLOCK_TRACKING) - expect(root.children[0]).toMatchObject({ - type: NodeTypes.FOR, - // should cache the entire v-for expression, not just a single branch - codegenNode: { - type: NodeTypes.JS_CACHE_EXPRESSION - } + describe('errors', () => { + test('inside v-for', () => { + const onError = jest.fn() + transformWithOnce(`
`, { + onError + }) + + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCodes.X_V_ONCE_INSIDE_FOR + }) + ) }) }) }) diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 09ec7290135..7472bb32edf 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -76,6 +76,8 @@ export const enum ErrorCodes { X_V_FOR_NO_EXPRESSION, X_V_FOR_MALFORMED_EXPRESSION, X_V_FOR_TEMPLATE_KEY_PLACEMENT, + X_V_ONCE_INSIDE_FOR, + X_V_MEMO_INSIDE_FOR, X_V_BIND_NO_EXPRESSION, X_V_ON_NO_EXPRESSION, X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET, @@ -148,6 +150,8 @@ export const errorMessages: Record = { [ErrorCodes.X_V_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`, [ErrorCodes.X_V_IF_SAME_KEY]: `v-if/else branches must use unique keys.`, [ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`, + [ErrorCodes.X_V_MEMO_INSIDE_FOR]: `v-memo cannot be used inside v-for.`, + [ErrorCodes.X_V_ONCE_INSIDE_FOR]: `v-once cannot be used inside v-for.`, [ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`, [ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`, [ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `