Before:
  runtime ale_linters/javascript/flow.vim

After:
  unlet! g:flow_output
  unlet! g:expected
  unlet! g:actual
  call ale#linter#Reset()

Execute(The flow handler should throw away non-JSON lines):
  AssertEqual
  \ [],
  \ ale_linters#javascript#flow#Handle(bufnr(''), [
  \   'Already up-to-date.',
  \   '{"flowVersion":"0.50.0","errors":[],"passed":true}',
  \ ])
  AssertEqual
  \ [],
  \ ale_linters#javascript#flow#Handle(bufnr(''), [
  \   'foo',
  \   'bar',
  \   'baz',
  \   '{"flowVersion":"0.50.0","errors":[],"passed":true}',
  \ ])

Execute(The flow handler should process errors correctly.):
  silent! noautocmd file /home/w0rp/Downloads/graphql-js/src/language/parser.js

  let g:flow_output = {
  \ "flowVersion": "0.39.0",
  \ "errors": [
  \   {
  \     "kind": "infer",
  \     "level": "error",
  \     "message": [
  \       {
  \         "context": "  return 1",
  \         "descr": "number",
  \         "type": "Blame",
  \         "loc": {
  \           "source": expand('%:p'),
  \           "type": "SourceFile",
  \           "start": {
  \             "line": 417,
  \             "column": 10,
  \             "offset": 9503
  \           },
  \           "end": {
  \             "line": 417,
  \             "column": 10,
  \             "offset": 9504
  \           }
  \         },
  \         "path": expand('%:p'),
  \         "line": 417,
  \         "endline": 417,
  \         "start": 10,
  \         "end": 10
  \       },
  \       {
  \         "context": v:null,
  \         "descr": "This type is incompatible with the expected return type of",
  \         "type": "Comment",
  \         "path": "",
  \         "line": 0,
  \         "endline": 0,
  \         "start": 1,
  \         "end": 0
  \       },
  \       {
  \         "context": "function parseArguments(lexer: Lexer<*>): Array<ArgumentNode> {",
  \         "descr": "array type",
  \         "type": "Blame",
  \         "loc": {
  \           "source": expand('%:p'),
  \           "type": "SourceFile",
  \           "start": {
  \             "line": 416,
  \             "column": 43,
  \             "offset": 9472
  \           },
  \           "end": {
  \             "line": 416,
  \             "column": 61,
  \             "offset": 9491
  \           }
  \         },
  \         "path": expand('%:p'),
  \         "line": 416,
  \         "endline": 416,
  \         "start": 43,
  \         "end": 61
  \       }
  \     ]
  \   },
  \   {
  \     "kind": "infer",
  \     "level": "warning",
  \     "message": [
  \       {
  \         "context": "  return peek(lexer, TokenKind.PAREN_L) ?",
  \         "descr": "unreachable code",
  \         "type": "Blame",
  \         "loc": {
  \           "source": expand('%:p'),
  \           "type": "SourceFile",
  \           "start": {
  \             "line": 419,
  \             "column": 3,
  \             "offset": 9508
  \           },
  \           "end": {
  \             "line": 421,
  \             "column": 7,
  \             "offset": 9626
  \           }
  \         },
  \         "path": expand('%:p'),
  \         "line": 419,
  \         "endline": 421,
  \         "start": 3,
  \         "end": 7
  \       }
  \     ]
  \   }
  \ ],
  \ "passed": v:false
  \}

  let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)])
  let g:expected = [
  \ {
  \   'lnum': 417,
  \   'type': 'E',
  \   'col': 10,
  \   'text': 'number: This type is incompatible with the expected return type of array type',
  \ },
  \ {
  \   'lnum': 419,
  \   'type': 'W',
  \   'col': 3,
  \   'text': 'unreachable code:',
  \ },
  \]

  AssertEqual g:expected, g:actual

Execute(The flow handler should fetch the correct location for the currently opened file, even when it's not in the first message.):
  silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js

  let g:flow_output = {
  \ "flowVersion": "0.44.0",
  \ "errors": [{
  \   "operation": {
  \     "context": "  <Foo foo=\"bar\"/>, document.getElementById('foo')",
  \     "descr": "React element `Foo`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": expand('%:p'),
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 6,
  \         "column": 3,
  \         "offset": 92
  \       },
  \       "end": {
  \         "line": 6,
  \         "column": 18,
  \         "offset": 108
  \       }
  \     },
  \     "path": expand('%:p'),
  \     "line": 6,
  \     "endline": 6,
  \     "start": 3,
  \     "end": 18
  \   },
  \   "kind": "infer",
  \   "level": "error",
  \   "message": [{
  \     "context": "module.exports = function(props: Props) {",
  \     "descr": "property `bar`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": "/Users/rav/Projects/vim-ale-flow/foo.js",
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 9,
  \         "column": 34,
  \         "offset": 121
  \       },
  \       "end": {
  \         "line": 9,
  \         "column": 38,
  \         "offset": 126
  \       }
  \     },
  \     "path": "/Users/rav/Projects/vim-ale-flow/foo.js",
  \     "line": 9,
  \     "endline": 9,
  \     "start": 34,
  \     "end": 38
  \   }, {
  \     "context": v:null,
  \     "descr": "Property not found in",
  \     "type": "Comment",
  \     "path": "",
  \     "line": 0,
  \     "endline": 0,
  \     "start": 1,
  \     "end": 0
  \   }, {
  \     "context": "  <Foo foo=\"bar\"/>, document.getElementById('foo')",
  \     "descr": "props of React element `Foo`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": expand('%:p'),
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 6,
  \         "column": 3,
  \         "offset": 92
  \       },
  \       "end": {
  \         "line": 6,
  \         "column": 18,
  \         "offset": 108
  \       }
  \     },
  \     "path": expand('%:p'),
  \     "line": 6,
  \     "endline": 6,
  \     "start": 3,
  \     "end": 18
  \   }]
  \ }],
  \ "passed": v:false
  \}

  let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)])
  let g:expected = [
  \ {
  \   'lnum': 6,
  \   'col': 3,
  \   'type': 'E',
  \   'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`',
  \ }
  \]

  AssertEqual g:expected, g:actual

Execute(The flow handler should handle relative paths):
  silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js

  let g:flow_output = {
  \ "flowVersion": "0.44.0",
  \ "errors": [{
  \   "operation": {
  \     "context": "  <Foo foo=\"bar\"/>, document.getElementById('foo')",
  \     "descr": "React element `Foo`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": expand('%:p'),
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 6,
  \         "column": 3,
  \         "offset": 92
  \       },
  \       "end": {
  \         "line": 6,
  \         "column": 18,
  \         "offset": 108
  \       }
  \     },
  \     "path": expand('%:p'),
  \     "line": 6,
  \     "endline": 6,
  \     "start": 3,
  \     "end": 18
  \   },
  \   "kind": "infer",
  \   "level": "error",
  \   "message": [{
  \     "context": "module.exports = function(props: Props) {",
  \     "descr": "property `bar`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": "vim-ale-flow/foo.js",
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 9,
  \         "column": 34,
  \         "offset": 121
  \       },
  \       "end": {
  \         "line": 9,
  \         "column": 38,
  \         "offset": 126
  \       }
  \     },
  \     "path": "vim-ale-flow/foo.js",
  \     "line": 9,
  \     "endline": 9,
  \     "start": 34,
  \     "end": 38
  \   }, {
  \     "context": v:null,
  \     "descr": "Property not found in",
  \     "type": "Comment",
  \     "path": "",
  \     "line": 0,
  \     "endline": 0,
  \     "start": 1,
  \     "end": 0
  \   }, {
  \     "context": "  <Foo foo=\"bar\"/>, document.getElementById('foo')",
  \     "descr": "props of React element `Foo`",
  \     "type": "Blame",
  \     "loc": {
  \       "source": expand('%:p'),
  \       "type": "SourceFile",
  \       "start": {
  \         "line": 6,
  \         "column": 3,
  \         "offset": 92
  \       },
  \       "end": {
  \         "line": 6,
  \         "column": 18,
  \         "offset": 108
  \       }
  \     },
  \     "path": expand('%:p'),
  \     "line": 6,
  \     "endline": 6,
  \     "start": 3,
  \     "end": 18
  \   }]
  \ }],
  \ "passed": v:false
  \}

  let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)])
  let g:expected = [
  \ {
  \   'lnum': 6,
  \   'col': 3,
  \   'type': 'E',
  \   'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`',
  \ }
  \]

  AssertEqual g:expected, g:actual

Execute(The flow handler should handle extra errors):
  silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js

  let g:flow_output = {
  \  "flowVersion": "0.54.0",
  \  "errors": [{
  \    "extra": [{
  \      "message": [{
  \        "context": v:null,
  \        "descr": "Property \`setVector\` is incompatible:",
  \        "type": "Blame ",
  \        "path": "",
  \        "line": 0,
  \        "endline": 0,
  \        "start": 1,
  \        "end": 0
  \      }],
  \      "children": [{
  \        "message": [{
  \          "context": "setVector = \{2\}",
  \          "descr": "number ",
  \          "type": "Blame ",
  \          "loc": {
  \            "source": expand('%:p'),
  \            "type": "SourceFile ",
  \            "start": {
  \              "line": 90,
  \              "column": 30,
  \              "offset": 2296
  \            },
  \            "end": {
  \              "line": 90,
  \              "column": 30,
  \              "offset": 2297
  \            }
  \          },
  \          "path": expand('%:p'),
  \          "line": 90,
  \          "endline": 90,
  \          "start": 30,
  \          "end": 30
  \        }, {
  \          "context": v:null,
  \          "descr": "This type is incompatible with ",
  \          "type": "Comment ",
  \          "path": "",
  \          "line": 0,
  \          "endline": 0,
  \          "start": 1,
  \          "end": 0
  \        }, {
  \          "context": "setVector: VectorType => void,",
  \          "descr": "function type ",
  \          "type": "Blame ",
  \          "loc": {
  \            "source": expand('%:p'),
  \            "type": "SourceFile",
  \            "start": {
  \              "line": 9,
  \              "column": 14,
  \              "offset": 252
  \            },
  \            "end": {
  \              "line": 9,
  \              "column": 31,
  \              "offset": 270
  \            }
  \          },
  \          "path": expand('%:p'),
  \          "line": 9,
  \          "endline": 9,
  \          "start": 14,
  \          "end": 31
  \        }]
  \      }]
  \    }],
  \    "kind": "infer",
  \    "level": "error",
  \    "suppressions": [],
  \    "message": [{
  \      "context": " < New ",
  \      "descr": "props of React element `New`",
  \      "type": "Blame",
  \      "loc": {
  \        "source": "vim-ale-flow/foo.js",
  \        "type": "SourceFile",
  \        "start": {
  \          "line": 89,
  \          "column": 17,
  \          "offset": 2262
  \        },
  \        "end": {
  \          "line": 94,
  \          "column": 18,
  \          "offset": 2488
  \        }
  \      },
  \      "path": "",
  \      "line": 89,
  \      "endline": 94,
  \      "start": 17,
  \      "end": 18
  \    }, {
  \      "context": v:null,
  \      "descr": "This type is incompatible with",
  \      "type": "Comment",
  \      "path": "",
  \      "line": 0,
  \      "endline": 0,
  \      "start": 1,
  \      "end": 0
  \    }, {
  \      "context": "class New extends React.Component < NewProps,NewState > {",
  \      "descr": "object type",
  \      "type": "Blame",
  \      "loc": {
  \        "source": expand('%:p'),
  \        "type": "SourceFile",
  \        "start": {
  \          "line": 20,
  \          "column": 35,
  \          "offset": 489
  \        },
  \        "end": {
  \          "line": 20,
  \          "column": 42,
  \          "offset": 497
  \        }
  \      },
  \      "path": expand('%:p'),
  \      "line": 20,
  \      "endline": 20,
  \      "start": 35,
  \      "end": 42
  \    }]
  \  }],
  \  "passed": v:false
  \}

  let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)])
  let g:expected = [
  \ {
  \   'lnum': 20,
  \   'col': 35,
  \   'type': 'E',
  \   'text': 'props of React element `New`: This type is incompatible with object type',
  \   'detail': 'props of React element `New`: This type is incompatible with object type'
  \     .  "\nProperty `setVector` is incompatible: number  This type is incompatible with  function type ",
  \ }
  \]

  AssertEqual g:expected, g:actual