]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: nest v-for tests
authorEvan You <yyx990803@gmail.com>
Thu, 19 Sep 2019 17:36:27 +0000 (13:36 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 19 Sep 2019 17:36:27 +0000 (13:36 -0400)
packages/compiler-core/__tests__/directives/vFor.spec.ts

index 9a937d457a3b0cf4922be70b331a7a8437bc0b1c..b23537e373c6bc085b31f2ecc58dae27d4b7e811 100644 (file)
@@ -5,292 +5,141 @@ import { ForNode, NodeTypes } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
 
 describe('v-for', () => {
-  test('number expression', () => {
-    const node = parse('<span v-for="index in 5" />')
+  describe('transform', () => {
+    test('number expression', () => {
+      const node = parse('<span v-for="index in 5" />')
 
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('index')
-    expect(forNode.source.content).toBe('5')
-  })
-
-  test('value', () => {
-    const node = parse('<span v-for="(item) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('item')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('object de-structured value', () => {
-    const node = parse('<span v-for="({ id, value }) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('{ id, value }')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('array de-structured value', () => {
-    const node = parse('<span v-for="([ id, value ]) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('[ id, value ]')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('value and key', () => {
-    const node = parse('<span v-for="(item, key) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.keyAlias).not.toBeUndefined()
-    expect(forNode.keyAlias!.content).toBe('key')
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('item')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('value, key and index', () => {
-    const node = parse('<span v-for="(value, key, index) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).not.toBeUndefined()
-    expect(forNode.keyAlias!.content).toBe('key')
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias!.content).toBe('value')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('skipped key', () => {
-    const node = parse('<span v-for="(value,,index) in items" />')
-
-    transform(node, { transforms: [transformFor] })
-
-    expect(node.children.length).toBe(1)
-
-    const forNode = node.children[0] as ForNode
-
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias!.content).toBe('value')
-    expect(forNode.source.content).toBe('items')
-  })
-
-  test('skipped value and key', () => {
-    const node = parse('<span v-for="(,,index) in items" />')
-
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias).toBeUndefined()
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).toBeUndefined()
+      expect(forNode.valueAlias!.content).toBe('index')
+      expect(forNode.source.content).toBe('5')
+    })
 
-  test('unbracketed value', () => {
-    const node = parse('<span v-for="item in items" />')
+    test('value', () => {
+      const node = parse('<span v-for="(item) in items" />')
 
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('item')
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).toBeUndefined()
+      expect(forNode.valueAlias!.content).toBe('item')
+      expect(forNode.source.content).toBe('items')
+    })
 
-  test('unbracketed value and key', () => {
-    const node = parse('<span v-for="item, key in items" />')
+    test('object de-structured value', () => {
+      const node = parse('<span v-for="({ id, value }) in items" />')
 
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).not.toBeUndefined()
-    expect(forNode.keyAlias!.content).toBe('key')
-    expect(forNode.objectIndexAlias).toBeUndefined()
-    expect(forNode.valueAlias!.content).toBe('item')
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).toBeUndefined()
+      expect(forNode.valueAlias!.content).toBe('{ id, value }')
+      expect(forNode.source.content).toBe('items')
+    })
 
-  test('unbracketed value, key and index', () => {
-    const node = parse('<span v-for="value, key, index in items" />')
+    test('array de-structured value', () => {
+      const node = parse('<span v-for="([ id, value ]) in items" />')
 
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).not.toBeUndefined()
-    expect(forNode.keyAlias!.content).toBe('key')
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias!.content).toBe('value')
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).toBeUndefined()
+      expect(forNode.valueAlias!.content).toBe('[ id, value ]')
+      expect(forNode.source.content).toBe('items')
+    })
 
-  test('unbracketed skipped key', () => {
-    const node = parse('<span v-for="value, , index in items" />')
+    test('value and key', () => {
+      const node = parse('<span v-for="(item, key) in items" />')
 
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias!.content).toBe('value')
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.keyAlias).not.toBeUndefined()
+      expect(forNode.keyAlias!.content).toBe('key')
+      expect(forNode.objectIndexAlias).toBeUndefined()
+      expect(forNode.valueAlias!.content).toBe('item')
+      expect(forNode.source.content).toBe('items')
+    })
 
-  test('unbracketed skipped value and key', () => {
-    const node = parse('<span v-for=", , index in items" />')
+    test('value, key and index', () => {
+      const node = parse('<span v-for="(value, key, index) in items" />')
 
-    transform(node, { transforms: [transformFor] })
+      transform(node, { transforms: [transformFor] })
 
-    expect(node.children.length).toBe(1)
+      expect(node.children.length).toBe(1)
 
-    const forNode = node.children[0] as ForNode
+      const forNode = node.children[0] as ForNode
 
-    expect(forNode.type).toBe(NodeTypes.FOR)
-    expect(forNode.keyAlias).toBeUndefined()
-    expect(forNode.objectIndexAlias).not.toBeUndefined()
-    expect(forNode.objectIndexAlias!.content).toBe('index')
-    expect(forNode.valueAlias).toBeUndefined()
-    expect(forNode.source.content).toBe('items')
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).not.toBeUndefined()
+      expect(forNode.keyAlias!.content).toBe('key')
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
+      expect(forNode.objectIndexAlias!.content).toBe('index')
+      expect(forNode.valueAlias!.content).toBe('value')
+      expect(forNode.source.content).toBe('items')
+    })
 
-  test('missing expression', () => {
-    const node = parse('<span v-for />')
-    const onError = jest.fn()
-    transform(node, { transforms: [transformFor], onError })
+    test('skipped key', () => {
+      const node = parse('<span v-for="(value,,index) in items" />')
 
-    expect(onError).toHaveBeenCalledTimes(1)
-    expect(onError).toHaveBeenCalledWith(
-      expect.objectContaining({
-        code: ErrorCodes.X_FOR_NO_EXPRESSION
-      })
-    )
-  })
+      transform(node, { transforms: [transformFor] })
 
-  test('empty expression', () => {
-    const node = parse('<span v-for="" />')
-    const onError = jest.fn()
-    transform(node, { transforms: [transformFor], onError })
+      expect(node.children.length).toBe(1)
 
-    expect(onError).toHaveBeenCalledTimes(1)
-    expect(onError).toHaveBeenCalledWith(
-      expect.objectContaining({
-        code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
-      })
-    )
-  })
+      const forNode = node.children[0] as ForNode
 
-  test('invalid expression', () => {
-    const node = parse('<span v-for="items" />')
-    const onError = jest.fn()
-    transform(node, { transforms: [transformFor], onError })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
+      expect(forNode.objectIndexAlias!.content).toBe('index')
+      expect(forNode.valueAlias!.content).toBe('value')
+      expect(forNode.source.content).toBe('items')
+    })
 
-    expect(onError).toHaveBeenCalledTimes(1)
-    expect(onError).toHaveBeenCalledWith(
-      expect.objectContaining({
-        code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
-      })
-    )
-  })
+    test('skipped value and key', () => {
+      const node = parse('<span v-for="(,,index) in items" />')
 
-  test('missing source', () => {
-    const node = parse('<span v-for="item in" />')
-    const onError = jest.fn()
-    transform(node, { transforms: [transformFor], onError })
+      transform(node, { transforms: [transformFor] })
 
-    expect(onError).toHaveBeenCalledTimes(1)
-    expect(onError).toHaveBeenCalledWith(
-      expect.objectContaining({
-        code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
-      })
-    )
-  })
+      expect(node.children.length).toBe(1)
 
-  test('missing value', () => {
-    const node = parse('<span v-for="in items" />')
-    const onError = jest.fn()
-    transform(node, { transforms: [transformFor], onError })
+      const forNode = node.children[0] as ForNode
 
-    expect(onError).toHaveBeenCalledTimes(1)
-    expect(onError).toHaveBeenCalledWith(
-      expect.objectContaining({
-        code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
-      })
-    )
-  })
+      expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
+      expect(forNode.objectIndexAlias!.content).toBe('index')
+      expect(forNode.valueAlias).toBeUndefined()
+      expect(forNode.source.content).toBe('items')
+    })
 
-  describe('source location', () => {
-    test('value & source', () => {
-      const source = '<span v-for="item in items" />'
-      const node = parse(source)
+    test('unbracketed value', () => {
+      const node = parse('<span v-for="item in items" />')
 
       transform(node, { transforms: [transformFor] })
 
@@ -299,29 +148,14 @@ describe('v-for', () => {
       const forNode = node.children[0] as ForNode
 
       expect(forNode.type).toBe(NodeTypes.FOR)
-
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).toBeUndefined()
       expect(forNode.valueAlias!.content).toBe('item')
-      expect(forNode.valueAlias!.loc.start.offset).toBe(
-        source.indexOf('item') - 1
-      )
-      expect(forNode.valueAlias!.loc.start.line).toBe(1)
-      expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
-      expect(forNode.valueAlias!.loc.end.line).toBe(1)
-      expect(forNode.valueAlias!.loc.end.column).toBe(
-        source.indexOf('item') + 4
-      )
-
       expect(forNode.source.content).toBe('items')
-      expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
-      expect(forNode.source.loc.start.line).toBe(1)
-      expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
-      expect(forNode.source.loc.end.line).toBe(1)
-      expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
     })
 
-    test('bracketed value', () => {
-      const source = '<span v-for="( item ) in items" />'
-      const node = parse(source)
+    test('unbracketed value and key', () => {
+      const node = parse('<span v-for="item, key in items" />')
 
       transform(node, { transforms: [transformFor] })
 
@@ -330,29 +164,15 @@ describe('v-for', () => {
       const forNode = node.children[0] as ForNode
 
       expect(forNode.type).toBe(NodeTypes.FOR)
-
+      expect(forNode.keyAlias).not.toBeUndefined()
+      expect(forNode.keyAlias!.content).toBe('key')
+      expect(forNode.objectIndexAlias).toBeUndefined()
       expect(forNode.valueAlias!.content).toBe('item')
-      expect(forNode.valueAlias!.loc.start.offset).toBe(
-        source.indexOf('item') - 1
-      )
-      expect(forNode.valueAlias!.loc.start.line).toBe(1)
-      expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
-      expect(forNode.valueAlias!.loc.end.line).toBe(1)
-      expect(forNode.valueAlias!.loc.end.column).toBe(
-        source.indexOf('item') + 4
-      )
-
       expect(forNode.source.content).toBe('items')
-      expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
-      expect(forNode.source.loc.start.line).toBe(1)
-      expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
-      expect(forNode.source.loc.end.line).toBe(1)
-      expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
     })
 
-    test('de-structured value', () => {
-      const source = '<span v-for="(  { id, key })in items" />'
-      const node = parse(source)
+    test('unbracketed value, key and index', () => {
+      const node = parse('<span v-for="value, key, index in items" />')
 
       transform(node, { transforms: [transformFor] })
 
@@ -361,31 +181,16 @@ describe('v-for', () => {
       const forNode = node.children[0] as ForNode
 
       expect(forNode.type).toBe(NodeTypes.FOR)
-
-      expect(forNode.valueAlias!.content).toBe('{ id, key }')
-      expect(forNode.valueAlias!.loc.start.offset).toBe(
-        source.indexOf('{ id, key }') - 1
-      )
-      expect(forNode.valueAlias!.loc.start.line).toBe(1)
-      expect(forNode.valueAlias!.loc.start.column).toBe(
-        source.indexOf('{ id, key }')
-      )
-      expect(forNode.valueAlias!.loc.end.line).toBe(1)
-      expect(forNode.valueAlias!.loc.end.column).toBe(
-        source.indexOf('{ id, key }') + '{ id, key }'.length
-      )
-
+      expect(forNode.keyAlias).not.toBeUndefined()
+      expect(forNode.keyAlias!.content).toBe('key')
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
+      expect(forNode.objectIndexAlias!.content).toBe('index')
+      expect(forNode.valueAlias!.content).toBe('value')
       expect(forNode.source.content).toBe('items')
-      expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
-      expect(forNode.source.loc.start.line).toBe(1)
-      expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
-      expect(forNode.source.loc.end.line).toBe(1)
-      expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
     })
 
-    test('bracketed value, key, index', () => {
-      const source = '<span v-for="( item, key, index ) in items" />'
-      const node = parse(source)
+    test('unbracketed skipped key', () => {
+      const node = parse('<span v-for="value, , index in items" />')
 
       transform(node, { transforms: [transformFor] })
 
@@ -394,48 +199,15 @@ describe('v-for', () => {
       const forNode = node.children[0] as ForNode
 
       expect(forNode.type).toBe(NodeTypes.FOR)
-
-      expect(forNode.valueAlias!.content).toBe('item')
-      expect(forNode.valueAlias!.loc.start.offset).toBe(
-        source.indexOf('item') - 1
-      )
-      expect(forNode.valueAlias!.loc.start.line).toBe(1)
-      expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
-      expect(forNode.valueAlias!.loc.end.line).toBe(1)
-      expect(forNode.valueAlias!.loc.end.column).toBe(
-        source.indexOf('item') + 4
-      )
-
-      expect(forNode.keyAlias!.content).toBe('key')
-      expect(forNode.keyAlias!.loc.start.offset).toBe(source.indexOf('key') - 1)
-      expect(forNode.keyAlias!.loc.start.line).toBe(1)
-      expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key'))
-      expect(forNode.keyAlias!.loc.end.line).toBe(1)
-      expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3)
-
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
       expect(forNode.objectIndexAlias!.content).toBe('index')
-      expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
-        source.indexOf('index') - 1
-      )
-      expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
-      expect(forNode.objectIndexAlias!.loc.start.column).toBe(
-        source.indexOf('index')
-      )
-      expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
-      expect(forNode.objectIndexAlias!.loc.end.column).toBe(
-        source.indexOf('index') + 5
-      )
-
+      expect(forNode.valueAlias!.content).toBe('value')
       expect(forNode.source.content).toBe('items')
-      expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
-      expect(forNode.source.loc.start.line).toBe(1)
-      expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
-      expect(forNode.source.loc.end.line).toBe(1)
-      expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
     })
-    test('skipped key', () => {
-      const source = '<span v-for="( item,, index ) in items" />'
-      const node = parse(source)
+
+    test('unbracketed skipped value and key', () => {
+      const node = parse('<span v-for=", , index in items" />')
 
       transform(node, { transforms: [transformFor] })
 
@@ -444,37 +216,288 @@ describe('v-for', () => {
       const forNode = node.children[0] as ForNode
 
       expect(forNode.type).toBe(NodeTypes.FOR)
+      expect(forNode.keyAlias).toBeUndefined()
+      expect(forNode.objectIndexAlias).not.toBeUndefined()
+      expect(forNode.objectIndexAlias!.content).toBe('index')
+      expect(forNode.valueAlias).toBeUndefined()
+      expect(forNode.source.content).toBe('items')
+    })
 
-      expect(forNode.valueAlias!.content).toBe('item')
-      expect(forNode.valueAlias!.loc.start.offset).toBe(
-        source.indexOf('item') - 1
+    test('missing expression', () => {
+      const node = parse('<span v-for />')
+      const onError = jest.fn()
+      transform(node, { transforms: [transformFor], onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: ErrorCodes.X_FOR_NO_EXPRESSION
+        })
       )
-      expect(forNode.valueAlias!.loc.start.line).toBe(1)
-      expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
-      expect(forNode.valueAlias!.loc.end.line).toBe(1)
-      expect(forNode.valueAlias!.loc.end.column).toBe(
-        source.indexOf('item') + 4
+    })
+
+    test('empty expression', () => {
+      const node = parse('<span v-for="" />')
+      const onError = jest.fn()
+      transform(node, { transforms: [transformFor], onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
+        })
       )
+    })
 
-      expect(forNode.objectIndexAlias!.content).toBe('index')
-      expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
-        source.indexOf('index') - 1
+    test('invalid expression', () => {
+      const node = parse('<span v-for="items" />')
+      const onError = jest.fn()
+      transform(node, { transforms: [transformFor], onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
+        })
       )
-      expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
-      expect(forNode.objectIndexAlias!.loc.start.column).toBe(
-        source.indexOf('index')
+    })
+
+    test('missing source', () => {
+      const node = parse('<span v-for="item in" />')
+      const onError = jest.fn()
+      transform(node, { transforms: [transformFor], onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
+        })
       )
-      expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
-      expect(forNode.objectIndexAlias!.loc.end.column).toBe(
-        source.indexOf('index') + 5
+    })
+
+    test('missing value', () => {
+      const node = parse('<span v-for="in items" />')
+      const onError = jest.fn()
+      transform(node, { transforms: [transformFor], onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
+        })
       )
+    })
 
-      expect(forNode.source.content).toBe('items')
-      expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
-      expect(forNode.source.loc.start.line).toBe(1)
-      expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
-      expect(forNode.source.loc.end.line).toBe(1)
-      expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+    describe('source location', () => {
+      test('value & source', () => {
+        const source = '<span v-for="item in items" />'
+        const node = parse(source)
+
+        transform(node, { transforms: [transformFor] })
+
+        expect(node.children.length).toBe(1)
+
+        const forNode = node.children[0] as ForNode
+
+        expect(forNode.type).toBe(NodeTypes.FOR)
+
+        expect(forNode.valueAlias!.content).toBe('item')
+        expect(forNode.valueAlias!.loc.start.offset).toBe(
+          source.indexOf('item') - 1
+        )
+        expect(forNode.valueAlias!.loc.start.line).toBe(1)
+        expect(forNode.valueAlias!.loc.start.column).toBe(
+          source.indexOf('item')
+        )
+        expect(forNode.valueAlias!.loc.end.line).toBe(1)
+        expect(forNode.valueAlias!.loc.end.column).toBe(
+          source.indexOf('item') + 4
+        )
+
+        expect(forNode.source.content).toBe('items')
+        expect(forNode.source.loc.start.offset).toBe(
+          source.indexOf('items') - 1
+        )
+        expect(forNode.source.loc.start.line).toBe(1)
+        expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
+        expect(forNode.source.loc.end.line).toBe(1)
+        expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+      })
+
+      test('bracketed value', () => {
+        const source = '<span v-for="( item ) in items" />'
+        const node = parse(source)
+
+        transform(node, { transforms: [transformFor] })
+
+        expect(node.children.length).toBe(1)
+
+        const forNode = node.children[0] as ForNode
+
+        expect(forNode.type).toBe(NodeTypes.FOR)
+
+        expect(forNode.valueAlias!.content).toBe('item')
+        expect(forNode.valueAlias!.loc.start.offset).toBe(
+          source.indexOf('item') - 1
+        )
+        expect(forNode.valueAlias!.loc.start.line).toBe(1)
+        expect(forNode.valueAlias!.loc.start.column).toBe(
+          source.indexOf('item')
+        )
+        expect(forNode.valueAlias!.loc.end.line).toBe(1)
+        expect(forNode.valueAlias!.loc.end.column).toBe(
+          source.indexOf('item') + 4
+        )
+
+        expect(forNode.source.content).toBe('items')
+        expect(forNode.source.loc.start.offset).toBe(
+          source.indexOf('items') - 1
+        )
+        expect(forNode.source.loc.start.line).toBe(1)
+        expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
+        expect(forNode.source.loc.end.line).toBe(1)
+        expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+      })
+
+      test('de-structured value', () => {
+        const source = '<span v-for="(  { id, key })in items" />'
+        const node = parse(source)
+
+        transform(node, { transforms: [transformFor] })
+
+        expect(node.children.length).toBe(1)
+
+        const forNode = node.children[0] as ForNode
+
+        expect(forNode.type).toBe(NodeTypes.FOR)
+
+        expect(forNode.valueAlias!.content).toBe('{ id, key }')
+        expect(forNode.valueAlias!.loc.start.offset).toBe(
+          source.indexOf('{ id, key }') - 1
+        )
+        expect(forNode.valueAlias!.loc.start.line).toBe(1)
+        expect(forNode.valueAlias!.loc.start.column).toBe(
+          source.indexOf('{ id, key }')
+        )
+        expect(forNode.valueAlias!.loc.end.line).toBe(1)
+        expect(forNode.valueAlias!.loc.end.column).toBe(
+          source.indexOf('{ id, key }') + '{ id, key }'.length
+        )
+
+        expect(forNode.source.content).toBe('items')
+        expect(forNode.source.loc.start.offset).toBe(
+          source.indexOf('items') - 1
+        )
+        expect(forNode.source.loc.start.line).toBe(1)
+        expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
+        expect(forNode.source.loc.end.line).toBe(1)
+        expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+      })
+
+      test('bracketed value, key, index', () => {
+        const source = '<span v-for="( item, key, index ) in items" />'
+        const node = parse(source)
+
+        transform(node, { transforms: [transformFor] })
+
+        expect(node.children.length).toBe(1)
+
+        const forNode = node.children[0] as ForNode
+
+        expect(forNode.type).toBe(NodeTypes.FOR)
+
+        expect(forNode.valueAlias!.content).toBe('item')
+        expect(forNode.valueAlias!.loc.start.offset).toBe(
+          source.indexOf('item') - 1
+        )
+        expect(forNode.valueAlias!.loc.start.line).toBe(1)
+        expect(forNode.valueAlias!.loc.start.column).toBe(
+          source.indexOf('item')
+        )
+        expect(forNode.valueAlias!.loc.end.line).toBe(1)
+        expect(forNode.valueAlias!.loc.end.column).toBe(
+          source.indexOf('item') + 4
+        )
+
+        expect(forNode.keyAlias!.content).toBe('key')
+        expect(forNode.keyAlias!.loc.start.offset).toBe(
+          source.indexOf('key') - 1
+        )
+        expect(forNode.keyAlias!.loc.start.line).toBe(1)
+        expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key'))
+        expect(forNode.keyAlias!.loc.end.line).toBe(1)
+        expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3)
+
+        expect(forNode.objectIndexAlias!.content).toBe('index')
+        expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
+          source.indexOf('index') - 1
+        )
+        expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
+        expect(forNode.objectIndexAlias!.loc.start.column).toBe(
+          source.indexOf('index')
+        )
+        expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
+        expect(forNode.objectIndexAlias!.loc.end.column).toBe(
+          source.indexOf('index') + 5
+        )
+
+        expect(forNode.source.content).toBe('items')
+        expect(forNode.source.loc.start.offset).toBe(
+          source.indexOf('items') - 1
+        )
+        expect(forNode.source.loc.start.line).toBe(1)
+        expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
+        expect(forNode.source.loc.end.line).toBe(1)
+        expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+      })
+
+      test('skipped key', () => {
+        const source = '<span v-for="( item,, index ) in items" />'
+        const node = parse(source)
+
+        transform(node, { transforms: [transformFor] })
+
+        expect(node.children.length).toBe(1)
+
+        const forNode = node.children[0] as ForNode
+
+        expect(forNode.type).toBe(NodeTypes.FOR)
+
+        expect(forNode.valueAlias!.content).toBe('item')
+        expect(forNode.valueAlias!.loc.start.offset).toBe(
+          source.indexOf('item') - 1
+        )
+        expect(forNode.valueAlias!.loc.start.line).toBe(1)
+        expect(forNode.valueAlias!.loc.start.column).toBe(
+          source.indexOf('item')
+        )
+        expect(forNode.valueAlias!.loc.end.line).toBe(1)
+        expect(forNode.valueAlias!.loc.end.column).toBe(
+          source.indexOf('item') + 4
+        )
+
+        expect(forNode.objectIndexAlias!.content).toBe('index')
+        expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
+          source.indexOf('index') - 1
+        )
+        expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
+        expect(forNode.objectIndexAlias!.loc.start.column).toBe(
+          source.indexOf('index')
+        )
+        expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
+        expect(forNode.objectIndexAlias!.loc.end.column).toBe(
+          source.indexOf('index') + 5
+        )
+
+        expect(forNode.source.content).toBe('items')
+        expect(forNode.source.loc.start.offset).toBe(
+          source.indexOf('items') - 1
+        )
+        expect(forNode.source.loc.start.line).toBe(1)
+        expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
+        expect(forNode.source.loc.end.line).toBe(1)
+        expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
+      })
     })
   })
 })