-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added
watch
option to re-run the linters when a file changes ⌚️ (#2)
* Added `watch` option to re-run the linters when a file changes ⌚️ * Added watch events for adding/removing a file 🗳️ * Updated test text to increase clarity of functionality 🆑 * Set ignoreInitial on the watcher now that `add` has been added 🪨
- Loading branch information
Showing
8 changed files
with
349 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import { readFile } from 'fs' | ||
|
||
import chokidar from 'chokidar' | ||
|
||
import { Events } from '@Types' | ||
|
||
import { fileChangeEvent, watchFiles } from '../watchFiles' | ||
|
||
jest.mock('fs') | ||
jest.mock('chokidar') | ||
|
||
describe('watchFiles', () => { | ||
let mockWatcher: chokidar.FSWatcher | ||
|
||
const mockReadFile = (content: string) => { | ||
jest.mocked(readFile).mockImplementationOnce((_path: any, _encoding: any, callback?: (error: any, data: any) => void): void => { | ||
if (callback) { | ||
callback(null, content) | ||
} | ||
}) | ||
} | ||
|
||
beforeEach(() => { | ||
mockWatcher = { | ||
add: jest.fn(), | ||
close: jest.fn(), | ||
on: jest.fn(), | ||
unwatch: jest.fn(), | ||
} as unknown as chokidar.FSWatcher | ||
|
||
jest.mocked(chokidar.watch).mockReturnValue(mockWatcher) | ||
}) | ||
|
||
afterEach(() => { | ||
fileChangeEvent.removeAllListeners() | ||
}) | ||
|
||
it('initialises chokidar with the correct file and ignore patterns', () => { | ||
const filePatterns = ['**/*.ts'] | ||
const ignorePatterns = ['node_modules'] | ||
|
||
watchFiles({ filePatterns, ignorePatterns }) | ||
|
||
expect(chokidar.watch).toHaveBeenCalledWith(filePatterns, { | ||
ignored: ignorePatterns, | ||
ignoreInitial: true, | ||
persistent: true, | ||
}) | ||
}) | ||
|
||
it('emits a "FILE_CHANGED" event when saving an existing file (because there is no hash map yet)', done => { | ||
expect.assertions(1) | ||
|
||
const mockPath = 'mock/existing-file.ts' | ||
mockReadFile('open-and-save') | ||
|
||
fileChangeEvent.on(Events.FILE_CHANGED, params => { | ||
expect(params).toStrictEqual({ | ||
message: `File \`${mockPath}\` has been changed.`, | ||
path: mockPath, | ||
}) | ||
done() | ||
}) | ||
|
||
watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) | ||
|
||
const changeHandler = (mockWatcher.on as jest.Mock).mock.calls.find(call => call[0] === 'change')[1] | ||
changeHandler(mockPath) | ||
}) | ||
|
||
it('emits a "FILE_CHANGED" event if the file content changes', done => { | ||
expect.assertions(2) | ||
|
||
const mockPath = 'mock/old-file.ts' | ||
mockReadFile('old-content') | ||
|
||
fileChangeEvent.on(Events.FILE_CHANGED, params => { | ||
expect(params).toStrictEqual({ | ||
message: `File \`${mockPath}\` has been changed.`, | ||
path: mockPath, | ||
}) | ||
}) | ||
|
||
watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) | ||
|
||
const changeHandler = (mockWatcher.on as jest.Mock).mock.calls.find(call => call[0] === 'change')[1] | ||
changeHandler(mockPath) | ||
|
||
mockReadFile('new-content') | ||
changeHandler(mockPath) | ||
|
||
setTimeout(() => { | ||
done() | ||
}, 100) | ||
}) | ||
|
||
it('does not emit a "FILE_CHANGED" event if the file content did not change', done => { | ||
expect.assertions(1) | ||
|
||
const mockPath = 'mock/old-file.ts' | ||
mockReadFile('old-content') | ||
|
||
fileChangeEvent.on(Events.FILE_CHANGED, params => { | ||
expect(params).toStrictEqual({ | ||
message: `File \`${mockPath}\` has been changed.`, | ||
path: mockPath, | ||
}) | ||
}) | ||
|
||
watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) | ||
|
||
const changeHandler = (mockWatcher.on as jest.Mock).mock.calls.find(call => call[0] === 'change')[1] | ||
changeHandler(mockPath) | ||
changeHandler(mockPath) | ||
changeHandler(mockPath) | ||
|
||
setTimeout(() => { | ||
done() | ||
}, 100) | ||
}) | ||
|
||
it('emits a "FILE_CHANGED" event if a file is added', done => { | ||
expect.assertions(1) | ||
|
||
const mockPath = 'mock/new-file.ts' | ||
mockReadFile('new-content') | ||
|
||
fileChangeEvent.on(Events.FILE_CHANGED, params => { | ||
expect(params).toStrictEqual({ | ||
message: `File \`${mockPath}\` has been added.`, | ||
path: mockPath, | ||
}) | ||
done() | ||
}) | ||
|
||
watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) | ||
|
||
const changeHandler = (mockWatcher.on as jest.Mock).mock.calls.find(call => call[0] === 'add')[1] | ||
changeHandler(mockPath) | ||
}) | ||
|
||
it('emits a "FILE_CHANGED" event if a file is removed', done => { | ||
expect.assertions(1) | ||
|
||
const mockPath = 'mock/legacy-file.ts' | ||
mockReadFile('legacy-content') | ||
|
||
fileChangeEvent.on(Events.FILE_CHANGED, params => { | ||
expect(params).toStrictEqual({ | ||
message: `File \`${mockPath}\` has been removed.`, | ||
path: mockPath, | ||
}) | ||
done() | ||
}) | ||
|
||
watchFiles({ filePatterns: [mockPath], ignorePatterns: [] }) | ||
|
||
const changeHandler = (mockWatcher.on as jest.Mock).mock.calls.find(call => call[0] === 'unlink')[1] | ||
changeHandler(mockPath) | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { clearTerminal } from '../terminal' | ||
|
||
describe('clearTerminal', () => { | ||
|
||
it('calls process.stdout.write to clear the terminal', () => { | ||
const mockWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) | ||
|
||
clearTerminal() | ||
|
||
expect(mockWrite).toHaveBeenCalledWith('\x1Bc\x1B[3J\x1B[2J\x1B[H') | ||
mockWrite.mockRestore() | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
const clearTerminal = () => process.stdout.write('\x1Bc\x1B[3J\x1B[2J\x1B[H') | ||
|
||
export { | ||
clearTerminal, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { createHash } from 'crypto' | ||
import { EventEmitter } from 'events' | ||
import { readFile } from 'fs' | ||
|
||
import chokidar from 'chokidar' | ||
|
||
import { Events } from '@Types' | ||
|
||
interface WatchFiles { | ||
filePatterns: Array<string> | ||
ignorePatterns: Array<string> | ||
} | ||
|
||
const fileChangeEvent = new EventEmitter() | ||
|
||
const fileHashes = new Map<string, string>() | ||
|
||
const getHash = (data: string) => createHash('md5').update(data).digest('hex') | ||
|
||
const watchFiles = ({ filePatterns, ignorePatterns }: WatchFiles) => { | ||
const watcher = chokidar.watch(filePatterns, { | ||
ignored: ignorePatterns, | ||
ignoreInitial: true, | ||
persistent: true, | ||
}) | ||
|
||
watcher.on('add', (path, _stats) => { | ||
fileChangeEvent.emit(Events.FILE_CHANGED, { | ||
message: `File \`${path}\` has been added.`, | ||
path, | ||
}) | ||
}) | ||
|
||
watcher.on('change', (path, _stats) => { | ||
readFile(path, 'utf8', (_error, data) => { | ||
const newHash = getHash(data) | ||
if (fileHashes.get(path) !== newHash) { | ||
fileHashes.set(path, newHash) | ||
fileChangeEvent.emit(Events.FILE_CHANGED, { | ||
message: `File \`${path}\` has been changed.`, | ||
path, | ||
}) | ||
} | ||
}) | ||
}) | ||
|
||
watcher.on('unlink', path => { | ||
fileChangeEvent.emit(Events.FILE_CHANGED, { | ||
message: `File \`${path}\` has been removed.`, | ||
path, | ||
}) | ||
}) | ||
} | ||
|
||
export { | ||
fileChangeEvent, | ||
watchFiles, | ||
} |
Oops, something went wrong.