describe('@jsdoc/core.name', () => {
  const { name } = require('@jsdoc/core');

  it('exists', () => {
    expect(name).toBeObject();
  });

  it('has an applyNamespace method', () => {
    expect(name.applyNamespace).toBeFunction();
  });

  it('has a fromParts method', () => {
    expect(name.fromParts).toBeFunction();
  });

  it('has a getBasename method', () => {
    expect(name.getBasename).toBeFunction();
  });

  it('has a hasAncestor method', () => {
    expect(name.hasAncestor).toBeFunction();
  });

  it('has a hasLeadingScope method', () => {
    expect(name.hasLeadingScope).toBeFunction();
  });

  it('has a hasTrailingScope method', () => {
    expect(name.hasTrailingScope).toBeFunction();
  });

  it('has a LONGNAMES enum', () => {
    expect(name.LONGNAMES).toBeObject();
  });

  it('has a longnamesToTree method', () => {
    expect(name.longnamesToTree).toBeFunction();
  });

  it('has a MODULE_NAMESPACE property', () => {
    expect(name.MODULE_NAMESPACE).toBeString();
  });

  it('has a nameIsLongname method', () => {
    expect(name.nameIsLongname).toBeFunction();
  });

  it('has a prototypeToPunc method', () => {
    expect(name.prototypeToPunc).toBeFunction();
  });

  it('has a PUNC_TO_SCOPE enum', () => {
    expect(name.PUNC_TO_SCOPE).toBeObject();
  });

  it('has a SCOPE enum', () => {
    expect(name.SCOPE).toBeObject();
  });

  it('has a SCOPE_TO_PUNC enum', () => {
    expect(name.SCOPE_TO_PUNC).toBeObject();
  });

  it('has a splitNameAndDescription method', () => {
    expect(name.splitNameAndDescription).toBeFunction();
  });

  it('has a stripNamespace method', () => {
    expect(name.stripNamespace).toBeFunction();
  });

  it('has a stripVariation method', () => {
    expect(name.stripVariation).toBeFunction();
  });

  it('has a toParts method', () => {
    expect(name.toParts).toBeFunction();
  });

  describe('applyNamespace', () => {
    it('applies the namespace to the name part of the longname', () => {
      expect(name.applyNamespace('lib.Panel#open', 'event')).toBe('lib.Panel#event:open');
    });

    it('applies the namespace to the start of a top-level longname', () => {
      expect(name.applyNamespace('math/bigint', 'module')).toBe('module:math/bigint');
    });

    // TODO(hegemonic): This has never worked
    xit('handles longnames with quoted portions', () => {
      expect(name.applyNamespace('foo."*don\'t.look~in#here!"', 'event')).toBe(
        'foo.event:"*don\'t.look~in#here!"'
      );
    });

    it('handles longnames that already have namespaces', () => {
      expect(name.applyNamespace('lib.Panel#event:open', 'event')).toBe('lib.Panel#event:open');
    });
  });

  xdescribe('fromParts', () => {
    // TODO: tests
  });

  describe('getBasename', () => {
    it('returns null on empty input', () => {
      expect(name.getBasename()).toBeNull();
    });

    it('returns the original value if it has no punctuation except underscores', () => {
      expect(name.getBasename('foo_bar')).toBe('foo_bar');
    });

    it('returns the basename if the original value has punctuation', () => {
      expect(name.getBasename('foo.Bar#baz')).toBe('foo');
    });
  });

  describe('hasAncestor', () => {
    it('returns false if no parent is specified', () => {
      expect(name.hasAncestor(null, 'foo')).toBe(false);
    });

    it('returns false if no child is specified', () => {
      expect(name.hasAncestor('foo')).toBe(false);
    });

    it('returns true when the ancestor is the immediate parent', () => {
      expect(name.hasAncestor('module:foo', 'module:foo~bar')).toBe(true);
    });

    it('returns true when the ancestor is not the immediate parent', () => {
      expect(name.hasAncestor('module:foo', 'module:foo~bar.Baz#qux')).toBe(true);
    });

    it('returns false when a non-ancestor is passed in', () => {
      expect(name.hasAncestor('module:foo', 'foo')).toBe(false);
    });

    it('returns false if the parent and child are the same', () => {
      expect(name.hasAncestor('module:foo', 'module:foo')).toBe(false);
    });
  });

  describe('hasLeadingScope', () => {
    it('returns true if the string starts with a scope character', () => {
      expect(name.hasLeadingScope('#foo')).toBeTrue();
    });

    it('returns false if the string does not start with a scope character', () => {
      expect(name.hasLeadingScope('!foo')).toBeFalse();
    });
  });

  describe('hasTrailingScope', () => {
    it('returns true if the string ends with a scope character', () => {
      expect(name.hasTrailingScope('Foo#')).toBeTrue();
    });

    it('returns false if the string does not end with a scope character', () => {
      expect(name.hasTrailingScope('Foo!')).toBeFalse();
    });
  });

  describe('LONGNAMES', () => {
    it('has an ANONYMOUS property', () => {
      expect(name.LONGNAMES.ANONYMOUS).toBeString();
    });

    it('has a GLOBAL property', () => {
      expect(name.LONGNAMES.GLOBAL).toBeString();
    });
  });

  xdescribe('longnamesToTree', () => {
    // TODO: tests
  });

  describe('MODULE_NAMESPACE', () => {
    // This is just a string, so nothing to test.
  });

  xdescribe('nameIsLongname', () => {
    // TODO(hegemonic)
  });

  xdescribe('prototypeToPunc', () => {
    // TODO(hegemonic)
  });

  describe('PUNC_TO_SCOPE', () => {
    it('has the same number of properties as SCOPE_TO_PUNC', () => {
      expect(Object.keys(name.PUNC_TO_SCOPE).length).toBe(Object.keys(name.SCOPE_TO_PUNC).length);
    });
  });

  describe('SCOPE', () => {
    const SCOPE = name.SCOPE;

    it('has a NAMES enum', () => {
      expect(SCOPE.NAMES).toBeObject();
    });

    it('has a PUNC enum', () => {
      expect(SCOPE.PUNC).toBeObject();
    });

    describe('NAMES', () => {
      it('has a GLOBAL property', () => {
        expect(SCOPE.NAMES.GLOBAL).toBeString();
      });

      it('has an INNER property', () => {
        expect(SCOPE.NAMES.INNER).toBeString();
      });

      it('has an INSTANCE property', () => {
        expect(SCOPE.NAMES.INSTANCE).toBeString();
      });

      it('has a STATIC property', () => {
        expect(SCOPE.NAMES.STATIC).toBeString();
      });
    });

    describe('PUNC', () => {
      it('has an INNER property', () => {
        expect(SCOPE.PUNC.INNER).toBeString();
      });

      it('has an INSTANCE property', () => {
        expect(SCOPE.PUNC.INSTANCE).toBeString();
      });

      it('has a STATIC property', () => {
        expect(SCOPE.PUNC.STATIC).toBeString();
      });
    });
  });

  describe('SCOPE_TO_PUNC', () => {
    it('has an inner property', () => {
      expect(name.SCOPE_TO_PUNC.inner).toBeString();
    });

    it('has an instance property', () => {
      expect(name.SCOPE_TO_PUNC.instance).toBeString();
    });

    it('has a static property', () => {
      expect(name.SCOPE_TO_PUNC.static).toBeString();
    });
  });

  describe('splitNameAndDescription', () => {
    // TODO(hegemonic): This has never worked
    xit('separates the name from the description', () => {
      const parts = name.splitNameAndDescription(
        'ns.Page#"last \\"sentence\\"".words~sort(2)   - This is a description. '
      );

      expect(parts.name).toBe('ns.Page#"last \\"sentence\\"".words~sort(2)');
      expect(parts.description).toBe('This is a description.');
    });

    it('strips a separator when it starts on the same line as the name', () => {
      const parts = name.splitNameAndDescription('socket - The networking kind, not the wrench.');

      expect(parts.name).toBe('socket');
      expect(parts.description).toBe('The networking kind, not the wrench.');
    });

    it('does not strip a separator that is preceded by a line break', () => {
      const parts = name.splitNameAndDescription('socket\n - The networking kind, not the wrench.');

      expect(parts.name).toBe('socket');
      expect(parts.description).toBe('- The networking kind, not the wrench.');
    });

    it('allows default values to contain square brackets', () => {
      const parts = name.splitNameAndDescription(
        '[path=["home", "user"]] - Path split into components'
      );

      expect(parts.name).toBe('[path=["home", "user"]]');
      expect(parts.description).toBe('Path split into components');
    });

    it('allows default values to contain unmatched square brackets inside strings', () => {
      const parts = name.splitNameAndDescription(
        '[path=["Unmatched begin: ["]] - Path split into components'
      );

      expect(parts.name).toBe('[path=["Unmatched begin: ["]]');
      expect(parts.description).toBe('Path split into components');
    });

    it('fails gracefully when the default value has an unmatched square bracket', () => {
      const parts = name.splitNameAndDescription(
        '[path=["home", "user"] - Path split into components'
      );

      expect(parts).toBeObject();
      expect(parts.name).toBe('[path=["home", "user"]');
      expect(parts.description).toBe('Path split into components');
    });

    it('fails gracefully when the default value has an unmatched quote', () => {
      const parts = name.splitNameAndDescription(
        '[path=["home", "user] - Path split into components'
      );

      expect(parts).toBeObject();
      expect(parts.name).toBe('[path=["home", "user]');
      expect(parts.description).toBe('Path split into components');
    });
  });

  describe('stripNamespace', () => {
    it('removes the namespace from the longname', () => {
      expect(name.stripNamespace('module:foo/bar/baz')).toBe('foo/bar/baz');
    });

    it('does not remove the namespace from a child member', () => {
      expect(name.stripNamespace('foo/bar.baz~event:qux')).toBe('foo/bar.baz~event:qux');
    });

    it('does not change longnames that do not have a namespace', () => {
      expect(name.stripNamespace('Foo#bar')).toBe('Foo#bar');
    });
  });

  describe('stripVariation', () => {
    it('does not change longnames with no variation', () => {
      expect(name.stripVariation('Foo#bar')).toBe('Foo#bar');
    });

    it('removes the variation if present', () => {
      expect(name.stripVariation('Foo#bar(qux)')).toBe('Foo#bar');
    });
  });

  describe('toParts', () => {
    it('breaks up a longname into the correct parts', () => {
      const parts = name.toParts('lib.Panel#open');

      expect(parts.name).toBe('open');
      expect(parts.memberof).toBe('lib.Panel');
      expect(parts.scope).toBe('#');
    });

    it('handles static names', () => {
      const parts = name.toParts('elements.selected.getVisible');

      expect(parts.name).toBe('getVisible');
      expect(parts.memberof).toBe('elements.selected');
      expect(parts.scope).toBe('.');
    });

    it('handles members of a prototype', () => {
      const parts = name.toParts('Validator.prototype.$element');

      expect(parts.name).toEqual('$element');
      expect(parts.memberof).toEqual('Validator');
      expect(parts.scope).toEqual('#');
    });

    it('handles inner names', () => {
      const parts = name.toParts('Button~_onclick');

      expect(parts.name).toEqual('_onclick');
      expect(parts.memberof).toEqual('Button');
      expect(parts.scope).toEqual('~');
    });

    it('handles global names', () => {
      const parts = name.toParts('close');

      expect(parts.name).toEqual('close');
      expect(parts.memberof).toEqual('');
      expect(parts.scope).toEqual('');
    });

    it('handles a single property that uses bracket notation', () => {
      const parts = name.toParts('channels["#ops"]#open');

      expect(parts.name).toEqual('open');
      expect(parts.memberof).toEqual('channels."#ops"');
      expect(parts.scope).toEqual('#');
    });

    it('handles consecutive properties that use bracket notation', () => {
      const parts = name.toParts('channels["#bots"]["log.max"]');

      expect(parts.name).toEqual('"log.max"');
      expect(parts.memberof).toEqual('channels."#bots"');
      expect(parts.scope).toEqual('.');
    });

    it('handles a property that uses single-quoted bracket notation', () => {
      const parts = name.toParts("channels['#ops']");

      expect(parts.name).toBe("'#ops'");
      expect(parts.memberof).toBe('channels');
      expect(parts.scope).toBe('.');
    });

    it('handles double-quoted strings', () => {
      const parts = name.toParts('"foo.bar"');

      expect(parts.name).toEqual('"foo.bar"');
      expect(parts.longname).toEqual('"foo.bar"');
      expect(parts.memberof).toEqual('');
      expect(parts.scope).toEqual('');
    });

    it('handles single-quoted strings', () => {
      const parts = name.toParts("'foo.bar'");

      expect(parts.name).toBe("'foo.bar'");
      expect(parts.longname).toBe("'foo.bar'");
      expect(parts.memberof).toBe('');
      expect(parts.scope).toBe('');
    });

    it('handles variations', () => {
      const parts = name.toParts('anim.fadein(2)');

      expect(parts.variation).toEqual('2');
      expect(parts.name).toEqual('fadein');
      expect(parts.longname).toEqual('anim.fadein(2)');
    });
  });
});
