diff --git a/jest.config.js b/jest.config.js index 219dcb313..30a148770 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,9 +28,7 @@ if (process.platform === 'win32') { 'packages/metro-config/src/__tests__/loadConfig-test.js', 'packages/metro-symbolicate/src/__tests__/symbolicate-test.js', 'packages/metro-file-map/src/__tests__/index-test.js', - 'packages/metro-file-map/src/watchers/__tests__/WatchmanWatcher-test.js', 'packages/metro-file-map/src/crawlers/__tests__/node-test.js', - 'packages/metro-file-map/src/watchers/__tests__/integration-test.js', // resolveModulePath failed 'packages/metro-cache/src/stores/__tests__/FileStore-test.js', diff --git a/packages/metro-file-map/src/watchers/WatchmanWatcher.js b/packages/metro-file-map/src/watchers/WatchmanWatcher.js index 339c0b646..659617c63 100644 --- a/packages/metro-file-map/src/watchers/WatchmanWatcher.js +++ b/packages/metro-file-map/src/watchers/WatchmanWatcher.js @@ -20,6 +20,7 @@ import type { WatchmanWatchResponse, } from 'fb-watchman'; +import normalizePathSeparatorsToSystem from '../lib/normalizePathSeparatorsToSystem'; import {AbstractWatcher} from './AbstractWatcher'; import * as common from './common'; import RecrawlWarning from './RecrawlWarning'; @@ -105,9 +106,13 @@ export default class WatchmanWatcher extends AbstractWatcher { handleWarning(resp); + // NB: Watchman outputs posix-separated paths even on Windows, convert + // them to system-native separators. self.watchProjectInfo = { - relativePath: resp.relative_path ? resp.relative_path : '', - root: resp.watch, + relativePath: resp.relative_path + ? normalizePathSeparatorsToSystem(resp.relative_path) + : '', + root: normalizePathSeparatorsToSystem(resp.watch), }; self.client.command(['clock', getWatchRoot()], onClock); @@ -231,7 +236,7 @@ export default class WatchmanWatcher extends AbstractWatcher { ); const { - name: relativePath, + name: relativePosixPath, new: isNew = false, exists = false, type, @@ -239,6 +244,10 @@ export default class WatchmanWatcher extends AbstractWatcher { size, } = changeDescriptor; + // Watchman emits posix-separated paths on Windows, which is inconsistent + // with other watchers. Normalize to system-native separators. + const relativePath = normalizePathSeparatorsToSystem(relativePosixPath); + debug( 'Handling change to: %s (new: %s, exists: %s, type: %s)', relativePath, diff --git a/packages/metro-file-map/src/watchers/__tests__/WatchmanWatcher-test.js b/packages/metro-file-map/src/watchers/__tests__/WatchmanWatcher-test.js index d3cdb9d78..44fcaac18 100644 --- a/packages/metro-file-map/src/watchers/__tests__/WatchmanWatcher-test.js +++ b/packages/metro-file-map/src/watchers/__tests__/WatchmanWatcher-test.js @@ -28,13 +28,27 @@ const cmdCallback = (err: ?Error, result: Partial): void => { mockClient.command.mock.lastCall[1](err, result); }; +// Convenience function to write paths with posix separators but convert them +// to system separators, and prepend a mock drive letter to absolute paths on +// Windows. +const p: string => string = filePath => + process.platform === 'win32' + ? filePath.replaceAll('/', '\\').replace(/^\\/, 'C:\\') + : filePath; + +// Format a posix path as a Watchman-native path on the current platform, i.e., +// on Windows, drive letters on absolute paths, but posix-style separators. +// This should be used for mocking Watchman *output*. +const wp: string => string = filePath => + process.platform === 'win32' ? filePath.replace(/^\//, 'C:/') : filePath; + jest.mock('fb-watchman', () => ({ Client: jest.fn().mockImplementation(() => mockClient), })); describe('WatchmanWatcher', () => { test('initializes with watch-project, clock, subscribe', () => { - const watchmanWatcher = new WatchmanWatcher('/project/subdir/js', { + const watchmanWatcher = new WatchmanWatcher(p('/project/subdir/js'), { dot: true, ignored: null, globs: ['**/*.js'], @@ -46,16 +60,16 @@ describe('WatchmanWatcher', () => { .finally(() => (isSettled = true)); expect(mockClient.command).toHaveBeenCalledWith( - ['watch-project', '/project/subdir/js'], + ['watch-project', p('/project/subdir/js')], expect.any(Function), ); cmdCallback(null, { - watch: '/project', - relative_path: 'subdir/js', + watch: wp('/project'), + relative_path: wp('subdir/js'), }); expect(mockClient.command).toHaveBeenCalledWith( - ['clock', '/project'], + ['clock', p('/project')], expect.any(Function), ); cmdCallback(null, { @@ -65,12 +79,12 @@ describe('WatchmanWatcher', () => { expect(mockClient.command).toHaveBeenCalledWith( [ 'subscribe', - '/project', + p('/project'), watchmanWatcher.subscriptionName, { defer: ['busy'], fields: ['name', 'exists', 'new', 'type', 'size', 'mtime_ms'], - relative_root: 'subdir/js', + relative_root: p('subdir/js'), since: 'c:1629095304.251049', }, ], @@ -91,15 +105,20 @@ describe('WatchmanWatcher', () => { let startPromise: Promise; beforeEach(async () => { - watchmanWatcher = new WatchmanWatcher('/project/subdir/js', { + watchmanWatcher = new WatchmanWatcher(p('/project/subdir/js'), { dot: true, ignored: null, globs: ['**/*.js'], watchmanDeferStates: ['busy'], }); startPromise = watchmanWatcher.startWatching(); - cmdCallback(null, {}); - cmdCallback(null, {}); + cmdCallback(null, { + watch: wp('/project'), + relative_path: wp('subdir/js'), + }); + cmdCallback(null, { + clock: 'c:123', + }); }); afterEach(() => { diff --git a/packages/metro-file-map/src/watchers/__tests__/integration-test.js b/packages/metro-file-map/src/watchers/__tests__/integration-test.js index 1d5ba2c28..3e35c9abf 100644 --- a/packages/metro-file-map/src/watchers/__tests__/integration-test.js +++ b/packages/metro-file-map/src/watchers/__tests__/integration-test.js @@ -35,7 +35,9 @@ describe.each(Object.keys(WATCHERS))( // If all tests are skipped, Jest will not run before/after hooks either. const maybeTest = WATCHERS[watcherName] ? test : test.skip; const maybeTestOn = (...platforms: $ReadOnlyArray) => - platforms.includes(os.platform()) ? test : test.skip; + platforms.includes(os.platform()) && WATCHERS[watcherName] + ? test + : test.skip; beforeAll(async () => { watchRoot = await createTempWatchRoot(watcherName);