import gitGraphAst from './gitGraphAst.js';
import { parser } from './parser/gitGraph.jison';

describe('when parsing a gitGraph', function () {
  beforeEach(function () {
    parser.yy = gitGraphAst;
    parser.yy.clear();
  });
  it('should handle a gitGraph commit with NO pararms, get auto-generated reandom ID', function () {
    const str = `gitGraph:
    commit
    `;
    parser.parse(str);
    const commits = parser.yy.getCommits();
    //console.info(commits);
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit id only', function () {
    const str = `gitGraph:
    commit id:"1111"
    `;
    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit tag only', function () {
    const str = `gitGraph:
    commit tag:"test"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual(['test']);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit type HIGHLIGHT only', function () {
    const str = `gitGraph:
    commit type: HIGHLIGHT
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(2);
  });

  it('should handle a gitGraph commit with custom commit type REVERSE only', function () {
    const str = `gitGraph:
    commit type: REVERSE
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle a gitGraph commit with custom commit type NORMAL only', function () {
    const str = `gitGraph:
    commit type: NORMAL
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit msg only', function () {
    const str = `gitGraph:
    commit "test commit"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test commit');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit "msg:" key only', function () {
    const str = `gitGraph:
    commit msg: "test commit"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test commit');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual([]);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit id, tag  only', function () {
    const str = `gitGraph:
    commit id:"1111" tag: "test tag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(0);
  });

  it('should handle a gitGraph commit with custom commit type, tag  only', function () {
    const str = `gitGraph:
    commit type:HIGHLIGHT tag: "test tag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(2);
  });

  it('should handle a gitGraph commit with custom commit tag and type only', function () {
    const str = `gitGraph:
    commit tag: "test tag" type:HIGHLIGHT
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).not.toBeNull();
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(2);
  });

  it('should handle a gitGraph commit with custom commit id, type and tag only', function () {
    const str = `gitGraph:
    commit id:"1111" type:REVERSE tag: "test tag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle a gitGraph commit with custom commit id, type,  tag and msg', function () {
    const str = `gitGraph:
    commit id:"1111" type:REVERSE tag: "test tag" msg:"test msg"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test msg');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle a gitGraph commit with custom  type,tag, msg, commit id,', function () {
    const str = `gitGraph:
    commit type:REVERSE tag: "test tag" msg: "test msg" id: "1111"

    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test msg');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle a gitGraph commit with custom  tag, msg, commit id, type,', function () {
    const str = `gitGraph:
    commit  tag: "test tag" msg:"test msg" id:"1111" type:REVERSE
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test msg');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle a gitGraph commit with custom msg, commit id, type,tag', function () {
    const str = `gitGraph:
    commit msg:"test msg" id:"1111" type:REVERSE tag: "test tag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
    const key = commits.keys().next().value;
    expect(commits.get(key).message).toBe('test msg');
    expect(commits.get(key).id).toBe('1111');
    expect(commits.get(key).tags).toStrictEqual(['test tag']);
    expect(commits.get(key).type).toBe(1);
  });

  it('should handle 3 straight commits', function () {
    const str = `gitGraph:
    commit
    commit
    commit
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(3);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(1);
  });

  it('should handle new branch creation', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('testBranch');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
  });

  it('should allow quoted branch names', function () {
    const str = `gitGraph:
    commit
    branch "branch"
    checkout "branch"
    commit
    checkout main
    merge "branch"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(3);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2, commit3] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit2).branch).toBe('branch');
    expect(commits.get(commit3).branch).toBe('main');
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([{ name: 'main' }, { name: 'branch' }]);
  });

  it('should allow _-./ characters in branch names', function () {
    const str = `gitGraph:
    commit
    branch azAZ_-./test
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('azAZ_-./test');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
  });

  it('should allow branch names starting with numbers', function () {
    const str = `gitGraph:
    commit
    %% branch names starting with numbers are not recommended, but are supported by git
    branch 1.0.1
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('1.0.1');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
  });

  it('should allow branch names starting with unusual prefixes', function () {
    const str = `gitGraph:
    commit
    %% branch names starting with numbers are not recommended, but are supported by git
    branch branch01
    branch checkout02
    branch cherry-pick03
    branch branch/example-branch
    branch merge/test_merge
    %% single character branch name
    branch A
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('A');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(7);
    expect([...parser.yy.getBranches().keys()]).toEqual(
      expect.arrayContaining([
        'branch01',
        'checkout02',
        'cherry-pick03',
        'branch/example-branch',
        'merge/test_merge',
        'A',
      ])
    );
  });

  it('should handle new branch checkout', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    checkout testBranch
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('testBranch');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
  });
  it('should handle new branch checkout with order', function () {
    const str = `gitGraph:
    commit
    branch test1 order: 3
    branch test2 order: 2
    branch test3 order: 1
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('test3');
    expect(parser.yy.getBranches().size).toBe(4);
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'test3' },
      { name: 'test2' },
      { name: 'test1' },
    ]);
  });
  it('should handle new branch checkout with and without order', function () {
    const str = `gitGraph:
    commit
    branch test1 order: 1
    branch test2
    branch test3
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('test3');
    expect(parser.yy.getBranches().size).toBe(4);
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'test2' },
      { name: 'test3' },
      { name: 'test1' },
    ]);
  });

  it('should handle new branch checkout & commit', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    checkout testBranch
    commit
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(2);
    expect(parser.yy.getCurrentBranch()).toBe('testBranch');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit1).parents).toStrictEqual([]);
    expect(commits.get(commit2).branch).toBe('testBranch');
    expect(commits.get(commit2).parents).toStrictEqual([commit1]);
  });

  it('should handle new branch checkout & commit and merge', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    checkout testBranch
    commit
    commit
    checkout main
    merge testBranch
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(4);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2, commit3, commit4] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit1).parents).toStrictEqual([]);
    expect(commits.get(commit2).branch).toBe('testBranch');
    expect(commits.get(commit2).parents).toStrictEqual([commits.get(commit1).id]);
    expect(commits.get(commit3).branch).toBe('testBranch');
    expect(commits.get(commit3).parents).toStrictEqual([commits.get(commit2).id]);
    expect(commits.get(commit4).branch).toBe('main');
    expect(commits.get(commit4).parents).toStrictEqual([
      commits.get(commit1).id,
      commits.get(commit3).id,
    ]);
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'testBranch' },
    ]);
  });

  it('should handle new branch switch', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    switch testBranch
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(1);
    expect(parser.yy.getCurrentBranch()).toBe('testBranch');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
  });

  it('should handle new branch switch & commit', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    switch testBranch
    commit
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(2);
    expect(parser.yy.getCurrentBranch()).toBe('testBranch');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit1).parents).toStrictEqual([]);
    expect(commits.get(commit2).branch).toBe('testBranch');
    expect(commits.get(commit2).parents).toStrictEqual([commit1]);
  });

  it('should handle new branch switch & commit and merge', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    switch testBranch
    commit
    commit
    switch main
    merge testBranch
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(4);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2, commit3, commit4] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit1).parents).toStrictEqual([]);
    expect(commits.get(commit2).branch).toBe('testBranch');
    expect(commits.get(commit2).parents).toStrictEqual([commits.get(commit1).id]);
    expect(commits.get(commit3).branch).toBe('testBranch');
    expect(commits.get(commit3).parents).toStrictEqual([commits.get(commit2).id]);
    expect(commits.get(commit4).branch).toBe('main');
    expect(commits.get(commit4).parents).toStrictEqual([
      commits.get(commit1).id,
      commits.get(commit3).id,
    ]);
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'testBranch' },
    ]);
  });

  it('should handle merge tags', function () {
    const str = `gitGraph:
    commit
    branch testBranch
    checkout testBranch
    commit
    checkout main
    merge testBranch tag: "merge-tag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(3);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');
    expect(parser.yy.getBranches().size).toBe(2);
    const [commit1, commit2, commit3] = commits.keys();
    expect(commits.get(commit1).branch).toBe('main');
    expect(commits.get(commit1).parents).toStrictEqual([]);

    expect(commits.get(commit2).branch).toBe('testBranch');
    expect(commits.get(commit2).parents).toStrictEqual([commits.get(commit1).id]);

    expect(commits.get(commit3).branch).toBe('main');
    expect(commits.get(commit3).parents).toStrictEqual([
      commits.get(commit1).id,
      commits.get(commit2).id,
    ]);
    expect(commits.get(commit3).tags).toStrictEqual(['merge-tag']);
    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'testBranch' },
    ]);
  });

  it('should handle merge with custom ids, tags and typr', function () {
    const str = `gitGraph:
      commit
      branch testBranch
      checkout testBranch
      commit
      checkout main
      %% Merge Tag and ID
      merge testBranch tag: "merge-tag" id: "2-222"
      branch testBranch2
      checkout testBranch2
      commit
      checkout main
      %% Merge ID and Tag (reverse order)
      merge testBranch2 id: "4-444" tag: "merge-tag2" type:HIGHLIGHT
      branch testBranch3
      checkout testBranch3
      commit
      checkout main
      %% just Merge ID
      merge testBranch3 id: "6-666"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    expect(commits.size).toBe(7);
    expect(parser.yy.getCurrentBranch()).toBe('main');
    expect(parser.yy.getDirection()).toBe('LR');

    // The order of these commits is in alphabetical order of IDs
    const [
      mainCommit,
      testBranchCommit,
      testBranchMerge,
      testBranch2Commit,
      testBranch2Merge,
      testBranch3Commit,
      testBranch3Merge,
    ] = [...commits.values()];

    expect(mainCommit.branch).toBe('main');
    expect(mainCommit.parents).toStrictEqual([]);

    expect(testBranchCommit.branch).toBe('testBranch');
    expect(testBranchCommit.parents).toStrictEqual([mainCommit.id]);

    expect(testBranchMerge.branch).toBe('main');
    expect(testBranchMerge.parents).toStrictEqual([mainCommit.id, testBranchCommit.id]);
    expect(testBranchMerge.tags).toStrictEqual(['merge-tag']);
    expect(testBranchMerge.id).toBe('2-222');

    expect(testBranch2Merge.branch).toBe('main');
    expect(testBranch2Merge.parents).toStrictEqual([testBranchMerge.id, testBranch2Commit.id]);
    expect(testBranch2Merge.tags).toStrictEqual(['merge-tag2']);
    expect(testBranch2Merge.id).toBe('4-444');
    expect(testBranch2Merge.customType).toBe(2);
    expect(testBranch2Merge.customId).toBe(true);

    expect(testBranch3Merge.branch).toBe('main');
    expect(testBranch3Merge.parents).toStrictEqual([testBranch2Merge.id, testBranch3Commit.id]);
    expect(testBranch3Merge.id).toBe('6-666');

    expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
      { name: 'main' },
      { name: 'testBranch' },
      { name: 'testBranch2' },
      { name: 'testBranch3' },
    ]);
  });

  it('should support cherry-picking commits', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch develop
    commit id:"A"
    checkout main
    cherry-pick id:"A"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][2];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['cherry-pick:A']);
    expect(commits.get(cherryPickCommitID).branch).toBe('main');
  });

  it('should support cherry-picking commits with custom tag', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch develop
    commit id:"A"
    checkout main
    cherry-pick id:"A" tag:"MyTag"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][2];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['MyTag']);
    expect(commits.get(cherryPickCommitID).branch).toBe('main');
  });

  it('should support cherry-picking commits with no tag', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch develop
    commit id:"A"
    checkout main
    cherry-pick id:"A" tag:""
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][2];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual([]);
    expect(commits.get(cherryPickCommitID).branch).toBe('main');
  });

  it('should support cherry-picking of merge commits', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    cherry-pick id: "M" parent:"B"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][4];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['cherry-pick:M|parent:B']);
    expect(commits.get(cherryPickCommitID).branch).toBe('release');
  });

  it('should support cherry-picking of merge commits with tag', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    cherry-pick id: "M" parent:"ZERO" tag: "v1.0"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][4];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['v1.0']);
    expect(commits.get(cherryPickCommitID).branch).toBe('release');
  });

  it('should support cherry-picking of merge commits with additional commit', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    commit id: "C"
    cherry-pick id: "M" tag: "v2.1:ZERO" parent:"ZERO"
    commit id: "D"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][5];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['v2.1:ZERO']);
    expect(commits.get(cherryPickCommitID).branch).toBe('release');
  });

  it('should support cherry-picking of merge commits with empty tag', function () {
    const str = `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    commit id: "C"
    cherry-pick id:"M" parent: "ZERO" tag:""
    commit id: "D"
    cherry-pick id:"M" tag:"" parent: "B"
    `;

    parser.parse(str);
    const commits = parser.yy.getCommits();
    const cherryPickCommitID = [...commits.keys()][5];
    const cherryPickCommitID2 = [...commits.keys()][7];
    expect(commits.get(cherryPickCommitID).tags).toStrictEqual([]);
    expect(commits.get(cherryPickCommitID2).tags).toStrictEqual([]);
    expect(commits.get(cherryPickCommitID).branch).toBe('release');
  });

  it('should fail cherry-picking of merge commits if the parent of merge commits is not specified', function () {
    expect(() =>
      parser
        .parse(
          `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    commit id: "C"
    cherry-pick id:"M"
    `
        )
        .toThrow(
          'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
        )
    );
  });

  it('should fail cherry-picking of merge commits when the parent provided is not an immediate parent of cherry picked commit', function () {
    expect(() =>
      parser
        .parse(
          `gitGraph
    commit id: "ZERO"
    branch feature
    branch release
    checkout feature
    commit id: "A"
    commit id: "B"
    checkout main
    merge feature id: "M"
    checkout release
    commit id: "C"
    cherry-pick id:"M" parent: "A"
    `
        )
        .toThrow(
          'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
        )
    );
  });

  it('should throw error when try to branch existing branch: main', function () {
    const str = `gitGraph
    commit
    branch testBranch
    commit
    branch main
    commit
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout main")'
      );
    }
  });
  it('should throw error when try to branch existing branch: testBranch', function () {
    const str = `gitGraph
    commit
    branch testBranch
    commit
    branch testBranch
    commit
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout testBranch")'
      );
    }
  });
  it('should throw error when try to checkout unknown branch: testBranch', function () {
    const str = `gitGraph
    commit
    checkout testBranch
    commit
    branch testBranch
    commit
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Trying to checkout branch which is not yet created. (Help try using "branch testBranch")'
      );
    }
  });
  it('should throw error when trying to merge, when current branch has no commits', function () {
    const str = `gitGraph
    merge testBranch
    commit
    checkout testBranch
    commit
    branch testBranch
    commit
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe('Incorrect usage of "merge". Current branch (main)has no commits');
    }
  });
  it('should throw error when trying to merge unknown branch', function () {
    const str = `gitGraph
    commit
    merge testBranch
    commit
    checkout testBranch
    commit
    branch testBranch
    commit
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Incorrect usage of "merge". Branch to be merged (testBranch) does not exist'
      );
    }
  });
  it('should throw error when trying to merge branch to itself', function () {
    const str = `gitGraph
    commit
    branch testBranch
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe('Incorrect usage of "merge". Cannot merge a branch to itself');
    }
  });

  it('should throw error when using existing id as merge ID', function () {
    const str = `gitGraph
    commit id: "1-111"
    branch testBranch
    commit id: "2-222"
    commit id: "3-333"
    checkout main
    merge testBranch id: "1-111"
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Incorrect usage of "merge". Commit with id:1-111 already exists, use different custom Id'
      );
    }
  });
  it('should throw error when trying to merge branches having same heads', function () {
    const str = `gitGraph
    commit
    branch testBranch
    checkout main
    merge testBranch
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe('Incorrect usage of "merge". Both branches have same head');
    }
  });
  it('should throw error when trying to merge branch which has no commits', function () {
    const str = `gitGraph
    branch test1

    checkout main
    commit
    merge test1
    `;

    try {
      parser.parse(str);
      // Fail test if above expression doesn't throw anything.
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe(
        'Incorrect usage of "merge". Branch to be merged (test1) has no commits'
      );
    }
  });
  describe('accessibility', () => {
    it('should handle a title and a description (accDescr)', () => {
      const str = `gitGraph:
      accTitle: This is a title
      accDescr: This is a description
    commit
    `;
      parser.parse(str);
      expect(parser.yy.getAccTitle()).toBe('This is a title');
      expect(parser.yy.getAccDescription()).toBe('This is a description');
    });
    it('should handle a title and a multiline description (accDescr)', () => {
      const str = `gitGraph:
      accTitle: This is a title
      accDescr {
        This is a description
        using multiple lines
      }
    commit
    `;
      parser.parse(str);
      expect(parser.yy.getAccTitle()).toBe('This is a title');
      expect(parser.yy.getAccDescription()).toBe('This is a description\nusing multiple lines');
    });
  });

  describe('unsafe properties', () => {
    for (const prop of ['__proto__', 'constructor']) {
      it(`should work with custom commit id or branch name ${prop}`, () => {
        const str = `gitGraph
    commit id:"${prop}"
    branch ${prop}
    checkout ${prop}
    commit
    checkout main
    merge ${prop}
    `;
        parser.parse(str);
        const commits = parser.yy.getCommits();
        expect(commits.size).toBe(3);
        expect(commits.keys().next().value).toBe(prop);
        expect(parser.yy.getCurrentBranch()).toBe('main');
        expect(parser.yy.getBranches().size).toBe(2);
        expect(parser.yy.getBranchesAsObjArray()[1].name).toBe(prop);
      });
    }
  });
});
