From 449a863a56df86a72be83773ac739065658406c1 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Mon, 8 Jan 2018 16:43:19 +1100 Subject: [PATCH] test: add git tests --- __mocks__/execa.js | 21 ++++ __mocks__/prettier.js | 20 ++++ package.json | 1 + src/__tests__/pretty-quick.test.js | 18 +++- src/__tests__/scm-git.test.js | 162 +++++++++++++++++++++++++++++ src/formatFiles.js | 10 +- src/index.js | 9 +- yarn.lock | 4 + 8 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 __mocks__/execa.js create mode 100644 __mocks__/prettier.js create mode 100644 src/__tests__/scm-git.test.js diff --git a/__mocks__/execa.js b/__mocks__/execa.js new file mode 100644 index 0000000..347d08e --- /dev/null +++ b/__mocks__/execa.js @@ -0,0 +1,21 @@ +const mockStream = () => ({ + once: jest.fn(), + on: jest.fn(), + removeListener: jest.fn(), + pipe: jest.fn(), +}); + +const mockExeca = jest.fn().mockReturnValue({ + stdout: mockStream(), + stderr: mockStream(), + kill: () => {}, +}); + +const mockExecaSync = jest.fn().mockReturnValue({ + stdout: '', + stderr: '', + kill: () => {}, +}); + +module.exports = mockExeca; +module.exports.sync = mockExecaSync; diff --git a/__mocks__/prettier.js b/__mocks__/prettier.js new file mode 100644 index 0000000..3036d22 --- /dev/null +++ b/__mocks__/prettier.js @@ -0,0 +1,20 @@ +const prettierMock = { + format: jest.fn().mockImplementation(input => 'formatted:' + input), + resolveConfig: { + sync: jest.fn().mockImplementation(file => ({ file })), + }, + getSupportInfo: jest.fn().mockReturnValue({ + languages: [ + { + name: 'JavaScript', + extensions: ['.js'], + }, + { + name: 'Markdown', + extensions: ['.md'], + }, + ], + }), +}; + +module.exports = prettierMock; diff --git a/package.json b/package.json index f56c51a..5322e33 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "eslint-plugin-prettier": "^2.4.0", "husky": "^0.14.3", "jest": "^22.0.4", + "mock-fs": "^4.4.2", "prettier": "1.9.2", "semantic-release": "^11.0.2" } diff --git a/src/__tests__/pretty-quick.test.js b/src/__tests__/pretty-quick.test.js index 34e8aa8..81801f3 100644 --- a/src/__tests__/pretty-quick.test.js +++ b/src/__tests__/pretty-quick.test.js @@ -1,3 +1,17 @@ -it('has not tests', () => { - // yet... +import mock from 'mock-fs'; + +import prettyQuick from '..'; + +jest.mock('execa'); + +afterEach(() => mock.restore()); + +test('throws an error when no vcs is found', () => { + mock({ + 'root/README.md': '', + }); + + expect(() => prettyQuick('root')).toThrowError( + 'Unable to detect a source control manager.' + ); }); diff --git a/src/__tests__/scm-git.test.js b/src/__tests__/scm-git.test.js new file mode 100644 index 0000000..bf47cc4 --- /dev/null +++ b/src/__tests__/scm-git.test.js @@ -0,0 +1,162 @@ +import mock from 'mock-fs'; +import execa from 'execa'; +import fs from 'fs'; + +import prettyQuick from '..'; + +jest.mock('execa'); + +afterEach(() => { + mock.restore(); + jest.clearAllMocks(); +}); + +const mockGitFs = () => { + mock({ + 'root/.git': {}, + 'root/foo.js': 'foo()', + 'root/bar.md': '# foo', + }); + execa.sync.mockImplementation((command, args) => { + if (command !== 'git') { + throw new Error(`unexpected command: ${command}`); + } + switch (args[0]) { + case 'ls-files': + return { stdout: '' }; + case 'diff': + return { stdout: './foo.js\n' + './bar.md\n' }; + case 'add': + return { stdout: '' }; + default: + throw new Error(`unexpected arg0: ${args[0]}`); + } + }); +}; + +describe('with git', () => { + test('calls `git merge-base`', () => { + mock({ + 'root/.git': {}, + }); + + prettyQuick('root'); + + expect(execa.sync).toHaveBeenCalledWith( + 'git', + ['merge-base', 'HEAD', 'master'], + { cwd: 'root' } + ); + }); + + test('with --staged does NOT call `git merge-base`', () => { + mock({ + 'root/.git': {}, + }); + + prettyQuick('root'); + + expect(execa.sync).not.toHaveBeenCalledWith('git', [ + 'merge-base', + 'HEAD', + 'master', + ]); + }); + + test('calls `git diff --name-only` with revision', () => { + mock({ + 'root/.git': {}, + }); + + prettyQuick('root', { since: 'banana' }); + + expect(execa.sync).toHaveBeenCalledWith( + 'git', + ['diff', '--name-only', '--diff-filter=ACMRTUB', 'banana'], + { cwd: 'root' } + ); + }); + + test('calls `git ls-files`', () => { + mock({ + 'root/.git': {}, + }); + + prettyQuick('root', { since: 'banana' }); + + expect(execa.sync).toHaveBeenCalledWith( + 'git', + ['ls-files', '--others', '--exclude-standard'], + { cwd: 'root' } + ); + }); + + test('calls onFoundSinceRevision with return value from `git merge-base`', () => { + const onFoundSinceRevision = jest.fn(); + + mock({ + 'root/.git': {}, + }); + execa.sync.mockReturnValue({ stdout: 'banana' }); + + prettyQuick('root', { onFoundSinceRevision }); + + expect(onFoundSinceRevision).toHaveBeenCalledWith('git', 'banana'); + }); + + test('calls onFoundChangedFiles with changed files', () => { + const onFoundChangedFiles = jest.fn(); + mockGitFs(); + + prettyQuick('root', { since: 'banana', onFoundChangedFiles }); + + expect(onFoundChangedFiles).toHaveBeenCalledWith(['./foo.js', './bar.md']); + }); + + test('calls onWriteFile with changed files', () => { + const onWriteFile = jest.fn(); + mockGitFs(); + + prettyQuick('root', { since: 'banana', onWriteFile }); + + expect(onWriteFile).toHaveBeenCalledWith('./foo.js'); + expect(onWriteFile).toHaveBeenCalledWith('./bar.md'); + }); + + test('writes formatted files to disk', () => { + const onWriteFile = jest.fn(); + + mockGitFs(); + + prettyQuick('root', { since: 'banana', onWriteFile }); + + expect(fs.readFileSync('root/foo.js', 'utf8')).toEqual('formatted:foo()'); + expect(fs.readFileSync('root/bar.md', 'utf8')).toEqual('formatted:# foo'); + }); + + test('with --staged stages changed files', () => { + mockGitFs(); + + prettyQuick('root', { since: 'banana', staged: true }); + + expect(execa.sync).toHaveBeenCalledWith('git', ['add', './foo.js'], { + cwd: 'root', + }); + expect(execa.sync).toHaveBeenCalledWith('git', ['add', './bar.md'], { + cwd: 'root', + }); + }); + + test('without --staged does NOT stage changed files', () => { + mockGitFs(); + + prettyQuick('root', { since: 'banana' }); + + expect(execa.sync).not.toHaveBeenCalledWith('git', ['add', './foo.js'], { + cwd: 'root', + }); + expect(execa.sync).not.toHaveBeenCalledWith('git', ['add', './bar.md'], { + cwd: 'root', + }); + }); +}); diff --git a/src/formatFiles.js b/src/formatFiles.js index 32a3f2e..815cb43 100644 --- a/src/formatFiles.js +++ b/src/formatFiles.js @@ -1,20 +1,22 @@ import { readFileSync, writeFileSync } from 'fs'; import { resolveConfig, format } from 'prettier'; +import { join } from 'path'; -export default (files, { config, onWriteFile }) => { - for (const file of files) { +export default (directory, files, { config, onWriteFile } = {}) => { + for (const relative of files) { + const file = join(directory, relative); const options = resolveConfig.sync(file, { config }); const input = readFileSync(file, 'utf8'); const output = format( input, - Object.assign(options, { + Object.assign({}, options, { filepath: file, }) ); if (output !== input) { writeFileSync(file, output); - onWriteFile && onWriteFile(file); + onWriteFile && onWriteFile(relative); } } }; diff --git a/src/index.js b/src/index.js index 8af16ae..b8261ee 100644 --- a/src/index.js +++ b/src/index.js @@ -14,7 +14,7 @@ export default ( onFoundSinceRevision, onFoundChangedFiles, onWriteFile, - } + } = {} ) => { const scm = scms(directory); if (!scm) { @@ -27,17 +27,16 @@ export default ( const changedFiles = scm .getChangedFiles(directory, revision) - .map(file => relative(directory, file)) .filter(isSupportedExtension) .filter(createIgnorer(directory)); onFoundChangedFiles && onFoundChangedFiles(changedFiles); - formatFiles(changedFiles, { + formatFiles(directory, changedFiles, { config, onWriteFile: file => { - onWriteFile(file); - scm.stageFile(directory, file); + onWriteFile && onWriteFile(file); + staged && scm.stageFile(directory, file); }, }); }; diff --git a/yarn.lock b/yarn.lock index 6a73705..3e3b3c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3114,6 +3114,10 @@ minimist@~0.0.1: dependencies: minimist "0.0.8" +mock-fs@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.4.2.tgz#09dec5313f97095a450be6aa2ad8ab6738d63d6b" + modify-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2"