Skip to content

Commit c3b88fc

Browse files
authored
[FSSDK-12089] run tests in browser using vitest (#1125)
1 parent f3933c0 commit c3b88fc

25 files changed

+7200
-6469
lines changed

.github/workflows/javascript.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,31 @@ jobs:
4040
CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }}
4141
TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }}
4242

43+
browser_tests:
44+
runs-on: ubuntu-latest
45+
strategy:
46+
fail-fast: false
47+
matrix:
48+
browser: ['chrome', 'firefox', 'edge', 'safari']
49+
env:
50+
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
51+
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
52+
USE_LOCAL_BROWSER: 'false'
53+
TEST_BROWSER: ${{ matrix.browser }}
54+
steps:
55+
- uses: actions/checkout@v3
56+
- name: Set up Node
57+
uses: actions/setup-node@v3
58+
with:
59+
node-version: 20
60+
cache: 'npm'
61+
cache-dependency-path: ./package-lock.json
62+
- name: Browser tests - ${{ matrix.browser }}
63+
working-directory: .
64+
run: |
65+
npm install
66+
npm run test-browser
67+
4368
# crossbrowser_and_umd_unit_tests:
4469
# runs-on: ubuntu-latest
4570
# env:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ browserstack.err
1515
local.log
1616

1717
**/*.gen.ts
18+
19+
.env

lib/core/audience_evaluator/index.spec.ts

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { beforeEach, afterEach, describe, it, vi, expect, afterAll } from 'vitest';
16+
import { beforeEach, afterEach, describe, it, vi, expect, afterAll, MockInstance } from 'vitest';
1717

1818
import AudienceEvaluator, { createAudienceEvaluator } from './index';
1919
import * as conditionTreeEvaluator from '../condition_tree_evaluator';
@@ -23,6 +23,9 @@ import { getMockLogger } from '../../tests/mock/mock_logger';
2323
import { Audience, OptimizelyDecideOption, OptimizelyDecision } from '../../shared_types';
2424
import { IOptimizelyUserContext } from '../../optimizely_user_context';
2525

26+
vi.mock('../condition_tree_evaluator', { spy: true });
27+
vi.mock('../custom_attribute_condition_evaluator', { spy: true });
28+
2629
let mockLogger = getMockLogger();
2730

2831
const getMockUserContext = (attributes?: unknown, segments?: string[]): IOptimizelyUserContext => ({
@@ -111,7 +114,7 @@ describe('lib/core/audience_evaluator', () => {
111114
});
112115

113116
afterEach(() => {
114-
vi.restoreAllMocks();
117+
vi.resetAllMocks();
115118
});
116119

117120
describe('APIs', () => {
@@ -207,20 +210,19 @@ describe('lib/core/audience_evaluator', () => {
207210
});
208211

209212
describe('integration with dependencies', () => {
213+
const evaluateSpy = conditionTreeEvaluator.evaluate as unknown as MockInstance<typeof conditionTreeEvaluator.evaluate>;
214+
const getEvaluatorSpy = customAttributeConditionEvaluator.getEvaluator as unknown as MockInstance<typeof customAttributeConditionEvaluator.getEvaluator>;
215+
210216
beforeEach(() => {
211217
vi.clearAllMocks();
212218
});
213219

214-
afterEach(() => {
215-
vi.resetAllMocks();
216-
});
217-
218220
afterAll(() => {
219221
vi.resetAllMocks();
220222
});
221223

222224
it('returns true if conditionTreeEvaluator.evaluate returns true', () => {
223-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(true);
225+
evaluateSpy.mockReturnValue(true);
224226
const result = audienceEvaluator.evaluate(
225227
['or', '0', '1'],
226228
audiencesById,
@@ -230,7 +232,7 @@ describe('lib/core/audience_evaluator', () => {
230232
});
231233

232234
it('returns false if conditionTreeEvaluator.evaluate returns false', () => {
233-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(false);
235+
evaluateSpy.mockReturnValue(false);
234236
const result = audienceEvaluator.evaluate(
235237
['or', '0', '1'],
236238
audiencesById,
@@ -240,7 +242,7 @@ describe('lib/core/audience_evaluator', () => {
240242
});
241243

242244
it('returns false if conditionTreeEvaluator.evaluate returns null', () => {
243-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(null);
245+
evaluateSpy.mockReturnValue(null);
244246
const result = audienceEvaluator.evaluate(
245247
['or', '0', '1'],
246248
audiencesById,
@@ -250,13 +252,13 @@ describe('lib/core/audience_evaluator', () => {
250252
});
251253

252254
it('calls customAttributeConditionEvaluator.evaluate in the leaf evaluator for audience conditions', () => {
253-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementation((conditions: any, leafEvaluator) => {
255+
evaluateSpy.mockImplementation((conditions: any, leafEvaluator: any) => {
254256
return leafEvaluator(conditions[1]);
255257
});
256258

257259
const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(false);
258260

259-
vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({
261+
getEvaluatorSpy.mockReturnValue({
260262
evaluate: mockCustomAttributeConditionEvaluator,
261263
});
262264

@@ -277,26 +279,28 @@ describe('lib/core/audience_evaluator', () => {
277279
});
278280

279281
describe('Audience evaluation logging', () => {
280-
let mockCustomAttributeConditionEvaluator: ReturnType<typeof vi.fn>;
281-
282+
const evaluateSpy = conditionTreeEvaluator.evaluate as unknown as MockInstance<typeof conditionTreeEvaluator.evaluate>;
283+
const getEvaluatorSpy = customAttributeConditionEvaluator.getEvaluator as unknown as MockInstance<typeof customAttributeConditionEvaluator.getEvaluator>;
284+
282285
beforeEach(() => {
283-
mockCustomAttributeConditionEvaluator = vi.fn();
284-
vi.spyOn(conditionTreeEvaluator, 'evaluate');
285-
vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({
286-
evaluate: mockCustomAttributeConditionEvaluator,
287-
});
286+
vi.clearAllMocks();
288287
});
289288

290-
afterEach(() => {
291-
vi.restoreAllMocks();
289+
afterAll(() => {
290+
vi.resetAllMocks();
292291
});
293292

294293
it('logs correctly when conditionTreeEvaluator.evaluate returns null', () => {
295-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => {
294+
evaluateSpy.mockImplementationOnce((conditions: any, leafEvaluator) => {
296295
return leafEvaluator(conditions[1]);
297296
});
298297

299-
mockCustomAttributeConditionEvaluator.mockReturnValue(null);
298+
const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(null);
299+
300+
getEvaluatorSpy.mockReturnValue({
301+
evaluate: mockCustomAttributeConditionEvaluator,
302+
});
303+
300304
const userAttributes = { device_model: 5.5 };
301305
const user = getMockUserContext(userAttributes);
302306

@@ -319,11 +323,15 @@ describe('lib/core/audience_evaluator', () => {
319323
});
320324

321325
it('logs correctly when conditionTreeEvaluator.evaluate returns true', () => {
322-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => {
326+
evaluateSpy.mockImplementationOnce((conditions: any, leafEvaluator) => {
323327
return leafEvaluator(conditions[1]);
324328
});
325329

326-
mockCustomAttributeConditionEvaluator.mockReturnValue(true);
330+
const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(true);
331+
332+
getEvaluatorSpy.mockReturnValue({
333+
evaluate: mockCustomAttributeConditionEvaluator,
334+
});
327335

328336
const userAttributes = { device_model: 'iphone' };
329337
const user = getMockUserContext(userAttributes);
@@ -345,11 +353,16 @@ describe('lib/core/audience_evaluator', () => {
345353
});
346354

347355
it('logs correctly when conditionTreeEvaluator.evaluate returns false', () => {
348-
vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => {
356+
evaluateSpy.mockImplementationOnce((conditions: any, leafEvaluator) => {
349357
return leafEvaluator(conditions[1]);
350358
});
351359

352-
mockCustomAttributeConditionEvaluator.mockReturnValue(false);
360+
const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(false);
361+
362+
getEvaluatorSpy.mockReturnValue({
363+
evaluate: mockCustomAttributeConditionEvaluator,
364+
});
365+
353366

354367
const userAttributes = { device_model: 'android' };
355368
const user = getMockUserContext(userAttributes);

lib/core/bucketer/index.spec.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
16+
import { describe, it, expect, beforeEach, vi, afterEach, MockInstance } from 'vitest';
1717
import { sprintf } from '../../utils/fns';
1818
import projectConfig, { ProjectConfig } from '../../project_config/project_config';
1919
import { getTestProjectConfig } from '../../tests/test_data';
2020
import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message';
2121
import * as bucketer from './';
2222
import * as bucketValueGenerator from './bucket_value_generator';
2323

24+
vi.mock('./bucket_value_generator', { spy: true });
25+
2426
import {
2527
USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP,
2628
USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP,
@@ -65,8 +67,12 @@ describe('excluding groups', () => {
6567
let configObj;
6668
const mockLogger = getMockLogger();
6769
let bucketerParams: BucketerParams;
70+
const mockGenerateBucketValue = bucketValueGenerator.generateBucketValue as unknown as
71+
MockInstance<typeof bucketValueGenerator.generateBucketValue>;
6872

6973
beforeEach(() => {
74+
vi.clearAllMocks();
75+
7076
setLogSpy(mockLogger);
7177
configObj = projectConfig.createProjectConfig(cloneDeep(testData));
7278

@@ -83,13 +89,12 @@ describe('excluding groups', () => {
8389
validateEntity: true,
8490
};
8591

86-
vi.spyOn(bucketValueGenerator, 'generateBucketValue')
87-
.mockReturnValueOnce(50)
92+
mockGenerateBucketValue.mockReturnValueOnce(50)
8893
.mockReturnValueOnce(50000);
8994
});
9095

9196
afterEach(() => {
92-
vi.restoreAllMocks();
97+
vi.resetAllMocks();
9398
});
9499

95100
it('should return decision response with correct variation ID when provided bucket value', async () => {
@@ -113,8 +118,12 @@ describe('including groups: random', () => {
113118
let configObj: ProjectConfig;
114119
const mockLogger = getMockLogger();
115120
let bucketerParams: BucketerParams;
121+
const mockGenerateBucketValue = bucketValueGenerator.generateBucketValue as unknown as
122+
MockInstance<typeof bucketValueGenerator.generateBucketValue>;
116123

117124
beforeEach(() => {
125+
vi.clearAllMocks();
126+
118127
setLogSpy(mockLogger);
119128
configObj = projectConfig.createProjectConfig(cloneDeep(testData));
120129
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -133,11 +142,11 @@ describe('including groups: random', () => {
133142
});
134143

135144
afterEach(() => {
136-
vi.restoreAllMocks();
145+
vi.resetAllMocks();
137146
});
138147

139148
it('should return decision response with the proper variation for a user in a grouped experiment', () => {
140-
vi.spyOn(bucketValueGenerator, 'generateBucketValue')
149+
mockGenerateBucketValue
141150
.mockReturnValueOnce(50)
142151
.mockReturnValueOnce(50);
143152

@@ -156,7 +165,7 @@ describe('including groups: random', () => {
156165
});
157166

158167
it('should return decision response with variation null when a user is bucketed into a different grouped experiment than the one speicfied', () => {
159-
vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(5000);
168+
mockGenerateBucketValue.mockReturnValue(5000);
160169

161170
const decisionResponse = bucketer.bucket(bucketerParams);
162171

@@ -173,7 +182,7 @@ describe('including groups: random', () => {
173182
});
174183

175184
it('should return decision response with variation null when a user is not bucketed into any experiments in the random group', () => {
176-
vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(50000);
185+
mockGenerateBucketValue.mockReturnValue(50000);
177186

178187
const decisionResponse = bucketer.bucket(bucketerParams);
179188

@@ -185,7 +194,7 @@ describe('including groups: random', () => {
185194
});
186195

187196
it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', () => {
188-
vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(9000);
197+
mockGenerateBucketValue.mockReturnValueOnce(9000);
189198

190199
const decisionResponse = bucketer.bucket(bucketerParams);
191200

@@ -215,8 +224,12 @@ describe('including groups: overlapping', () => {
215224
let configObj: ProjectConfig;
216225
const mockLogger = getMockLogger();
217226
let bucketerParams: BucketerParams;
227+
const mockGenerateBucketValue = bucketValueGenerator.generateBucketValue as unknown as
228+
MockInstance<typeof bucketValueGenerator.generateBucketValue>;
218229

219230
beforeEach(() => {
231+
vi.clearAllMocks();
232+
220233
setLogSpy(mockLogger);
221234
configObj = projectConfig.createProjectConfig(cloneDeep(testData));
222235
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -235,11 +248,11 @@ describe('including groups: overlapping', () => {
235248
});
236249

237250
afterEach(() => {
238-
vi.restoreAllMocks();
251+
vi.resetAllMocks();
239252
});
240253

241254
it('should return decision response with variation when a user falls into an experiment within an overlapping group', () => {
242-
vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(0);
255+
mockGenerateBucketValue.mockReturnValueOnce(0);
243256

244257
const decisionResponse = bucketer.bucket(bucketerParams);
245258

@@ -249,7 +262,7 @@ describe('including groups: overlapping', () => {
249262
});
250263

251264
it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', () => {
252-
vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(3000);
265+
mockGenerateBucketValue.mockReturnValueOnce(3000);
253266
const decisionResponse = bucketer.bucket(bucketerParams);
254267

255268
expect(decisionResponse.result).toBeNull();
@@ -288,7 +301,7 @@ describe('bucket value falls into empty traffic allocation ranges', () => {
288301
});
289302

290303
afterEach(() => {
291-
vi.restoreAllMocks();
304+
vi.resetAllMocks();
292305
});
293306

294307
it('should return decision response with variation null', () => {
@@ -338,7 +351,7 @@ describe('traffic allocation has invalid variation ids', () => {
338351
});
339352

340353
afterEach(() => {
341-
vi.restoreAllMocks();
354+
vi.resetAllMocks();
342355
});
343356

344357
it('should return decision response with variation null', () => {

0 commit comments

Comments
 (0)