/* eslint-env jasmine */ import gitGraphAst from './gitGraphAst'; import { parser } from './parser/gitGraph'; import randomString from 'crypto-random-string'; import cryptoRandomString from 'crypto-random-string'; jest.mock('crypto-random-string'); describe('when parsing a gitGraph', function() { let randomNumber; beforeEach(function() { parser.yy = gitGraphAst; parser.yy.clear(); randomNumber = 0; cryptoRandomString.mockImplementation(() => { randomNumber = randomNumber + 1; return String(randomNumber); }); }); afterEach(function() { cryptoRandomString.mockReset(); }); it('should handle a gitGraph defintion', function() { const str = 'gitGraph:\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(1); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getDirection()).toBe('LR'); expect(Object.keys(parser.yy.getBranches()).length).toBe(1); }); it('should handle a gitGraph defintion with empty options', function() { const str = 'gitGraph:\n' + 'options\n' + 'end\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(parser.yy.getOptions()).toEqual({}); expect(Object.keys(commits).length).toBe(1); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getDirection()).toBe('LR'); expect(Object.keys(parser.yy.getBranches()).length).toBe(1); }); it('should handle a gitGraph defintion with valid options', function() { const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(parser.yy.getOptions()['key']).toBe('value'); expect(Object.keys(commits).length).toBe(1); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getDirection()).toBe('LR'); expect(Object.keys(parser.yy.getBranches()).length).toBe(1); }); it('should not fail on a gitGraph with malformed json', function() { const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(1); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getDirection()).toBe('LR'); expect(Object.keys(parser.yy.getBranches()).length).toBe(1); }); it('should handle set direction', function() { const str = 'gitGraph BT:\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(1); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getDirection()).toBe('BT'); expect(Object.keys(parser.yy.getBranches()).length).toBe(1); }); it('should checkout a branch', function() { const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(0); expect(parser.yy.getCurrentBranch()).toBe('new'); }); it('should add commits to checked out branch', function() { const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(2); expect(parser.yy.getCurrentBranch()).toBe('new'); const branchCommit = parser.yy.getBranches()['new']; expect(branchCommit).not.toBeNull(); expect(commits[branchCommit].parent).not.toBeNull(); }); it('should handle commit with args', function() { const str = 'gitGraph:\n' + 'commit "a commit"\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(1); const key = Object.keys(commits)[0]; expect(commits[key].message).toBe('a commit'); expect(parser.yy.getCurrentBranch()).toBe('master'); }); it('it should reset a branch', function() { const str = 'gitGraph:\n' + 'commit\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'reset master\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(3); expect(parser.yy.getCurrentBranch()).toBe('newbranch'); expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); }); it('reset can take an argument', function() { const str = 'gitGraph:\n' + 'commit\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'reset master^\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(3); expect(parser.yy.getCurrentBranch()).toBe('newbranch'); const master = commits[parser.yy.getBranches()['master']]; expect(parser.yy.getHead().id).toEqual(master.parent); }); it('it should handle fast forwardable merges', function() { const str = 'gitGraph:\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'commit\n' + 'checkout master\n' + 'merge newbranch\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(3); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); }); it('it should handle cases when merge is a noop', function() { const str = 'gitGraph:\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'commit\n' + 'merge master\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(3); expect(parser.yy.getCurrentBranch()).toBe('newbranch'); expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']); }); it('it should handle merge with 2 parents', function() { const str = 'gitGraph:\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'commit\n' + 'checkout master\n' + 'commit\n' + 'merge newbranch\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(5); expect(parser.yy.getCurrentBranch()).toBe('master'); expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']); expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); }); it('it should handle ff merge when history walk has two parents (merge commit)', function() { const str = 'gitGraph:\n' + 'commit\n' + 'branch newbranch\n' + 'checkout newbranch\n' + 'commit\n' + 'commit\n' + 'checkout master\n' + 'commit\n' + 'merge newbranch\n' + 'commit\n' + 'checkout newbranch\n' + 'merge master\n'; parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits).length).toBe(6); expect(parser.yy.getCurrentBranch()).toBe('newbranch'); expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']); expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']); parser.yy.prettyPrint(); }); it('it should generate a secure random ID for commits', function() { const str = 'gitGraph:\n' + 'commit\n' + 'commit\n'; const EXPECTED_LENGTH = 7; const EXPECTED_CHARACTERS = '0123456789abcdef'; let idCount = 0; randomString.mockImplementation(options => { if ( options.length === EXPECTED_LENGTH && options.characters === EXPECTED_CHARACTERS && Object.keys(options).length === 2 ) { const id = `abcdef${idCount}`; idCount += 1; return id; } return 'unexpected-ID'; }); parser.parse(str); const commits = parser.yy.getCommits(); expect(Object.keys(commits)).toEqual(['abcdef0', 'abcdef1']); Object.keys(commits).forEach(key => { expect(commits[key].id).toEqual(key); }); }); it('it should generate an array of known branches', function() { const str = 'gitGraph:\n' + 'commit\n' + 'branch b1\n' + 'checkout b1\n' + 'commit\n' + 'commit\n' + 'branch b2\n'; parser.parse(str); const branches = gitGraphAst.getBranchesAsObjArray(); expect(branches).toHaveLength(3); expect(branches[0]).toHaveProperty('name', 'master'); expect(branches[1]).toHaveProperty('name', 'b1'); expect(branches[2]).toHaveProperty('name', 'b2'); }); });