Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vitest-analytics-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/analytics': patch
---

Migrate test suite to Vitest and export test helper function.
17 changes: 6 additions & 11 deletions packages/analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@
"build:deps": "lerna run build --scope @firebase/analytics --include-dependencies",
"dev": "rollup -c -w",
"test": "run-p --npm-path npm lint test:all",
"test:all": "run-p --npm-path npm test:browser test:integration",
"test:all": "vitest run",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The test:all script has been simplified to vitest run, which only runs the default unit tests. This means integration tests (configured in vitest.integration.config.mjs) are no longer executed as part of test:all or in CI (via test:ci). Please restore the execution of both unit and integration tests.

Suggested change
"test:all": "vitest run",
"test:all": "run-p --npm-path npm test:browser test:integration",

"test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all",
"test:browser": "karma start --nocache",
"test:integration": "karma start ./karma.integration.conf.js --nocache",
"test:browser": "vitest run",
"test:integration": "vitest run -c vitest.integration.config.mjs",
"trusted-type-check": "tsec -p tsconfig.json --noEmit",
"api-report": "api-extractor run --local --verbose",
"doc": "api-documenter markdown --input temp --output docs",
"build:doc": "yarn build && yarn doc",
"typings:public": "node ../../scripts/build/use_typings.js ./dist/analytics-public.d.ts"
"typings:public": "node ../../scripts/build/use_typings.js ./dist/analytics-public.d.ts",
"test:browser:debug": "vitest --browser.headless=false"
},
"peerDependencies": {
"@firebase/app": "0.x"
Expand Down Expand Up @@ -64,11 +65,5 @@
"bugs": {
"url": "https://github.com/firebase/firebase-js-sdk/issues"
},
"typings": "dist/src/index.d.ts",
"nyc": {
"extension": [
".ts"
],
"reportDir": "./coverage/node"
}
"typings": "dist/src/index.d.ts"
}
2 changes: 1 addition & 1 deletion packages/analytics/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const buildPlugins = [
typescriptPlugin({
typescript,
tsconfigOverride: {
exclude: [...tsconfig.exclude, '**/*.test.ts']
exclude: [...tsconfig.exclude, '**/*.test.ts', 'testing/**']
}
}),
json({ preferConst: true })
Expand Down
56 changes: 27 additions & 29 deletions packages/analytics/src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
* limitations under the License.
*/

import { expect } from 'chai';
import { SinonStub, stub } from 'sinon';
import '../testing/setup';
import { expect, vi } from 'vitest';
import { getFullApp } from '../testing/get-fake-firebase-services';
import {
getAnalytics,
Expand All @@ -27,35 +25,40 @@ import {
} from './api';
import { FirebaseApp, deleteApp } from '@firebase/app';
import { AnalyticsError } from './errors';
import * as init from './initialize-analytics';
const fakeAppParams = { appId: 'abcdefgh12345:23405', apiKey: 'AAbbCCdd12345' };
import * as factory from './factory';

import * as initAnalytics from './initialize-analytics';

vi.mock('./initialize-analytics', { spy: true });

import { _setWrappedGtagFunction } from './factory';

import {
defaultConsentSettingsForInit,
defaultEventParametersForInit
} from './functions';
import { ConsentSettings } from './public-types';

describe('FirebaseAnalytics API tests', () => {
let initStub: SinonStub = stub();
let app: FirebaseApp;
const wrappedGtag: SinonStub = stub();
const wrappedGtag = vi.fn();

beforeEach(() => {
initStub = stub(init, '_initializeAnalytics').resolves(
vi.spyOn(initAnalytics, '_initializeAnalytics').mockResolvedValue(
'FAKE_MEASUREMENT_ID'
);
_setWrappedGtagFunction(undefined);
});

afterEach(async () => {
await initStub();
initStub.restore();
_setWrappedGtagFunction(undefined);
wrappedGtag.mockReset();
if (app) {
return deleteApp(app);
}
Comment on lines 56 to 58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The suite-level app variable is deleted in afterEach but not reset to undefined. Resetting it to undefined prevents potential state leakage or double-deletion attempts in subsequent tests.

    if (app) {
      await deleteApp(app);
      app = undefined as any;
    }

});
Comment thread
Manvi1203 marked this conversation as resolved.

after(() => {
afterAll(() => {
delete window['gtag'];
delete window['dataLayer'];
});
Expand All @@ -64,7 +67,7 @@ describe('FirebaseAnalytics API tests', () => {
app = getFullApp(fakeAppParams);
const analyticsInstance = initializeAnalytics(app);
const newInstance = initializeAnalytics(app);
expect(analyticsInstance).to.equal(newInstance);
expect(analyticsInstance).toBe(newInstance);
});
it('initializeAnalytics() with same options returns same instance', () => {
app = getFullApp(fakeAppParams);
Expand All @@ -74,7 +77,7 @@ describe('FirebaseAnalytics API tests', () => {
const newInstance = initializeAnalytics(app, {
config: { 'send_page_view': false }
});
expect(analyticsInstance).to.equal(newInstance);
expect(analyticsInstance).toBe(newInstance);
});
it('initializeAnalytics() with different options throws', () => {
app = getFullApp(fakeAppParams);
Expand All @@ -85,7 +88,7 @@ describe('FirebaseAnalytics API tests', () => {
initializeAnalytics(app, {
config: { 'send_page_view': true }
})
).to.throw(AnalyticsError.ALREADY_INITIALIZED);
).toThrow(AnalyticsError.ALREADY_INITIALIZED);
});
it('initializeAnalytics() with different options (one undefined) throws', () => {
app = getFullApp(fakeAppParams);
Expand All @@ -94,17 +97,17 @@ describe('FirebaseAnalytics API tests', () => {
initializeAnalytics(app, {
config: { 'send_page_view': true }
})
).to.throw(AnalyticsError.ALREADY_INITIALIZED);
).toThrow(AnalyticsError.ALREADY_INITIALIZED);
});
it('getAnalytics() returns same instance created by previous getAnalytics()', () => {
app = getFullApp(fakeAppParams);
const analyticsInstance = getAnalytics(app);
expect(getAnalytics(app)).to.equal(analyticsInstance);
expect(getAnalytics(app)).toBe(analyticsInstance);
});
it('getAnalytics() returns same instance created by initializeAnalytics()', () => {
app = getFullApp(fakeAppParams);
const analyticsInstance = initializeAnalytics(app);
expect(getAnalytics(app)).to.equal(analyticsInstance);
expect(getAnalytics(app)).toBe(analyticsInstance);
});
it('setDefaultEventParameters() updates defaultEventParametersForInit if gtag does not exist ', () => {
const eventParametersForInit = {
Expand All @@ -113,42 +116,37 @@ describe('FirebaseAnalytics API tests', () => {
};
app = getFullApp(fakeAppParams);
setDefaultEventParameters(eventParametersForInit);
expect(defaultEventParametersForInit).to.deep.equal(eventParametersForInit);
expect(defaultEventParametersForInit).toEqual(eventParametersForInit);
});
it('setDefaultEventParameters() calls gtag set if wrappedGtagFunction exists', () => {
const eventParametersForInit = {
'github_user': 'dwyfrequency',
'company': 'google'
};
stub(factory, 'wrappedGtagFunction').get(() => wrappedGtag);
_setWrappedGtagFunction(wrappedGtag);
app = getFullApp(fakeAppParams);
setDefaultEventParameters(eventParametersForInit);
expect(wrappedGtag).to.have.been.calledWithExactly(
'set',
eventParametersForInit
);
expect(wrappedGtag).toHaveBeenCalledWith('set', eventParametersForInit);
});
it('setConsent() updates defaultConsentSettingsForInit if gtag does not exist ', () => {
const consentParametersForInit: ConsentSettings = {
'analytics_storage': 'granted',
'functionality_storage': 'denied'
};
stub(factory, 'wrappedGtagFunction').get(() => undefined);
_setWrappedGtagFunction(undefined);
app = getFullApp(fakeAppParams);
setConsent(consentParametersForInit);
expect(defaultConsentSettingsForInit).to.deep.equal(
consentParametersForInit
);
expect(defaultConsentSettingsForInit).toEqual(consentParametersForInit);
});
it('setConsent() calls gtag consent "update" if wrappedGtagFunction exists', () => {
const consentParametersForInit: ConsentSettings = {
'analytics_storage': 'granted',
'functionality_storage': 'denied'
};
stub(factory, 'wrappedGtagFunction').get(() => wrappedGtag);
_setWrappedGtagFunction(wrappedGtag);
app = getFullApp(fakeAppParams);
setConsent(consentParametersForInit);
expect(wrappedGtag).to.have.been.calledWithExactly(
expect(wrappedGtag).toHaveBeenCalledWith(
'consent',
'update',
consentParametersForInit
Expand Down
8 changes: 8 additions & 0 deletions packages/analytics/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ let gtagCoreFunction: Gtag;
*/
export let wrappedGtagFunction: Gtag;

/**
* For testing
* @internal
*/
export function _setWrappedGtagFunction(fn: Gtag | undefined): void {
wrappedGtagFunction = fn as Gtag;
}

/**
* Flag to ensure page initialization steps (creation or wrapping of
* dataLayer and gtag script) are only run once per page load.
Expand Down
Loading
Loading