1import { vol, fs } from 'memfs';
2
3import { createControlledEnvironment, getFiles } from '../env';
4
5beforeEach(() => {
6  vol.reset();
7});
8
9const originalEnv = process.env;
10
11function resetEnv() {
12  process.env = originalEnv;
13  delete process.env.EXPO_NO_DOTENV;
14}
15
16beforeEach(() => {
17  resetEnv();
18});
19afterAll(() => {
20  resetEnv();
21});
22
23describe(getFiles, () => {
24  const originalError = console.error;
25  beforeEach(() => {
26    console.error = jest.fn();
27    resetEnv();
28  });
29  afterEach(() => {
30    console.error = originalError;
31  });
32
33  it(`gets development files`, () => {
34    expect(getFiles('development')).toEqual([
35      '.env.development.local',
36      '.env.local',
37      '.env.development',
38      '.env',
39    ]);
40  });
41  it(`gets production files`, () => {
42    expect(getFiles('production')).toEqual([
43      '.env.production.local',
44      '.env.local',
45      '.env.production',
46      '.env',
47    ]);
48  });
49  it(`gets test files`, () => {
50    // important
51    expect(getFiles('test')).toEqual(['.env.test.local', '.env.test', '.env']);
52  });
53  it(`gets no files when dotenv is disabled`, () => {
54    process.env.EXPO_NO_DOTENV = '1';
55    ['development', 'production', 'test'].forEach((mode) => {
56      expect(getFiles(mode)).toEqual([]);
57    });
58  });
59
60  it(`throws if NODE_ENV is not set`, () => {
61    getFiles(undefined);
62
63    expect(console.error).toBeCalledTimes(2);
64    expect(console.error).toBeCalledWith(
65      expect.stringContaining('The NODE_ENV environment variable is required but was not specified')
66    );
67  });
68  it(`throws if NODE_ENV is not valid`, () => {
69    expect(() => getFiles('invalid')).toThrowErrorMatchingInlineSnapshot(
70      `"Environment variable "NODE_ENV=invalid" is invalid. Valid values are "development", "test", and "production"`
71    );
72  });
73});
74
75describe('get', () => {
76  beforeEach(() => {
77    resetEnv();
78  });
79
80  it(`memoizes`, () => {
81    delete process.env.FOO;
82    const envRuntime = createControlledEnvironment();
83    vol.fromJSON(
84      {
85        '.env': 'FOO=default',
86      },
87      '/'
88    );
89    expect(envRuntime.get('/')).toEqual({
90      FOO: 'default',
91    });
92
93    fs.writeFileSync('/.env', 'FOO=changed');
94
95    expect(envRuntime.get('/')).toEqual({
96      FOO: 'default',
97    });
98    expect(envRuntime.get('/', { force: true })).toEqual({
99      FOO: 'changed',
100    });
101  });
102});
103describe('_getForce', () => {
104  beforeEach(() => {
105    resetEnv();
106  });
107
108  it(`returns the value of the environment variable`, () => {
109    delete process.env.FOO;
110
111    const envRuntime = createControlledEnvironment();
112    vol.fromJSON(
113      {
114        '.env': 'FOO=bar',
115      },
116      '/'
117    );
118
119    expect(envRuntime._getForce('/')).toEqual({
120      FOO: 'bar',
121    });
122  });
123
124  it(`cascades env files (development)`, () => {
125    delete process.env.FOO;
126    process.env.NODE_ENV = 'development';
127    const envRuntime = createControlledEnvironment();
128    vol.fromJSON(
129      {
130        '.env': 'FOO=default',
131        '.env.local': 'FOO=default-local',
132        '.env.development': 'FOO=dev',
133        '.env.production': 'FOO=prod',
134        '.env.production.local': 'FOO=prod-local',
135        '.env.development.local': 'FOO=dev-local',
136      },
137      '/'
138    );
139
140    expect(envRuntime._getForce('/')).toEqual({
141      FOO: 'dev-local',
142    });
143  });
144
145  it(`cascades env files (production)`, () => {
146    delete process.env.FOO;
147    process.env.NODE_ENV = 'production';
148    const envRuntime = createControlledEnvironment();
149    vol.fromJSON(
150      {
151        '.env': 'FOO=default',
152        '.env.local': 'FOO=default-local',
153        '.env.production': 'FOO=prod',
154        '.env.production.local': 'FOO=prod-local',
155      },
156      '/'
157    );
158
159    expect(envRuntime._getForce('/')).toEqual({
160      FOO: 'prod-local',
161    });
162  });
163
164  it(`cascades env files (default)`, () => {
165    delete process.env.FOO;
166    const envRuntime = createControlledEnvironment();
167    vol.fromJSON(
168      {
169        '.env': 'FOO=default',
170        '.env.local': 'FOO=default-local',
171      },
172      '/'
173    );
174
175    expect(envRuntime._getForce('/')).toEqual({
176      FOO: 'default-local',
177    });
178  });
179
180  it(`skips modifying the environment with dotenv if disabled with EXPO_NO_DOTENV`, () => {
181    delete process.env.FOO;
182    process.env.EXPO_NO_DOTENV = '1';
183    const envRuntime = createControlledEnvironment();
184    vol.fromJSON(
185      {
186        '.env': 'FOO=default',
187        '.env.local': 'FOO=default-local',
188      },
189      '/'
190    );
191
192    expect(envRuntime._getForce('/')).toEqual({});
193  });
194
195  it(`does not return the env var if the initial the value of the environment variable`, () => {
196    const envRuntime = createControlledEnvironment();
197    process.env.FOO = 'not-bar';
198
199    vol.fromJSON(
200      {
201        '.env': 'FOO=bar',
202      },
203      '/'
204    );
205
206    expect(envRuntime._getForce('/')).toEqual({});
207  });
208
209  it(`Does not fail when no files are available`, () => {
210    vol.fromJSON({}, '/');
211    expect(createControlledEnvironment()._getForce('/')).toEqual({});
212  });
213
214  it(`Does not assert on invalid env files`, () => {
215    vol.fromJSON(
216      {
217        '.env': 'ˆ˙•ª∆ø…ˆ',
218      },
219      '/'
220    );
221
222    expect(createControlledEnvironment()._getForce('/')).toEqual({});
223  });
224});
225