<commit_before>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

'use strict';

jest.mock('child_process', () => ({
  // If this does not throw, we'll use the (mocked) watchman crawler
  execSync() {},
}));

jest.mock('worker-farm', () => {
  const mock = jest.fn(
    (options, worker) =>
      (workerFarmMock = jest.fn((data, callback) =>
        require(worker)(data, callback),
      )),
  );
  mock.end = jest.fn();
  return mock;
});

jest.mock('../crawlers/node');
jest.mock('../crawlers/watchman', () =>
  jest.fn(options => {
    const {data, ignore, roots} = options;
    data.clocks = mockClocks;

    const list = mockChangedFiles || mockFs;
    for (const file in list) {
      if (new RegExp(roots.join('|')).test(file) && !ignore(file)) {
        if (list[file]) {
          data.files[file] = ['', 32, 0, []];
        } else {
          delete data.files[file];
        }
      }
    }

    return Promise.resolve(data);
  }),
);

jest.mock('sane', () => {
  const watcher = jest.fn(root => {
    const EventEmitter = require('events').EventEmitter;
    mockEmitters[root] = new EventEmitter();
    mockEmitters[root].close = jest.fn(callback => callback());
    setTimeout(() => mockEmitters[root].emit('ready'), 0);
    return mockEmitters[root];
  });
  return {
    NodeWatcher: watcher,
    WatchmanWatcher: watcher,
  };
});

let mockChangedFiles;
let mockFs;

jest.mock('graceful-fs', () => ({
  readFileSync: jest.fn((path, options) => {
    expect(options).toBe('utf8');

    // A file change can be triggered by writing into the
    // mockChangedFiles object.
    if (mockChangedFiles && path in mockChangedFiles) {
      return mockChangedFiles[path];
    }

    if (mockFs[path]) {
      return mockFs[path];
    }

    const error = new Error(`Cannot read path '${path}'.`);
    error.code = 'ENOENT';
    throw error;
  }),
  writeFileSync: jest.fn((path, data, options) => {
    expect(options).toBe('utf8');
    mockFs[path] = data;
  }),
}));
jest.mock('fs', () => require('graceful-fs'));

const skipOnWindows = require('../../../../scripts/skip_on_windows');

const cacheFilePath = '/cache-file';
let consoleWarn;
let defaultConfig;
let fs;
let H;
let HasteMap;
let mockClocks;
let mockEmitters;
let object;
let workerFarmMock;
let getCacheFilePath;

describe('HasteMap', () => {
  skipOnWindows.suite();

  beforeEach(() => {
    jest.resetModules();

    object = data => Object.assign(Object.create(null), data);

    mockEmitters = Object.create(null);
    mockFs = object({
      '/fruits/__mocks__/Pear.js': ['const Melon = require("Melon");'].join(
        '\n',
      ),
      '/fruits/banana.js': [
        '/**',
        ' * @providesModule Banana',
        ' */',
        'const Strawberry = require("Strawberry");',
      ].join('\n'),
      '/fruits/kiwi.js': ['/**', ' * @providesModule Kiwi', ' */'].join('\n'),
      '/fruits/pear.js': [
        '/**',
        ' * @providesModule Pear',
        ' */',
        'const Banana = require("Banana");',
        'const Strawberry = require("Strawberry");',
      ].join('\n'),
      '/fruits/strawberry.js': [
        '/**',
        ' * @providesModule Strawberry',
        ' */',
      ].join('\n'),
      '/vegetables/melon.js': ['/**', ' * @providesModule Melon', ' */'].join(
        '\n',
      ),
    });
    mockClocks = object({
      '/fruits': 'c:fake-clock:1',
      '/vegetables': 'c:fake-clock:2',
    });

    mockChangedFiles = null;

    fs = require('graceful-fs');

    consoleWarn = console.warn;
    console.warn = jest.fn();

    HasteMap = require('../');
    H = HasteMap.H;

    getCacheFilePath = HasteMap.getCacheFilePath;
    HasteMap.getCacheFilePath = jest.fn(() => cacheFilePath);

    defaultConfig = {
      extensions: ['js', 'json'],
      ignorePattern: /kiwi/,
      maxWorkers: 1,
      name: 'haste-map-test',
      platforms: ['ios', 'android'],
      resetCache: false,
      roots: ['/fruits', '/vegetables'],
      useWatchman: true,
    };
  });

  afterEach(() => {
    console.warn = consoleWarn;
  });

  it('exports constants', () => {
    expect(HasteMap.H).toBe(require('../constants').default);
  });

  it('creates valid cache file paths', () => {
    jest.resetModuleRegistry();
    HasteMap = require('../');

    expect(
      HasteMap.getCacheFilePath('/', '@scoped/package', 'random-value'),
    ).toMatch(/^\/-scoped-package-(.*)$/);

    expect(
      HasteMap.getCacheFilePath('/', '@scoped/package', 'random-value'),
    ).not.toEqual(
      HasteMap.getCacheFilePath('/', '-scoped-package', 'random-value'),
    );
  });

  it('matches files against a pattern', () => {
    return new HasteMap(defaultConfig).build().then(({hasteFS}) => {
      expect(hasteFS.matchFiles(/fruits/)).toEqual([
        '/fruits/__mocks__/Pear.js',
        '/fruits/banana.js',
        '/fruits/pear.js',
        '/fruits/strawberry.js',
      ]);

      expect(hasteFS.matchFiles(/__mocks__/)).toEqual([
        '/fruits/__mocks__/Pear.js',
      ]);
    });
  });

  it('builds a haste map on a fresh cache', () => {
    // Include these files in the map
    mockFs['/fruits/node_modules/react/react.js'] = [
      '/**',
      ' * @providesModule React',
      ' */',
      'const Component = require("Component");',
    ].join('\n');
    mockFs['/fruits/node_modules/fbjs/lib/flatMap.js'] = [
      '/**',
      ' * @providesModule flatMap',
      ' */',
    ].join('\n');

    // Ignore these
    mockFs['/fruits/node_modules/react/node_modules/fbjs/lib/mapObject.js'] = [
      '/**',
      ' * @providesModule mapObject',
      ' */',
    ].join('\n');
    mockFs['/fruits/node_modules/react/node_modules/dummy/merge.js'] = [
      '/**',
      ' * @providesModule merge',
      ' */',
    ].join('\n');
    mockFs['/fruits/node_modules/react/node_modules/merge/package.json'] = [
      '{',
      '  "name": "merge"',
      '}',
    ].join('\n');
    mockFs['/fruits/node_modules/jest/jest.js'] = [
      '/**',
      ' * @providesModule Jest',
      ' */',
      'const Test = require("Test");',
    ].join('\n');
    mockFs['/fruits/node_modules/fbjs2/index.js'] = [
      '/**',
      ' * @providesModule fbjs2',
      ' */',
    ].join('\n');

    const hasteMap = new HasteMap(
      Object.assign({}, defaultConfig, {
        mocksPattern: '/__mocks__/',
        providesModuleNodeModules: ['react', 'fbjs'],
      }),
    );

    return hasteMap.build().then(({__hasteMapForTest: data}) => {
      expect(data.clocks).toEqual(mockClocks);

      expect(data.files).toEqual({
        '/fruits/__mocks__/Pear.js': ['', 32, 1, ['Melon']],
        '/fruits/banana.js': ['Banana', 32, 1, ['Strawberry']],
        // node modules
        '/fruits/node_modules/fbjs/lib/flatMap.js': ['flatMap', 32, 1, []],
        '/fruits/node_modules/react/react.js': ['React', 32, 1, ['Component']],

        '/fruits/pear.js': ['Pear', 32, 1, ['Banana', 'Strawberry']],
        '/fruits/strawberry.js': ['Strawberry', 32, 1, []],
        '/vegetables/melon.js': ['Melon', 32, 1, []],
      });

      expect(data.map).toEqual({
        Banana: {[H.GENERIC_PLATFORM]: ['/fruits/banana.js', H.MODULE]},
        Melon: {[H.GENERIC_PLATFORM]: ['/vegetables/melon.js', H.MODULE]},
        Pear: {[H.GENERIC_PLATFORM]: ['/fruits/pear.js', H.MODULE]},
        React: {
          [H.GENERIC_PLATFORM]: [
            '/fruits/node_modules/react/react.js',
            H.MODULE,
          ],
        },
        Strawberry: {
          [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE],
        },
        flatMap: {
          [H.GENERIC_PLATFORM]: [
            '/fruits/node_modules/fbjs/lib/flatMap.js',
            H.MODULE,
          ],
        },
      });

      expect(data.mocks).toEqual({
        Pear: '/fruits/__mocks__/Pear.js',
      });

      // The cache file must exactly mirror the data structure returned
      // from a build.
      expect(hasteMap.read()).toEqual(data);
    });
  });

  it('retains all files if `retainAllFiles` is specified', () => {
    mockFs['/fruits/node_modules/fbjs/index.js'] = [
      '/**',
      ' * @providesModule fbjs',
      ' */',
    ].join('\n');

    const hasteMap = new HasteMap(
      Object.assign({}, defaultConfig, {
        mocksPattern: '/__mocks__/',
        retainAllFiles: true,
      }),
    );

    return hasteMap.build().then(({__hasteMapForTest: data}) => {
      // Expect the node module to be part of files but make sure it wasn't
      // read.
      expect(data.files['/fruits/node_modules/fbjs/index.js']).toEqual([
        '',
        32,
        0,
        [],
      ]);

      expect(data.map.fbjs).not.toBeDefined();

      // cache file + 5 modules - the node_module
      expect(fs.readFileSync.mock.calls.length).toBe(6);
    });
  });

  it('warns on duplicate mock files', () => {
    // Duplicate mock files for blueberry
    mockFs['/fruits1/__mocks__/subdir/blueberry.js'] = [
      '/**',
      ' * @providesModule Blueberry1',
      ' */',
    ].join('\n');
    mockFs['/fruits2/__mocks__/subdir/blueberry.js'] = [
      '/**',
      ' * @providesModule Blueberry2',
      ' */',
    ].join('\n');

    return new HasteMap(
      Object.assign({mocksPattern: '__mocks__'}, defaultConfig),
    )
      .build()
      .then(({__hasteMapForTest: data}) => {
        expect(console.warn.mock.calls[0][0]).toMatchSnapshot();
      });
  });

  it('warns on duplicate module ids', () => {
    // Raspberry thinks it is a Strawberry
    mockFs['/fruits/raspberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: data}) => {
        // Duplicate modules are removed so that it doesn't cause
        // non-determinism later on.
        expect(data.map.Strawberry[H.GENERIC_PLATFORM]).not.toBeDefined();

        expect(console.warn.mock.calls[0][0]).toMatchSnapshot();
      });
  });

  it('throws on duplicate module ids if "throwOnModuleCollision" is set to true', () => {
    // Raspberry thinks it is a Strawberry
    mockFs['/fruits/raspberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    return new HasteMap(
      Object.assign({throwOnModuleCollision: true}, defaultConfig),
    )
      .build()
      .catch(err => {
        expect(err).toMatchSnapshot();
      });
  });

  it('splits up modules by platform', () => {
    mockFs = Object.create(null);
    mockFs['/fruits/strawberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    mockFs['/fruits/strawberry.ios.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Raspberry = require("Raspberry");',
    ].join('\n');

    mockFs['/fruits/strawberry.android.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Blackberry = require("Blackberry");',
    ].join('\n');

    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: data}) => {
        expect(data.files).toEqual({
          '/fruits/strawberry.android.js': [
            'Strawberry',
            32,
            1,
            ['Blackberry'],
          ],
          '/fruits/strawberry.ios.js': ['Strawberry', 32, 1, ['Raspberry']],
          '/fruits/strawberry.js': ['Strawberry', 32, 1, ['Banana']],
        });

        expect(data.map).toEqual({
          Strawberry: {
            [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE],
            android: ['/fruits/strawberry.android.js', H.MODULE],
            ios: ['/fruits/strawberry.ios.js', H.MODULE],
          },
        });
      });
  });

  it('does not access the file system on a warm cache with no changes', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        // The first run should access the file system once for the (empty)
        // cache file and five times for the files in the system.
        expect(fs.readFileSync.mock.calls.length).toBe(6);

        fs.readFileSync.mockClear();

        // Explicitly mock that no files have changed.
        mockChangedFiles = Object.create(null);

        // Watchman would give us different clocks.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:4',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            expect(fs.readFileSync.mock.calls.length).toBe(1);
            expect(fs.readFileSync).toBeCalledWith(cacheFilePath, 'utf8');

            expect(data.clocks).toEqual(mockClocks);
            expect(data.files).toEqual(initialData.files);
            expect(data.map).toEqual(initialData.map);
          });
      });
  });

  it('only does minimal file system access when files change', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        fs.readFileSync.mockClear();

        // Let's assume one JS file has changed.
        mockChangedFiles = object({
          '/fruits/banana.js': [
            '/**',
            ' * @providesModule Kiwi', // Identity crisis.
            ' */',
            'const Raspberry = require("Raspberry");',
          ].join('\n'),
        });

        // Watchman would give us different clocks for `/fruits`.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:2',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            expect(fs.readFileSync.mock.calls.length).toBe(2);

            expect(fs.readFileSync).toBeCalledWith(cacheFilePath, 'utf8');
            expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8');

            expect(data.clocks).toEqual(mockClocks);

            const files = object(initialData.files);
            files['/fruits/banana.js'] = ['Kiwi', 32, 1, ['Raspberry']];

            expect(data.files).toEqual(files);

            const map = object(initialData.map);

            map.Kiwi = map.Banana;
            delete map.Banana;
            expect(data.map).toEqual(map);
          });
      });
  });

  it('correctly handles file deletions', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        fs.readFileSync.mockClear();

        // Let's assume one JS file was removed.
        delete mockFs['/fruits/banana.js'];
        mockChangedFiles = object({
          '/fruits/banana.js': null,
        });

        // Watchman would give us different clocks for `/fruits`.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:2',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            const files = object(initialData.files);
            delete files['/fruits/banana.js'];
            expect(data.files).toEqual(files);

            const map = object(initialData.map);
            delete map.Banana;
            expect(data.map).toEqual(map);
          });
      });
  });

  it('discards the cache when configuration changes', () => {
    HasteMap.getCacheFilePath = getCacheFilePath;
    return new HasteMap(defaultConfig).build().then(() => {
      fs.readFileSync.mockClear();

      // Explicitly mock that no files have changed.
      mockChangedFiles = Object.create(null);

      // Watchman would give us different clocks.
      mockClocks = object({
        '/fruits': 'c:fake-clock:3',
        '/vegetables': 'c:fake-clock:4',
      });

      const config = Object.assign({}, defaultConfig, {
        ignorePattern: /kiwi|pear/,
      });
      return new HasteMap(config).build().then(({moduleMap}) => {
        expect(moduleMap.getModule('Pear')).toBe(null);
      });
    });
  });

  it('ignores files that do not exist', () => {
    const watchman = require('../crawlers/watchman');
    const mockImpl = watchman.getMockImplementation();
    // Wrap the watchman mock and add an invalid file to the file list.
    watchman.mockImplementation(options => {
      return mockImpl(options).then(() => {<commit_msg>jest-haste-map: add test to demo broken duplicates behavior (#4800)<commit_after>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

'use strict';

jest.mock('child_process', () => ({
  // If this does not throw, we'll use the (mocked) watchman crawler
  execSync() {},
}));

jest.mock('worker-farm', () => {
  const mock = jest.fn(
    (options, worker) =>
      (workerFarmMock = jest.fn((data, callback) =>
        require(worker)(data, callback),
      )),
  );
  mock.end = jest.fn();
  return mock;
});

jest.mock('../crawlers/node');
jest.mock('../crawlers/watchman', () =>
  jest.fn(options => {
    const {data, ignore, roots} = options;
    data.clocks = mockClocks;

    const list = mockChangedFiles || mockFs;
    for (const file in list) {
      if (new RegExp(roots.join('|')).test(file) && !ignore(file)) {
        if (list[file]) {
          data.files[file] = ['', 32, 0, []];
        } else {
          delete data.files[file];
        }
      }
    }

    return Promise.resolve(data);
  }),
);

jest.mock('sane', () => {
  const watcher = jest.fn(root => {
    const EventEmitter = require('events').EventEmitter;
    mockEmitters[root] = new EventEmitter();
    mockEmitters[root].close = jest.fn(callback => callback());
    setTimeout(() => mockEmitters[root].emit('ready'), 0);
    return mockEmitters[root];
  });
  return {
    NodeWatcher: watcher,
    WatchmanWatcher: watcher,
  };
});

let mockChangedFiles;
let mockFs;

jest.mock('graceful-fs', () => ({
  readFileSync: jest.fn((path, options) => {
    expect(options).toBe('utf8');

    // A file change can be triggered by writing into the
    // mockChangedFiles object.
    if (mockChangedFiles && path in mockChangedFiles) {
      return mockChangedFiles[path];
    }

    if (mockFs[path]) {
      return mockFs[path];
    }

    const error = new Error(`Cannot read path '${path}'.`);
    error.code = 'ENOENT';
    throw error;
  }),
  writeFileSync: jest.fn((path, data, options) => {
    expect(options).toBe('utf8');
    mockFs[path] = data;
  }),
}));
jest.mock('fs', () => require('graceful-fs'));

const skipOnWindows = require('../../../../scripts/skip_on_windows');

const cacheFilePath = '/cache-file';
let consoleWarn;
let defaultConfig;
let fs;
let H;
let HasteMap;
let mockClocks;
let mockEmitters;
let object;
let workerFarmMock;
let getCacheFilePath;

describe('HasteMap', () => {
  skipOnWindows.suite();

  beforeEach(() => {
    jest.resetModules();

    object = data => Object.assign(Object.create(null), data);

    mockEmitters = Object.create(null);
    mockFs = object({
      '/fruits/__mocks__/Pear.js': ['const Melon = require("Melon");'].join(
        '\n',
      ),
      '/fruits/banana.js': [
        '/**',
        ' * @providesModule Banana',
        ' */',
        'const Strawberry = require("Strawberry");',
      ].join('\n'),
      '/fruits/kiwi.js': ['/**', ' * @providesModule Kiwi', ' */'].join('\n'),
      '/fruits/pear.js': [
        '/**',
        ' * @providesModule Pear',
        ' */',
        'const Banana = require("Banana");',
        'const Strawberry = require("Strawberry");',
      ].join('\n'),
      '/fruits/strawberry.js': [
        '/**',
        ' * @providesModule Strawberry',
        ' */',
      ].join('\n'),
      '/vegetables/melon.js': ['/**', ' * @providesModule Melon', ' */'].join(
        '\n',
      ),
    });
    mockClocks = object({
      '/fruits': 'c:fake-clock:1',
      '/vegetables': 'c:fake-clock:2',
    });

    mockChangedFiles = null;

    fs = require('graceful-fs');

    consoleWarn = console.warn;
    console.warn = jest.fn();

    HasteMap = require('../');
    H = HasteMap.H;

    getCacheFilePath = HasteMap.getCacheFilePath;
    HasteMap.getCacheFilePath = jest.fn(() => cacheFilePath);

    defaultConfig = {
      extensions: ['js', 'json'],
      ignorePattern: /kiwi/,
      maxWorkers: 1,
      name: 'haste-map-test',
      platforms: ['ios', 'android'],
      resetCache: false,
      roots: ['/fruits', '/vegetables'],
      useWatchman: true,
    };
  });

  afterEach(() => {
    console.warn = consoleWarn;
  });

  it('exports constants', () => {
    expect(HasteMap.H).toBe(require('../constants').default);
  });

  it('creates valid cache file paths', () => {
    jest.resetModuleRegistry();
    HasteMap = require('../');

    expect(
      HasteMap.getCacheFilePath('/', '@scoped/package', 'random-value'),
    ).toMatch(/^\/-scoped-package-(.*)$/);

    expect(
      HasteMap.getCacheFilePath('/', '@scoped/package', 'random-value'),
    ).not.toEqual(
      HasteMap.getCacheFilePath('/', '-scoped-package', 'random-value'),
    );
  });

  it('matches files against a pattern', () => {
    return new HasteMap(defaultConfig).build().then(({hasteFS}) => {
      expect(hasteFS.matchFiles(/fruits/)).toEqual([
        '/fruits/__mocks__/Pear.js',
        '/fruits/banana.js',
        '/fruits/pear.js',
        '/fruits/strawberry.js',
      ]);

      expect(hasteFS.matchFiles(/__mocks__/)).toEqual([
        '/fruits/__mocks__/Pear.js',
      ]);
    });
  });

  it('builds a haste map on a fresh cache', () => {
    // Include these files in the map
    mockFs['/fruits/node_modules/react/react.js'] = [
      '/**',
      ' * @providesModule React',
      ' */',
      'const Component = require("Component");',
    ].join('\n');
    mockFs['/fruits/node_modules/fbjs/lib/flatMap.js'] = [
      '/**',
      ' * @providesModule flatMap',
      ' */',
    ].join('\n');

    // Ignore these
    mockFs['/fruits/node_modules/react/node_modules/fbjs/lib/mapObject.js'] = [
      '/**',
      ' * @providesModule mapObject',
      ' */',
    ].join('\n');
    mockFs['/fruits/node_modules/react/node_modules/dummy/merge.js'] = [
      '/**',
      ' * @providesModule merge',
      ' */',
    ].join('\n');
    mockFs['/fruits/node_modules/react/node_modules/merge/package.json'] = [
      '{',
      '  "name": "merge"',
      '}',
    ].join('\n');
    mockFs['/fruits/node_modules/jest/jest.js'] = [
      '/**',
      ' * @providesModule Jest',
      ' */',
      'const Test = require("Test");',
    ].join('\n');
    mockFs['/fruits/node_modules/fbjs2/index.js'] = [
      '/**',
      ' * @providesModule fbjs2',
      ' */',
    ].join('\n');

    const hasteMap = new HasteMap(
      Object.assign({}, defaultConfig, {
        mocksPattern: '/__mocks__/',
        providesModuleNodeModules: ['react', 'fbjs'],
      }),
    );

    return hasteMap.build().then(({__hasteMapForTest: data}) => {
      expect(data.clocks).toEqual(mockClocks);

      expect(data.files).toEqual({
        '/fruits/__mocks__/Pear.js': ['', 32, 1, ['Melon']],
        '/fruits/banana.js': ['Banana', 32, 1, ['Strawberry']],
        // node modules
        '/fruits/node_modules/fbjs/lib/flatMap.js': ['flatMap', 32, 1, []],
        '/fruits/node_modules/react/react.js': ['React', 32, 1, ['Component']],

        '/fruits/pear.js': ['Pear', 32, 1, ['Banana', 'Strawberry']],
        '/fruits/strawberry.js': ['Strawberry', 32, 1, []],
        '/vegetables/melon.js': ['Melon', 32, 1, []],
      });

      expect(data.map).toEqual({
        Banana: {[H.GENERIC_PLATFORM]: ['/fruits/banana.js', H.MODULE]},
        Melon: {[H.GENERIC_PLATFORM]: ['/vegetables/melon.js', H.MODULE]},
        Pear: {[H.GENERIC_PLATFORM]: ['/fruits/pear.js', H.MODULE]},
        React: {
          [H.GENERIC_PLATFORM]: [
            '/fruits/node_modules/react/react.js',
            H.MODULE,
          ],
        },
        Strawberry: {
          [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE],
        },
        flatMap: {
          [H.GENERIC_PLATFORM]: [
            '/fruits/node_modules/fbjs/lib/flatMap.js',
            H.MODULE,
          ],
        },
      });

      expect(data.mocks).toEqual({
        Pear: '/fruits/__mocks__/Pear.js',
      });

      // The cache file must exactly mirror the data structure returned
      // from a build.
      expect(hasteMap.read()).toEqual(data);
    });
  });

  it('retains all files if `retainAllFiles` is specified', () => {
    mockFs['/fruits/node_modules/fbjs/index.js'] = [
      '/**',
      ' * @providesModule fbjs',
      ' */',
    ].join('\n');

    const hasteMap = new HasteMap(
      Object.assign({}, defaultConfig, {
        mocksPattern: '/__mocks__/',
        retainAllFiles: true,
      }),
    );

    return hasteMap.build().then(({__hasteMapForTest: data}) => {
      // Expect the node module to be part of files but make sure it wasn't
      // read.
      expect(data.files['/fruits/node_modules/fbjs/index.js']).toEqual([
        '',
        32,
        0,
        [],
      ]);

      expect(data.map.fbjs).not.toBeDefined();

      // cache file + 5 modules - the node_module
      expect(fs.readFileSync.mock.calls.length).toBe(6);
    });
  });

  it('warns on duplicate mock files', () => {
    // Duplicate mock files for blueberry
    mockFs['/fruits1/__mocks__/subdir/blueberry.js'] = [
      '/**',
      ' * @providesModule Blueberry1',
      ' */',
    ].join('\n');
    mockFs['/fruits2/__mocks__/subdir/blueberry.js'] = [
      '/**',
      ' * @providesModule Blueberry2',
      ' */',
    ].join('\n');

    return new HasteMap(
      Object.assign({mocksPattern: '__mocks__'}, defaultConfig),
    )
      .build()
      .then(({__hasteMapForTest: data}) => {
        expect(console.warn.mock.calls[0][0]).toMatchSnapshot();
      });
  });

  it('warns on duplicate module ids', () => {
    // Raspberry thinks it is a Strawberry
    mockFs['/fruits/raspberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: data}) => {
        // Duplicate modules are removed so that it doesn't cause
        // non-determinism later on.
        expect(data.map.Strawberry[H.GENERIC_PLATFORM]).not.toBeDefined();

        expect(console.warn.mock.calls[0][0]).toMatchSnapshot();
      });
  });

  it('throws on duplicate module ids if "throwOnModuleCollision" is set to true', () => {
    // Raspberry thinks it is a Strawberry
    mockFs['/fruits/raspberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    return new HasteMap(
      Object.assign({throwOnModuleCollision: true}, defaultConfig),
    )
      .build()
      .catch(err => {
        expect(err).toMatchSnapshot();
      });
  });

  it('splits up modules by platform', () => {
    mockFs = Object.create(null);
    mockFs['/fruits/strawberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Banana = require("Banana");',
    ].join('\n');

    mockFs['/fruits/strawberry.ios.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Raspberry = require("Raspberry");',
    ].join('\n');

    mockFs['/fruits/strawberry.android.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Blackberry = require("Blackberry");',
    ].join('\n');

    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: data}) => {
        expect(data.files).toEqual({
          '/fruits/strawberry.android.js': [
            'Strawberry',
            32,
            1,
            ['Blackberry'],
          ],
          '/fruits/strawberry.ios.js': ['Strawberry', 32, 1, ['Raspberry']],
          '/fruits/strawberry.js': ['Strawberry', 32, 1, ['Banana']],
        });

        expect(data.map).toEqual({
          Strawberry: {
            [H.GENERIC_PLATFORM]: ['/fruits/strawberry.js', H.MODULE],
            android: ['/fruits/strawberry.android.js', H.MODULE],
            ios: ['/fruits/strawberry.ios.js', H.MODULE],
          },
        });
      });
  });

  it('does not access the file system on a warm cache with no changes', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        // The first run should access the file system once for the (empty)
        // cache file and five times for the files in the system.
        expect(fs.readFileSync.mock.calls.length).toBe(6);

        fs.readFileSync.mockClear();

        // Explicitly mock that no files have changed.
        mockChangedFiles = Object.create(null);

        // Watchman would give us different clocks.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:4',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            expect(fs.readFileSync.mock.calls.length).toBe(1);
            expect(fs.readFileSync).toBeCalledWith(cacheFilePath, 'utf8');

            expect(data.clocks).toEqual(mockClocks);
            expect(data.files).toEqual(initialData.files);
            expect(data.map).toEqual(initialData.map);
          });
      });
  });

  it('only does minimal file system access when files change', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        fs.readFileSync.mockClear();

        // Let's assume one JS file has changed.
        mockChangedFiles = object({
          '/fruits/banana.js': [
            '/**',
            ' * @providesModule Kiwi', // Identity crisis.
            ' */',
            'const Raspberry = require("Raspberry");',
          ].join('\n'),
        });

        // Watchman would give us different clocks for `/fruits`.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:2',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            expect(fs.readFileSync.mock.calls.length).toBe(2);

            expect(fs.readFileSync).toBeCalledWith(cacheFilePath, 'utf8');
            expect(fs.readFileSync).toBeCalledWith('/fruits/banana.js', 'utf8');

            expect(data.clocks).toEqual(mockClocks);

            const files = object(initialData.files);
            files['/fruits/banana.js'] = ['Kiwi', 32, 1, ['Raspberry']];

            expect(data.files).toEqual(files);

            const map = object(initialData.map);

            map.Kiwi = map.Banana;
            delete map.Banana;
            expect(data.map).toEqual(map);
          });
      });
  });

  it('correctly handles file deletions', () => {
    return new HasteMap(defaultConfig)
      .build()
      .then(({__hasteMapForTest: initialData}) => {
        fs.readFileSync.mockClear();

        // Let's assume one JS file was removed.
        delete mockFs['/fruits/banana.js'];
        mockChangedFiles = object({
          '/fruits/banana.js': null,
        });

        // Watchman would give us different clocks for `/fruits`.
        mockClocks = object({
          '/fruits': 'c:fake-clock:3',
          '/vegetables': 'c:fake-clock:2',
        });

        return new HasteMap(defaultConfig)
          .build()
          .then(({__hasteMapForTest: data}) => {
            const files = object(initialData.files);
            delete files['/fruits/banana.js'];
            expect(data.files).toEqual(files);

            const map = object(initialData.map);
            delete map.Banana;
            expect(data.map).toEqual(map);
          });
      });
  });

  it('recovers from duplicate modules (broken)', async () => {
    mockFs['/fruits/another_strawberry.js'] = [
      '/**',
      ' * @providesModule Strawberry',
      ' */',
      'const Blackberry = require("Blackberry");',
    ].join('\n');

    let {__hasteMapForTest: data} = await new HasteMap(defaultConfig).build();
    expect(data.duplicates).toEqual({
      Strawberry: {
        g: {'/fruits/another_strawberry.js': 0, '/fruits/strawberry.js': 0},
      },
    });
    expect(data.map['Strawberry']).toEqual({});

    delete mockFs['/fruits/another_strawberry.js'];
    mockChangedFiles = object({
      '/fruits/another_strawberry.js': null,
    });
    mockClocks = object({
      '/fruits': 'c:fake-clock:3',
      '/vegetables': 'c:fake-clock:2',
    });

    ({__hasteMapForTest: data} = await new HasteMap(defaultConfig).build());
    // This is broken, there should not be duplicates anymore.
    expect(data.duplicates).toEqual({
      Strawberry: {
        g: {'/fruits/another_strawberry.js': 0, '/fruits/strawberry.js': 0},
      },
    });
    // This is broken, Strawberry should now resolve to "/fruits/strawberry.js"
    expect(data.map['Strawberry']).toEqual({});
  });

  it('discards the cache when configuration changes', () => {
    HasteMap.getCacheFilePath = getCacheFilePath;
    return new HasteMap(defaultConfig).build().then(() => {
      fs.readFileSync.mockClear();

      // Explicitly mock that no files have changed.
      mockChangedFiles = Object.create(null);

      // Watchman would give us different clocks.
      mockClocks = object({
        '/fruits': 'c:fake-clock:3',
        '/vegetables': 'c:fake-clock:4',
      });

      const config = Object.assign({}, defaultConfig, {
        ignorePattern: /kiwi|pear/,
      });
      return new HasteMap(config).build().then(({moduleMap}) => {
        expect(moduleMap.getModule('Pear')).toBe(null);
      });
    });
  });

  it('ignores files that do not exist', () => {
    const watchman = require('../crawlers/watchman');
    const mockImpl = watchman.getMockImplementation();
    // Wrap the watchman mock and add an invalid file to the file list.
    watchman.mockImplementation(options => {
      return mockImpl(options).then(() => {
{'fraction_non_alphanumeric': 0.1848522631716299, 'fraction_numerical': 0.004639429350189927, 'mean_word_length': 2.3059815950920246, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 104, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3102367', 'n_tokens_mistral': 11562, 'n_tokens_neox': 11285, 'n_words': 1998}