text: const parse = require('../../src/parsers/javascript');
  const remark = require('remark');
  const visit = require('unist-util-visit');
  
  function pick(obj, props) {
    if (Array.isArray(props)) {
      return props.reduce(function(memo, prop) {
        if (obj[prop] !== undefined) {
          memo[prop] = obj[prop];
        }
        return memo;
      }, {});
    }
    return obj[props];
  }
  
  function evaluate(fn, filename) {
    return parse(
      {
        file: filename || 'test.js',
        source: fn instanceof Function ? '(' + fn.toString() + ')' : fn
      },
      {}
    );
  }
  
  function addJSDocTag(tree) {
    visit(tree, 'link', function(node) {
      node.jsdoc = true;
    });
    return tree;
  }
  
  function removePosition(tree) {
    visit(tree, function(node) {
      delete node.position;
    });
    return tree;
  }
  
  test('parse - @abstract', function() {
    expect(
      evaluate(function() {
        /** @abstract */
      })[0].abstract
    ).toBe(true);
  });
  
  test('parse - @access', function() {
    expect(
      evaluate(function() {
        /** @access public */
      })[0].access
    ).toBe('public');
  
    expect(
      evaluate(function() {
        /** @access protected */
      })[0].access
    ).toBe('protected');
  
    expect(
      evaluate(function() {
        /** @access private */
      })[0].access
    ).toBe('private');
  });
  
  test('parse - @alias', function() {});
  
  test('parse - @arg', function() {});
  
  test('parse - @argument', function() {});
  
  test('parse - @async', function() {
    expect(
      evaluate(function() {
        /** @async */
      })[0].async
    ).toBe(true);
  });
  
  test('parse - @augments', function() {
    expect(
      evaluate(function() {
        /** @augments Foo */
      })[0].augments[0].name
    ).toBe('Foo');
  });
  
  test('parse - @description', function() {
    expect(
      evaluate(function() {
        /**
         * This is a free-form description
         * @description This tagged description wins, and [is markdown](http://markdown.com).
         */
      })[0].description
    ).toEqual(
      remark().parse(
        'This tagged description wins, and [is markdown](http://markdown.com).'
      )
    );
  });
  
  /*
   * Dossier-style augments tag
   * https://github.com/google/closure-library/issues/746
   */
  test('parse - @augments in dossier style', function() {
    expect(
      evaluate(function() {
        /** @augments {Foo} */
      })[0].augments[0].name
    ).toBe('Foo');
  });
  
  test('parse - @augments of complex passes through', function() {
    expect(
      evaluate(function() {
        /** @augments {function()} */
      })[0].augments
    ).toEqual([]);
  });
  
  test('parse - @author', function() {});
  
  test('parse - @borrows', function() {});
  
  test('parse - @callback', function() {
    expect(
      pick(
        evaluate(function() {
          /** @callback name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      name: 'name',
      kind: 'typedef',
      type: {
        type: 'NameExpression',
        name: 'Function'
      }
    });
  });
  
  test('parse - @class', function() {
    expect(
      pick(
        evaluate(function() {
          /** @class */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'class'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @class name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      name: 'name',
      kind: 'class'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @class {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      name: 'name',
      kind: 'class',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @classdesc', function() {
    expect(
      evaluate(function() {
        /** @classdesc test */
      })[0].classdesc
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @const', function() {});
  
  test('parse - @constant', function() {
    expect(
      pick(
        evaluate(function() {
          /** @constant */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'constant'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @constant name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'constant',
      name: 'name'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @constant {Object} */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'constant',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @constant {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'constant',
      name: 'name',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @constructor', function() {});
  
  test('parse - @constructs', function() {});
  
  test('parse - @copyright', function() {
    expect(
      evaluate(function() {
        /** @copyright test */
      })[0].copyright
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @default', function() {});
  
  test('parse - @defaultvalue', function() {});
  
  test('parse - @deprecated', function() {
    expect(
      evaluate(function() {
        /** @deprecated test */
      })[0].deprecated
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @desc', function() {
    expect(
      evaluate(function() {
        /** @desc test */
      })[0].description
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @description', function() {
    expect(
      evaluate(function() {
        /** @description test */
      })[0].description
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - description', function() {
    expect(
      evaluate(function() {
        /** test */
      })[0].description
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @emits', function() {});
  
  test('parse - @enum', function() {
    expect(
      pick(
        evaluate(function() {
          /** @enum {string} */
        })[0],
        ['kind', 'type']
      )
    ).toEqual({
      kind: 'enum',
      type: {
        type: 'NameExpression',
        name: 'string'
      }
    });
  });
  
  test('parse - @event', function() {
    expect(
      pick(
        evaluate(function() {
          /** @event name */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'event',
      name: 'name'
    });
  });
  
  test('parse - @example', function() {
    expect(
      evaluate(function() {
        /** @example test */
      })[0].examples[0]
    ).toEqual({
      description: 'test'
    });
  
    expect(
      evaluate(function() {
        /**
         * @example
         * a
         * b
         */
      })[0].examples[0]
    ).toEqual({
      description: 'a\nb'
    });
  
    expect(
      evaluate(function() {
        /**
         * @example <caption>caption</caption>
         * a
         * b
         */
      })[0].examples[0]
    ).toEqual({
      description: 'a\nb',
      caption: remark().parse('caption')
    });
  
    expect(
      evaluate(function() {
        /** @example */
      })[0].errors[0]
    ).toEqual({
      message: '@example without code',
      commentLineNumber: 0
    });
  });
  
  test('parse - @exception', function() {});
  
  test('parse - @exports', function() {});
  
  test('parse - @extends', function() {
    expect(
      evaluate(function() {
        /** @extends Foo */
      })[0].augments[0].name
    ).toEqual('Foo');
  });
  
  test('parse - @external', function() {
    expect(
      pick(
        evaluate(function() {
          /** @external name */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'external',
      name: 'name'
    });
  });
  
  test('parse - @file', function() {
    expect(
      pick(
        evaluate(function() {
          /** @file */
        })[0],
        ['kind']
      )
    ).toEqual({
      kind: 'file'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @file desc */
        })[0],
        ['kind', 'description']
      )
    ).toEqual({
      kind: 'file',
      description: remark().parse('desc')
    });
  });
  
  test('parse - @fileoverview', function() {});
  
  test('parse - @fires', function() {});
  
  test('parse - @func', function() {});
  
  test('parse - @function', function() {
    expect(
      pick(
        evaluate(function() {
          /** @function */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'function'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @function name */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'function',
      name: 'name'
    });
  
    // When @function takes a name, it is acting as a shorthand for @name and
    // should detach from code the same way @name does.
    expect(
      evaluate(function() {
        /** @function */
        function foo() {}
      })[0].context.ast
    ).toBeDefined();
  
    expect(
      evaluate(function() {
        /** @function name */
        function foo() {}
      })[0].context.ast
    ).toBeUndefined();
  });
  
  test('parse - @generator', function() {
    expect(
      evaluate(function() {
        /** @generator */
      })[0].generator
    ).toBe(true);
  });
  
  test('parse - @global', function() {
    expect(
      evaluate(function() {
        /** @global */
      })[0].scope
    ).toBe('global');
  });
  
  test('parse - @hideconstructor', function() {
    expect(
      evaluate(function() {
        /** @hideconstructor */
      })[0].hideconstructor
    ).toBe(true);
  });
  
  test('parse - @host', function() {});
  
  test('parse - @ignore', function() {
    expect(
      evaluate(function() {
        /** @ignore */
      })[0].ignore
    ).toBe(true);
  });
  
  test('parse - @implements', function() {
    expect(
      evaluate(function() {
        /** @implements {Foo} */
      })[0].implements[0].name
    ).toEqual('Foo');
  });
  
  test('parse - @inheritdoc', function() {});
  
  test('parse - @inner', function() {
    expect(
      evaluate(function() {
        /** @inner*/
      })[0].scope
    ).toBe('inner');
  });
  
  test('parse - @instance', function() {
    expect(
      evaluate(function() {
        /** @instance*/
      })[0].scope
    ).toBe('instance');
  });
  
  test('parse - @interface', function() {
    expect(
      evaluate(function() {
        /** @interface */
      })[0].kind
    ).toEqual('interface');
  
    expect(
      evaluate(function() {
        /** @interface Foo */
      })[0].name
    ).toEqual('Foo');
  });
  
  test('parse - @kind', function() {
    expect(
      evaluate(function() {
        /** @kind class */
      })[0].kind
    ).toBe('class');
  });
  
  test('parse - @license', function() {});
  
  test('parse - @listens', function() {});
  
  test('parse - @member', function() {
    expect(
      pick(
        evaluate(function() {
          /** @member */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'member'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @member name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'member',
      name: 'name'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @member {Object} */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'member',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @member {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'member',
      name: 'name',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @memberof', function() {
    expect(
      evaluate(function() {
        /** @memberof test */
      })[0].memberof
    ).toBe('test');
  });
  
  test('parse - @method', function() {});
  
  test('parse - @mixes', function() {});
  
  test('parse - @mixin', function() {
    expect(
      pick(
        evaluate(function() {
          /** @mixin */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'mixin'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @mixin name */
        })[0],
        ['kind', 'name']
      )
    ).toEqual({
      kind: 'mixin',
      name: 'name'
    });
  });
  
  test('parse - @module', function() {
    expect(
      pick(
        evaluate(function() {
          /** @module */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'module'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @module name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'module',
      name: 'name'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @module {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'module',
      name: 'name',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @name', function() {
    expect(
      evaluate(function() {
        /** @name test */
      })[0].name
    ).toBe('test');
  
    expect(
      evaluate(function() {
        /** @name foo */
        const bar = 0;
      })[0].context.ast
    ).toBeUndefined();
  });
  
  test('parse - @namespace', function() {
    expect(
      pick(
        evaluate(function() {
          /** @namespace */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'namespace'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @namespace name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'namespace',
      name: 'name'
    });
  
    expect(
      pick(
        evaluate(function() {
          /** @namespace {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'namespace',
      name: 'name',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @override', function() {
    expect(
      evaluate(function() {
        /** @override */
      })[0].override
    ).toBe(true);
  });
  
  test('parse - @overview', function() {});
  
  test('parse - @param', function() {
    expect(
      evaluate(function() {
        /** @param test */
      })[0].params[0]
    ).toEqual({
      name: 'test',
      title: 'param',
      lineNumber: 0
    });
  
    expect(
      evaluate(function() {
        /** @param {number} test */
      })[0].params[0]
    ).toEqual({
      name: 'test',
      title: 'param',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      lineNumber: 0
    });
  
    expect(
      evaluate(function() {
        /** @param {number} test - desc */
      })[0].params[0]
    ).toEqual({
      name: 'test',
      title: 'param',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      description: remark().parse('desc'),
      lineNumber: 0
    });
  });
  
  test('parse - @private', function() {
    expect(
      evaluate(function() {
        /** @private */
      })[0].access
    ).toBe('private');
  });
  
  test('parse - @prop', function() {
    expect(
      evaluate(function() {
        /** @prop {number} test */
      })[0].properties[0]
    ).toEqual({
      name: 'test',
      title: 'property',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      lineNumber: 0
    });
  
    expect(
      evaluate(function() {
        /** @prop {number} test - desc */
      })[0].properties[0]
    ).toEqual({
      name: 'test',
      title: 'property',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      description: remark().parse('desc'),
      lineNumber: 0
    });
  });
  
  test('parse - @property', function() {
    expect(
      evaluate(function() {
        /** @property {number} test */
      })[0].properties[0]
    ).toEqual({
      name: 'test',
      title: 'property',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      lineNumber: 0
    });
  
    expect(
      evaluate(function() {
        /** @property {number} test - desc */
      })[0].properties[0]
    ).toEqual({
      name: 'test',
      title: 'property',
      type: {
        name: 'number',
        type: 'NameExpression'
      },
      description: remark().parse('desc'),
      lineNumber: 0
    });
  });
  
  test('parse - @protected', function() {
    expect(
      evaluate(function() {
        /** @protected */
      })[0].access
    ).toBe('protected');
  });
  
  test('parse - @public', function() {});
  
  test('parse - @readonly', function() {
    expect(
      evaluate(function() {
        /** @readonly */
      })[0].readonly
    ).toBe(true);
  });
  
  test('parse - @requires', function() {});
  
  test('parse - @return', function() {
    expect(
      evaluate(function() {
        /** @return test */
      })[0].returns[0]
    ).toEqual({
      title: 'returns',
      description: remark().parse('test')
    });
  
    expect(
      evaluate(function() {
        /** @return {number} test */
      })[0].returns[0]
    ).toEqual({
      description: remark().parse('test'),
      title: 'returns',
      type: {
        name: 'number',
        type: 'NameExpression'
      }
    });
  });
  
  test('parse - @returns', function() {
    expect(
      evaluate(function() {
        /** @returns test */
      })[0].returns[0]
    ).toEqual({
      title: 'returns',
      description: remark().parse('test')
    });
  
    expect(
      evaluate(function() {
        /** @returns {number} test */
      })[0].returns[0]
    ).toEqual({
      description: remark().parse('test'),
      title: 'returns',
      type: {
        name: 'number',
        type: 'NameExpression'
      }
    });
  });
  
  test('parse - @see', function() {
    expect(
      evaluate(function() {
        /** @see test */
      })[0].sees
    ).toEqual([remark().parse('test')]);
  
    expect(
      evaluate(function() {
        /**
         * @see a
         * @see b
         */
      })[0].sees
    ).toEqual([remark().parse('a'), remark().parse('b')]);
  });
  
  test('parse - @since', function() {});
  
  test('parse - @static', function() {
    expect(
      evaluate(function() {
        /** @static */
      })[0].scope
    ).toBe('static');
  });
  
  test('parse - @summary', function() {
    expect(
      evaluate(function() {
        /** @summary test */
      })[0].summary
    ).toEqual(remark().parse('test'));
  });
  
  test('parse - @this', function() {});
  
  test('parse - @throws', function() {
    expect(
      evaluate(function() {
        /** @throws desc */
      })[0].throws[0]
    ).toEqual({
      description: remark().parse('desc')
    });
  
    expect(
      evaluate(function() {
        /** @throws {Error} */
      })[0].throws[0]
    ).toEqual({
      type: {
        name: 'Error',
        type: 'NameExpression'
      }
    });
  
    expect(
      evaluate(function() {
        /** @throws {Error} desc */
      })[0].throws[0]
    ).toEqual({
      type: {
        name: 'Error',
        type: 'NameExpression'
      },
      description: remark().parse('desc')
    });
  
    expect(
      evaluate(function() {
        /**
         * @throws a
         * @throws b
         */
      })[0].throws
    ).toEqual([
      {
        description: remark().parse('a')
      },
      {
        description: remark().parse('b')
      }
    ]);
  });
  
  test('parse - @todo', function() {
    expect(
      evaluate(function() {
        /** @todo test */
      })[0].todos
    ).toEqual([remark().parse('test')]);
  
    expect(
      evaluate(function() {
        /**
         * @todo a
         * @todo b
         */
      })[0].todos
    ).toEqual([remark().parse('a'), remark().parse('b')]);
  });
  
  test('parse - @tutorial', function() {});
  
  test('parse - @type', function() {});
  
  test('parse - @typedef', function() {
    expect(
      pick(
        evaluate(function() {
          /** @typedef {Object} name */
        })[0],
        ['kind', 'name', 'type']
      )
    ).toEqual({
      kind: 'typedef',
      name: 'name',
      type: {
        type: 'NameExpression',
        name: 'Object'
      }
    });
  });
  
  test('parse - @var', function() {});
  
  test('parse - @variation', function() {
    expect(
      evaluate(function() {
        /** @variation 1 */
      })[0].variation
    ).toBe(1);
  });
  
  test('parse - @version', function() {});
  
  test('parse - @virtual', function() {});
  
  test('parse - @yield', function() {
    expect(
      evaluate(function() {
        /** @yield test */
      })[0].yields[0]
    ).toEqual({
      title: 'yields',
      description: remark().parse('test')
    });
  
    expect(
      evaluate(function() {
        /** @yield {number} test */
      })[0].yields[0]
    ).toEqual({
      description: remark().parse('test'),
      title: 'yields',
      type: {
        name: 'number',
        type: 'NameExpression'
      }
    });
  });
  
  test('parse - @yields', function() {
    expect(
      evaluate(function() {
        /** @yields test */
      })[0].yields[0]
    ).toEqual({
      title: 'yields',
      description: remark().parse('test')
    });
  
    expect(
      evaluate(function() {
        /** @yields {number} test */
      })[0].yields[0]
    ).toEqual({
      description: remark().parse('test'),
      title: 'yields',
      type: {
        name: 'number',
        type: 'NameExpression'
      }
    });
  });
  
  test('parse - unknown tag', function() {
    expect(
      evaluate(function() {
        /** @unknown */
      })[0].errors[0]
    ).toEqual({
      message: 'unknown tag @unknown',
      commentLineNumber: 0
    });
  });
  
  test('parse - {@link}', function() {
    expect(
      removePosition(
        evaluate(function() {
          /** {@link Foo} */
        })[0].description
      )
    ).toEqual(addJSDocTag(removePosition(remark().parse('[Foo](Foo)'))));
  
    expect(
      removePosition(
        evaluate(function() {
          /** {@link Foo|text} */
        })[0].description
      )
    ).toEqual(addJSDocTag(removePosition(remark().parse('[text](Foo)'))));
  
    expect(
      removePosition(
        evaluate(function() {
          /** {@link Foo text} */
        })[0].description
      )
    ).toEqual(addJSDocTag(removePosition(remark().parse('[text](Foo)'))));
  });
  
  test('parse - {@linkcode}', function() {});
  
  test('parse - {@linkplain}', function() {});
  
  test('parse - {@tutorial}', function() {
    expect(
      removePosition(
        evaluate(function() {
          /** {@tutorial id} */
        })[0].description
      )
    ).toEqual({
      type: 'root',
      children: [
        {
          type: 'paragraph',
          children: [
            {
              type: 'tutorial',
              url: 'id',
              jsdoc: true,
              title: null,
              children: [
                {
                  type: 'text',
                  value: 'id'
                }
              ]
            }
          ]
        }
      ]
    });
  
    expect(
      removePosition(
        evaluate(function() {
          /** {@tutorial id|text} */
        })[0].description
      )
    ).toEqual({
      type: 'root',
      children: [
        {
          type: 'paragraph',
          children: [
            {
              type: 'tutorial',
              url: 'id',
              jsdoc: true,
              title: null,
              children: [
                {
                  type: 'text',
                  value: 'text'
                }
              ]
            }
          ]
        }
      ]
    });
  
    expect(
      removePosition(
        evaluate(function() {
          /** {@tutorial id text} */
        })[0].description
      )
    ).toEqual({
      type: 'root',
      children: [
        {
          type: 'paragraph',
          children: [
            {
              type: 'tutorial',
              url: 'id',
              jsdoc: true,
              title: null,
              children: [
                {
                  type: 'text',
                  value: 'text'
                }
              ]
            }
          ]
        }
      ]
    });
  });
  