UNPKG

react-native

Version:

A framework for building native apps using React

512 lines (472 loc) • 15.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.disableAutomock(); const Bundle = require('../Bundle'); const ModuleTransport = require('../../lib/ModuleTransport'); const SourceMapGenerator = require('source-map').SourceMapGenerator; const crypto = require('crypto'); describe('Bundle', () => { var bundle; beforeEach(() => { bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.getSourceMap = jest.fn(() => { return 'test-source-map'; }); }); describe('source bundle', () => { pit('should create a bundle and get the source', () => { return Promise.resolve().then(() => { return addModule({ bundle, code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle, code: 'transformed bar;', sourceCode: 'source bar', sourcePath: 'bar path', }); }).then(() => { bundle.finalize({}); expect(bundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', '\/\/# sourceMappingURL=test_url' ].join('\n')); }); }); pit('should be ok to leave out the source map url', () => { const otherBundle = new Bundle(); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle: otherBundle, code: 'transformed bar;', sourceCode: 'source bar', sourcePath: 'bar path', }); }).then(() => { otherBundle.finalize({}); expect(otherBundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', ].join('\n')); }); }); pit('should create a bundle and add run module code', () => { return Promise.resolve().then(() => { return addModule({ bundle, code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle, code: 'transformed bar;', sourceCode: 'source bar', sourcePath: 'bar path', }); }).then(() => { bundle.setMainModuleId('foo'); bundle.finalize({ runBeforeMainModule: ['bar'], runMainModule: true, }); expect(bundle.getSource({dev: true})).toBe([ 'transformed foo;', 'transformed bar;', ';require("bar");', ';require("foo");', '\/\/# sourceMappingURL=test_url', ].join('\n')); }); }); pit('should insert modules in a deterministic order, independent from timing of the wrapping process', () => { const moduleTransports = [ createModuleTransport({name: 'module1'}), createModuleTransport({name: 'module2'}), createModuleTransport({name: 'module3'}), ]; const resolves = {}; const resolver = { wrapModule({name}) { return new Promise(resolve => resolves[name] = resolve); } }; const promise = Promise.all( moduleTransports.map(m => bundle.addModule(resolver, null, {isPolyfill: () => false}, m))) .then(() => { expect(bundle.getModules()) .toEqual(moduleTransports); }); resolves.module2({code: ''}); resolves.module3({code: ''}); resolves.module1({code: ''}); return promise; }); }); describe('sourcemap bundle', () => { pit('should create sourcemap', () => { const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, code: [ 'transformed foo', 'transformed foo', 'transformed foo', ].join('\n'), sourceCode: [ 'source foo', 'source foo', 'source foo', ].join('\n'), sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle: otherBundle, code: [ 'transformed bar', 'transformed bar', 'transformed bar', ].join('\n'), sourceCode: [ 'source bar', 'source bar', 'source bar', ].join('\n'), sourcePath: 'bar path', }); }).then(() => { otherBundle.setMainModuleId('foo'); otherBundle.finalize({ runBeforeMainModule: [], runMainModule: true, }); const sourceMap = otherBundle.getSourceMap({dev: true}); expect(sourceMap).toEqual(genSourceMap(otherBundle.getModules())); }); }); pit('should combine sourcemaps', () => { const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, code: 'transformed foo;\n', sourceCode: 'source foo', map: {name: 'sourcemap foo'}, sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle: otherBundle, code: 'transformed bar;\n', sourceCode: 'source bar', map: {name: 'sourcemap bar'}, sourcePath: 'bar path', }); }).then(() => { return addModule({ bundle: otherBundle, code: 'image module;\nimage module;', virtual: true, sourceCode: 'image module;\nimage module;', sourcePath: 'image.png', }); }).then(() => { otherBundle.setMainModuleId('foo'); otherBundle.finalize({ runBeforeMainModule: ['InitializeCore'], runMainModule: true, }); const sourceMap = otherBundle.getSourceMap({dev: true}); expect(sourceMap).toEqual({ file: 'test_url', version: 3, sections: [ { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } }, { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } }, { offset: { column: 0, line: 4 }, map: { file: 'image.png', mappings: 'AAAA;AACA;', names: [], sources: [ 'image.png' ], sourcesContent: ['image module;\nimage module;'], version: 3, } }, { offset: { column: 0, line: 6 }, map: { file: 'require-InitializeCore.js', mappings: 'AAAA;', names: [], sources: [ 'require-InitializeCore.js' ], sourcesContent: [';require("InitializeCore");'], version: 3, } }, { offset: { column: 0, line: 7 }, map: { file: 'require-foo.js', mappings: 'AAAA;', names: [], sources: [ 'require-foo.js' ], sourcesContent: [';require("foo");'], version: 3, } }, ], }); }); }); }); describe('getAssets()', () => { it('should save and return asset objects', () => { var p = new Bundle({sourceMapUrl: 'test_url'}); var asset1 = {}; var asset2 = {}; p.addAsset(asset1); p.addAsset(asset2); p.finalize(); expect(p.getAssets()).toEqual([asset1, asset2]); }); }); describe('getJSModulePaths()', () => { pit('should return module paths', () => { var otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, code: 'transformed foo;\n', sourceCode: 'source foo', sourcePath: 'foo path', }); }).then(() => { return addModule({ bundle: otherBundle, code: 'image module;\nimage module;', virtual: true, sourceCode: 'image module;\nimage module;', sourcePath: 'image.png', }); }).then(() => { expect(otherBundle.getJSModulePaths()).toEqual(['foo path']); }); }); }); describe('getEtag()', function() { it('should return an etag', function() { var bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.finalize({}); var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex'); expect(bundle.getEtag()).toEqual(eTag); }); }); describe('main module id:', function() { it('can save a main module ID', function() { const id = 'arbitrary module ID'; bundle.setMainModuleId(id); expect(bundle.getMainModuleId()).toEqual(id); }); it('can serialize and deserialize the module ID', function() { const id = 'arbitrary module ID'; bundle.setMainModuleId(id); bundle.finalize({}); const deserialized = Bundle.fromJSON(bundle.toJSON()); expect(deserialized.getMainModuleId()).toEqual(id); }); }); describe('random access bundle groups:', () => { let moduleTransports; beforeEach(() => { moduleTransports = [ transport('Product1', ['React', 'Relay']), transport('React', ['ReactFoo', 'ReactBar']), transport('ReactFoo', ['invariant']), transport('invariant', []), transport('ReactBar', ['cx']), transport('cx', []), transport('OtherFramework', ['OtherFrameworkFoo', 'OtherFrameworkBar']), transport('OtherFrameworkFoo', ['invariant']), transport('OtherFrameworkBar', ['crc32']), transport('crc32', ['OtherFrameworkBar']), ]; }); it('can create a single group', () => { bundle = createBundle([fsLocation('React')]); const {groups} = bundle.getUnbundle(); expect(groups).toEqual(new Map([ [idFor('React'), new Set(['ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor))], ])); }); it('can create two groups', () => { bundle = createBundle([fsLocation('ReactFoo'), fsLocation('ReactBar')]); const {groups} = bundle.getUnbundle(); expect(groups).toEqual(new Map([ [idFor('ReactFoo'), new Set([idFor('invariant')])], [idFor('ReactBar'), new Set([idFor('cx')])], ])); }); it('can handle circular dependencies', () => { bundle = createBundle([fsLocation('OtherFramework')]); const {groups} = bundle.getUnbundle(); expect(groups).toEqual(new Map([[ idFor('OtherFramework'), new Set(['OtherFrameworkFoo', 'invariant', 'OtherFrameworkBar', 'crc32'].map(idFor)), ]])); }); it('omits modules that are contained by more than one group', () => { bundle = createBundle([fsLocation('React'), fsLocation('OtherFramework')]); const {groups} = bundle.getUnbundle(); expect(groups).toEqual(new Map([ [idFor('React'), new Set(['ReactFoo', 'ReactBar', 'cx'].map(idFor))], [idFor('OtherFramework'), new Set(['OtherFrameworkFoo', 'OtherFrameworkBar', 'crc32'].map(idFor))], ])); }); it('ignores missing dependencies', () => { bundle = createBundle([fsLocation('Product1')]); const {groups} = bundle.getUnbundle(); expect(groups).toEqual(new Map([[ idFor('Product1'), new Set(['React', 'ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor)) ]])); }); it('throws for group roots that do not exist', () => { bundle = createBundle([fsLocation('DoesNotExist')]); expect(() => { const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars }).toThrow(new Error(`Group root ${fsLocation('DoesNotExist')} is not part of the bundle`)); }); function idFor(name) { const {map} = idFor; if (!map) { idFor.map = new Map([[name, 0]]); idFor.next = 1; return 0; } if (map.has(name)) { return map.get(name); } const id = idFor.next++; map.set(name, id); return id; } function createBundle(ramGroups, options = {}) { const b = new Bundle(Object.assign(options, {ramGroups})); moduleTransports.forEach(t => addModule({bundle: b, ...t})); b.finalize(); return b; } function fsLocation(name) { return `/fs/${name}.js`; } function module(name) { return {path: fsLocation(name)}; } function transport(name, deps) { return createModuleTransport({ name, id: idFor(name), sourcePath: fsLocation(name), meta: {dependencyPairs: deps.map(d => [d, module(d)])}, }); } }); }); function genSourceMap(modules) { var sourceMapGen = new SourceMapGenerator({file: 'test_url', version: 3}); var bundleLineNo = 0; for (var i = 0; i < modules.length; i++) { var module = modules[i]; var transformedCode = module.code; var sourcePath = module.sourcePath; var sourceCode = module.sourceCode; var transformedLineCount = 0; var lastCharNewLine = false; for (var t = 0; t < transformedCode.length; t++) { if (t === 0 || lastCharNewLine) { sourceMapGen.addMapping({ generated: {line: bundleLineNo + 1, column: 0}, original: {line: transformedLineCount + 1, column: 0}, source: sourcePath }); } lastCharNewLine = transformedCode[t] === '\n'; if (lastCharNewLine) { transformedLineCount++; bundleLineNo++; } } bundleLineNo++; sourceMapGen.setSourceContent( sourcePath, sourceCode ); } return sourceMapGen.toJSON(); } function resolverFor(code, map) { return { wrapModule: () => Promise.resolve({code, map}), }; } function addModule({bundle, code, sourceCode, sourcePath, map, virtual, polyfill, meta, id = ''}) { return bundle.addModule( resolverFor(code, map), null, {isPolyfill: () => polyfill}, createModuleTransport({ code, sourceCode, sourcePath, id, map, meta, virtual, polyfill, }), ); } function createModuleTransport(data) { return new ModuleTransport({ code: '', sourceCode: '', sourcePath: '', id: 'id' in data ? data.id : '', ...data, }); }