UNPKG

react-native

Version:

A framework for building native apps using React

413 lines (360 loc) • 13.1 kB
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; jest .dontMock('absolute-path') .dontMock('json-stable-stringify') .dontMock('imurmurhash') .dontMock('../lib/replacePatterns') .dontMock('../DependencyGraph/docblock') .dontMock('../Module'); jest .mock('fs'); const Module = require('../Module'); const ModuleCache = require('../ModuleCache'); const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); const TransformCache = require('../../lib/TransformCache'); const fs = require('graceful-fs'); const packageJson = JSON.stringify({ name: 'arbitrary', version: '1.0.0', description: "A require('foo') story", }); function mockFS(rootChildren) { fs.__setMockFilesystem({root: rootChildren}); } function mockPackageFile() { mockFS({'package.json': packageJson}); } function mockIndexFile(indexJs) { mockFS({'index.js': indexJs}); } describe('Module', () => { const fileName = '/root/index.js'; let cache; const createCache = () => ({ get: jest.genMockFn().mockImplementation( (filepath, field, cb) => cb(filepath) ), invalidate: jest.genMockFn(), end: jest.genMockFn(), }); let transformCacheKey; const createModule = options => new Module({ options: { cacheTransformResults: true, }, transformCode: (module, sourceCode, transformOptions) => { return Promise.resolve({code: sourceCode}); }, ...options, cache, file: options && options.file || fileName, depGraphHelpers: new DependencyGraphHelpers(), moduleCache: new ModuleCache({cache}), getTransformCacheKey: () => transformCacheKey, }); const createJSONModule = options => createModule({...options, file: '/root/package.json'}); beforeEach(function() { process.platform = 'linux'; cache = createCache(); transformCacheKey = 'abcdef'; TransformCache.mock.reset(); }); describe('Module ID', () => { const moduleId = 'arbitraryModule'; const source = `/** * @providesModule ${moduleId} */ `; let module; beforeEach(() => { module = createModule(); }); describe('@providesModule annotations', () => { beforeEach(() => { mockIndexFile(source); }); it('extracts the module name from the header', () => module.getName().then(name => expect(name).toEqual(moduleId)) ); it('identifies the module as haste module', () => module.isHaste().then(isHaste => expect(isHaste).toBe(true)) ); it('does not transform the file in order to access the name', () => { const transformCode = jest.genMockFn().mockReturnValue(Promise.resolve()); return createModule({transformCode}).getName() .then(() => expect(transformCode).not.toBeCalled()); }); it('does not transform the file in order to access the haste status', () => { const transformCode = jest.genMockFn().mockReturnValue(Promise.resolve()); return createModule({transformCode}).isHaste() .then(() => expect(transformCode).not.toBeCalled()); }); }); describe('no annotation', () => { beforeEach(() => { mockIndexFile('arbitrary(code);'); }); it('uses the file name as module name', () => module.getName().then(name => expect(name).toEqual(fileName)) ); it('does not identify the module as haste module', () => module.isHaste().then(isHaste => expect(isHaste).toBe(false)) ); it('does not transform the file in order to access the name', () => { const transformCode = jest.genMockFn().mockReturnValue(Promise.resolve()); return createModule({transformCode}).getName() .then(() => expect(transformCode).not.toBeCalled()); }); it('does not transform the file in order to access the haste status', () => { const transformCode = jest.genMockFn().mockReturnValue(Promise.resolve()); return createModule({transformCode}).isHaste() .then(() => expect(transformCode).not.toBeCalled()); }); }); }); describe('Code', () => { const fileContents = 'arbitrary(code)'; beforeEach(function() { mockIndexFile(fileContents); }); it('exposes file contents as `code` property on the data exposed by `read()`', () => createModule().read().then(({code}) => expect(code).toBe(fileContents)) ); it('exposes file contents via the `getCode()` method', () => createModule().getCode().then(code => expect(code).toBe(fileContents)) ); }); describe('Custom Code Transform', () => { let transformCode; let transformResult; const fileContents = 'arbitrary(code);'; const exampleCode = ` ${'require'}('a'); ${'System.import'}('b'); ${'require'}('c');`; beforeEach(function() { transformResult = {code: ''}; transformCode = jest.genMockFn() .mockImplementation((module, sourceCode, options) => { TransformCache.writeSync({ filePath: module.path, sourceCode, transformOptions: options, getTransformCacheKey: () => transformCacheKey, result: transformResult, }); return Promise.resolve(transformResult); }); mockIndexFile(fileContents); }); it('passes the module and file contents to the transform function when reading', () => { const module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toBeCalledWith(module, fileContents, undefined); }); }); it('passes any additional options to the transform function when reading', () => { const module = createModule({transformCode}); const transformOptions = {arbitrary: Object()}; return module.read(transformOptions) .then(() => expect(transformCode.mock.calls[0][2]).toBe(transformOptions) ); }); it('passes module and file contents if the file is annotated with @extern', () => { const module = createModule({transformCode}); const customFileContents = ` /** * @extern */ `; mockIndexFile(customFileContents); return module.read().then(() => { expect(transformCode).toBeCalledWith(module, customFileContents, {extern: true}); }); }); it('passes the module and file contents to the transform for JSON files', () => { mockPackageFile(); const module = createJSONModule({transformCode}); return module.read().then(() => { expect(transformCode).toBeCalledWith(module, packageJson, {extern: true}); }); }); it('does not extend the passed options object if the file is annotated with @extern', () => { const module = createModule({transformCode}); const customFileContents = ` /** * @extern */ `; mockIndexFile(customFileContents); const options = {arbitrary: 'foo'}; return module.read(options).then(() => { expect(options).not.toEqual(jasmine.objectContaining({extern: true})); expect(transformCode) .toBeCalledWith(module, customFileContents, {...options, extern: true}); }); }); it('does not extend the passed options object for JSON files', () => { mockPackageFile(); const module = createJSONModule({transformCode}); const options = {arbitrary: 'foo'}; return module.read(options).then(() => { expect(options).not.toEqual(jasmine.objectContaining({extern: true})); expect(transformCode) .toBeCalledWith(module, packageJson, {...options, extern: true}); }); }); it('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { const mockedDependencies = ['foo', 'bar']; transformResult = { code: exampleCode, dependencies: mockedDependencies, }; const module = createModule({transformCode}); return module.getDependencies().then(dependencies => { expect(dependencies).toEqual(mockedDependencies); }); }); it('forwards all additional properties of the result provided by `transformCode`', () => { transformResult = { code: exampleCode, arbitrary: 'arbitrary', dependencyOffsets: [12, 764], map: {version: 3}, subObject: {foo: 'bar'}, }; const module = createModule({transformCode}); return module.read().then(result => { expect(result).toEqual(jasmine.objectContaining(transformResult)); }); }); it('only stores dependencies if `cacheTransformResults` option is disabled', () => { transformResult = { code: exampleCode, arbitrary: 'arbitrary', dependencies: ['foo', 'bar'], dependencyOffsets: [12, 764], map: {version: 3}, subObject: {foo: 'bar'}, }; const module = createModule({transformCode, options: { cacheTransformResults: false, }}); return module.read().then(result => { expect(result).toEqual({ dependencies: ['foo', 'bar'], }); }); }); it('stores all things if options is undefined', () => { transformResult = { code: exampleCode, arbitrary: 'arbitrary', dependencies: ['foo', 'bar'], dependencyOffsets: [12, 764], map: {version: 3}, subObject: {foo: 'bar'}, }; const module = createModule({transformCode, options: undefined}); return module.read().then(result => { expect(result).toEqual({...transformResult, source: 'arbitrary(code);'}); }); }); it('exposes the transformed code rather than the raw file contents', () => { transformResult = {code: exampleCode}; const module = createModule({transformCode}); return Promise.all([module.read(), module.getCode()]) .then(([data, code]) => { expect(data.code).toBe(exampleCode); expect(code).toBe(exampleCode); }); }); it('exposes the raw file contents as `source` property', () => { const module = createModule({transformCode}); return module.read() .then(data => expect(data.source).toBe(fileContents)); }); it('exposes a source map returned by the transform', () => { const map = {version: 3}; transformResult = {map, code: exampleCode}; const module = createModule({transformCode}); return Promise.all([module.read(), module.getMap()]) .then(([data, sourceMap]) => { expect(data.map).toBe(map); expect(sourceMap).toBe(map); }); }); it('caches the transform result for the same transform options', () => { let module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); // We want to check transform caching rather than shallow caching of // Promises returned by read(). module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); }); }); }); it('triggers a new transform for different transform options', () => { const module = createModule({transformCode}); return module.read({foo: 1}) .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); return module.read({foo: 2}) .then(() => { expect(transformCode).toHaveBeenCalledTimes(2); }); }); }); it('triggers a new transform for different source code', () => { let module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); cache = createCache(); mockIndexFile('test'); module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(2); }); }); }); it('triggers a new transform for different transform cache key', () => { let module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(1); transformCacheKey = 'other'; module = createModule({transformCode}); return module.read() .then(() => { expect(transformCode).toHaveBeenCalledTimes(2); }); }); }); }); });