1import assert from 'assert';
2import { vol } from 'memfs';
3import path from 'path';
4
5import {
6  writeAssetMapAsync,
7  writeBundlesAsync,
8  writeDebugHtmlAsync,
9  writeSourceMapsAsync,
10} from '../writeContents';
11
12describe(writeDebugHtmlAsync, () => {
13  afterEach(() => {
14    vol.reset();
15  });
16
17  it(`creates a debug html file`, async () => {
18    const projectRoot = '/';
19    await writeDebugHtmlAsync({
20      outputDir: projectRoot,
21      fileNames: { ios: 'index.ios.js', android: 'index.android.js', windows: 'index.windows.js' },
22    });
23    expect(vol.readFileSync(path.join(projectRoot, 'debug.html'), 'utf8')).toMatchSnapshot();
24  });
25});
26
27describe(writeBundlesAsync, () => {
28  afterEach(() => {
29    vol.reset();
30  });
31  it(`writes JS bundles to disk`, async () => {
32    const projectRoot = '/';
33
34    const contents = `var foo = true;`;
35    const results = await writeBundlesAsync({
36      outputDir: projectRoot,
37      bundles: {
38        ios: {
39          code: contents,
40        },
41        android: {
42          code: contents,
43        },
44      },
45    });
46
47    expect(results.fileNames.android).toBeDefined();
48
49    expect(results.fileNames.ios).toBe('ios-4fe3891dcaca43901bd8797db78405e4.js');
50    expect(results.hashes).toStrictEqual({
51      ios: expect.any(String),
52      android: expect.any(String),
53    });
54    expect(vol.readFileSync(path.join(projectRoot, results.fileNames.ios!), 'utf8')).toBe(contents);
55  });
56  it(`writes hbc bundles to disk`, async () => {
57    const projectRoot = '/';
58    const contents = Uint8Array.from([1, 2, 3]);
59    const results = await writeBundlesAsync({
60      outputDir: projectRoot,
61      bundles: {
62        ios: {
63          // this overwrites js code if present
64          hermesBytecodeBundle: contents,
65          code: 'var foo = true;',
66        },
67      },
68    });
69
70    expect(results.fileNames.ios).toBe('ios-5289df737df57326fcdd22597afb1fac.hbc');
71    expect(results.hashes).toStrictEqual({
72      ios: expect.any(String),
73    });
74    expect(vol.readFileSync(path.join(projectRoot, results.fileNames.ios!))).toBeDefined();
75  });
76});
77
78describe(writeAssetMapAsync, () => {
79  afterEach(() => {
80    vol.reset();
81  });
82  it(`writes asset map to disk`, async () => {
83    const projectRoot = '/';
84
85    const results = await writeAssetMapAsync({
86      outputDir: projectRoot,
87      assets: [{ hash: 'alpha' }, { hash: 'beta' }] as any,
88    });
89
90    expect(results).toStrictEqual({
91      alpha: { hash: 'alpha' },
92      beta: { hash: 'beta' },
93    });
94
95    expect(
96      JSON.parse(vol.readFileSync(path.join(projectRoot, 'assetmap.json'), 'utf8') as string)
97    ).toBeDefined();
98  });
99});
100
101describe(writeSourceMapsAsync, () => {
102  afterEach(() => {
103    vol.reset();
104  });
105
106  it(`writes source maps to disk`, async () => {
107    const projectRoot = '/';
108
109    // User wrote this
110    const contents = `var foo = true;\ninvalid-source-map-comment`;
111
112    // Metro made this
113    const bundles = {
114      ios: {
115        code: contents,
116        map: 'ios_map',
117      },
118      android: {
119        code: contents,
120        map: 'android_map',
121      },
122    };
123
124    // Expo persists the code and returns info
125    const jsResults = await writeBundlesAsync({
126      outputDir: projectRoot,
127      bundles,
128    });
129
130    // Expo also modifies the source maps and persists
131    const results = await writeSourceMapsAsync({
132      outputDir: projectRoot,
133      hashes: jsResults.hashes,
134      fileNames: jsResults.fileNames,
135      bundles,
136    });
137
138    for (const item of results) {
139      assert(item);
140      expect(vol.readFileSync(path.join(projectRoot, item.fileName), 'utf8')).toBe(item.map);
141      expect(
142        vol.readFileSync(path.join(projectRoot, `${item.platform}-${item.hash}.js`), 'utf8')
143      ).toMatch(/\/\/# sourceMappingURL=/);
144      // // Removed by `removeOriginalSourceMappingUrl`
145      // expect(
146      //   vol.readFileSync(path.join(projectRoot, `${item.platform}-${item.hash}.js`), 'utf8')
147      // ).not.toMatch(/invalid-source-map-comment/);
148    }
149  });
150
151  it(`skips writing when the map is not defined`, async () => {
152    const projectRoot = '/';
153
154    // User wrote this
155    const contents = `var foo = true;\ninvalid-source-map-comment`;
156
157    // Metro made this
158    const bundles = {
159      ios: {
160        code: contents,
161      },
162      android: {
163        code: contents,
164        map: 'android_map',
165      },
166    };
167
168    // Expo persists the code and returns info
169    const jsResults = await writeBundlesAsync({
170      outputDir: projectRoot,
171      bundles,
172    });
173
174    // Expo also modifies the source maps and persists
175    const results = await writeSourceMapsAsync({
176      outputDir: projectRoot,
177      hashes: jsResults.hashes,
178      fileNames: jsResults.fileNames,
179      bundles,
180    });
181
182    expect(results).toHaveLength(1);
183  });
184});
185