From 9aa1215e2157d2b7c7775b9218b8a180d1fcef25 Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Wed, 1 Mar 2017 07:50:39 -0800 Subject: [PATCH] packager: DependencyGraph-test: go through fs.watch mock rather than manual API Reviewed By: davidaurelio Differential Revision: D4628593 fbshipit-source-id: 43ccdb038bd387e87096f2a7020c98d915fa5bba --- .../src/node-haste/__mocks__/graceful-fs.js | 34 +++++ .../__tests__/DependencyGraph-test.js | 138 +++++++++--------- .../metro-bundler/src/node-haste/index.js | 5 +- 3 files changed, 111 insertions(+), 66 deletions(-) diff --git a/packages/metro-bundler/src/node-haste/__mocks__/graceful-fs.js b/packages/metro-bundler/src/node-haste/__mocks__/graceful-fs.js index 8a885f8b..bd429181 100644 --- a/packages/metro-bundler/src/node-haste/__mocks__/graceful-fs.js +++ b/packages/metro-bundler/src/node-haste/__mocks__/graceful-fs.js @@ -8,6 +8,7 @@ */ 'use strict'; +const {EventEmitter} = require('events'); const {dirname} = require.requireActual('path'); const fs = jest.genMockFromModule('fs'); const path = require('path'); @@ -96,7 +97,12 @@ fs.readFileSync.mockImplementation(function(filepath, encoding) { function makeStatResult(node) { const isSymlink = node != null && node.SYMLINK != null; return { + isBlockDevice: () => false, + isCharacterDevice: () => false, isDirectory: () => node != null && typeof node === 'object' && !isSymlink, + isFIFO: () => false, + isFile: () => node != null && typeof node === 'string', + isSocket: () => false, isSymbolicLink: () => isSymlink, mtime, }; @@ -242,6 +248,34 @@ fs.createWriteStream.mock.returned = []; fs.__setMockFilesystem = object => (filesystem = object); +const watcherListByPath = new Map(); + +fs.watch.mockImplementation((filename, options, listener) => { + if (options.recursive) { + throw new Error('recursive watch not implemented'); + } + let watcherList = watcherListByPath.get(filename); + if (watcherList == null) { + watcherList = []; + watcherListByPath.set(filename, watcherList); + } + const fsWatcher = new EventEmitter(); + fsWatcher.on('change', listener); + fsWatcher.close = () => { + watcherList.splice(watcherList.indexOf(fsWatcher), 1); + fsWatcher.close = () => { throw new Error('FSWatcher is already closed'); }; + }; + watcherList.push(fsWatcher); +}); + +fs.__triggerWatchEvent = (eventType, filename) => { + const directWatchers = watcherListByPath.get(filename) || []; + directWatchers.forEach(wtc => wtc.emit('change', eventType)); + const dirPath = path.dirname(filename); + const dirWatchers = watcherListByPath.get(dirPath) || []; + dirWatchers.forEach(wtc => wtc.emit('change', eventType, path.relative(dirPath, filename))); +}; + function getToNode(filepath) { // Ignore the drive for Windows paths. if (filepath.match(/^[a-zA-Z]:\\/)) { diff --git a/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js b/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js index e6e00a01..e3449e62 100644 --- a/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js +++ b/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js @@ -130,7 +130,7 @@ describe('DependencyGraph', function() { }, getTransformCacheKey: () => 'abcdef', reporter: require('../../lib/reporting').nullReporter, - watch: false, + watch: true, }; }); @@ -3111,70 +3111,74 @@ describe('DependencyGraph', function() { filesystem.root['index.js'] = filesystem.root['index.js'] .replace('require("dontWork")', '') .replace('require("wontWork")', ''); - dgraph.processFileChange('change', root + '/index.js', mockStat); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [ - 'shouldWork', - 'ember', - 'internalVendoredPackage', - 'anotherIndex', - ], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-haste/main.js', - dependencies: ['submodule'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'submodule/main.js', - path: '/root/node_modules/react-haste/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'ember/main.js', - path: '/root/node_modules/ember/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'internalVendoredPackage', - path: '/root/vendored_modules/a-vendored-package/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'anotherIndex', - path: '/anotherRoot/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); + triggerWatchEvent('change', root + '/index.js'); + return new Promise(resolve => { + dgraph.once('change', () => { + return resolve(getOrderedDependenciesAsJSON(dgraph, '/root/index.js') + .then(deps => { + expect(deps).toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [ + 'shouldWork', + 'ember', + 'internalVendoredPackage', + 'anotherIndex', + ], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-haste/main.js', + dependencies: ['submodule'], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'submodule/main.js', + path: '/root/node_modules/react-haste/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'ember/main.js', + path: '/root/node_modules/ember/main.js', + dependencies: [], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'internalVendoredPackage', + path: '/root/vendored_modules/a-vendored-package/main.js', + dependencies: [], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'anotherIndex', + path: '/anotherRoot/index.js', + dependencies: [], + isAsset: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + })); + }); }); }); }); @@ -5455,4 +5459,8 @@ describe('DependencyGraph', function() { function setMockFileSystem(object) { return require('graceful-fs').__setMockFilesystem(object); } + + function triggerWatchEvent(eventType, filename) { + return require('graceful-fs').__triggerWatchEvent(eventType, filename); + } }); diff --git a/packages/metro-bundler/src/node-haste/index.js b/packages/metro-bundler/src/node-haste/index.js index 7aeaadd1..5600b090 100644 --- a/packages/metro-bundler/src/node-haste/index.js +++ b/packages/metro-bundler/src/node-haste/index.js @@ -36,6 +36,7 @@ const { createActionStartEntry, log, } = require('../Logger'); +const {EventEmitter} = require('events'); import type {Options as TransformOptions} from '../JSTransformer/worker/worker'; import type GlobalTransformCache from '../lib/GlobalTransformCache'; @@ -72,7 +73,7 @@ type Options = { watch: boolean, }; -class DependencyGraph { +class DependencyGraph extends EventEmitter { _opts: Options; _haste: JestHasteMap; _hasteFS: HasteFS; @@ -84,6 +85,7 @@ class DependencyGraph { _loading: Promise; constructor(opts: Options) { + super(); this._opts = {...opts}; this._helpers = new DependencyGraphHelpers(this._opts); this.load(); @@ -154,6 +156,7 @@ class DependencyGraph { eventsQueue.forEach(({type, filePath, stat}) => this.processFileChange(type, filePath, stat) ); + this.emit('change'); }); const buildingHasteMapLogEntry =