Skip to content

Commit

Permalink
chore(crashlytics): deprecation warning for v8 API ahead of future ma…
Browse files Browse the repository at this point in the history
…jor release (#8107)
  • Loading branch information
russellwheatley authored Nov 22, 2024
1 parent addcd07 commit 5e5072d
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 19 deletions.
58 changes: 58 additions & 0 deletions packages/app/lib/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,64 @@ export function tryJSONStringify(data) {
}
}

// Used to indicate if there is no corresponding modular function
const NO_REPLACEMENT = true;

const mapOfDeprecationReplacements = {
crashlytics: {
checkForUnsentReports: 'checkForUnsentReports()',
crash: 'crash()',
deleteUnsentReports: 'deleteUnsentReports()',
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
log: 'log()',
setAttribute: 'setAttribute()',
setAttributes: 'setAttributes()',
setUserId: 'setUserId()',
recordError: 'recordError()',
sendUnsentReports: 'sendUnsentReports()',
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
},
};

const v8deprecationMessage =
'This v8 method is deprecated and will be removed in the next major release ' +
'as part of move to match Firebase Web modular v9 SDK API.';

export function deprecationConsoleWarning(moduleName, methodName, isModularMethod) {
if (!isModularMethod) {
const moduleMap = mapOfDeprecationReplacements[moduleName];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
// only warn if it is mapped and purposefully deprecated
if (replacementMethodName) {
const message = createMessage(moduleName, methodName);

// eslint-disable-next-line no-console
console.warn(message);
}
}
}
}

export function createMessage(moduleName, methodName, uniqueMessage = '') {
if (uniqueMessage.length > 0) {
// Unique deprecation message used for testing
return uniqueMessage;
}

const moduleMap = mapOfDeprecationReplacements[moduleName];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
if (replacementMethodName) {
if (replacementMethodName !== NO_REPLACEMENT) {
return v8deprecationMessage + ` Please use \`${replacementMethodName}\` instead.`;
} else {
return v8deprecationMessage;
}
}
}
}

export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-call';

export function warnIfNotModularCall(args, replacementMethodName, noAlternative) {
Expand Down
40 changes: 38 additions & 2 deletions packages/app/lib/common/unitTestUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
// @ts-nocheck
import { expect, jest } from '@jest/globals';
import { createMessage } from './index';

export const checkV9Deprecation = (modularFunction: () => void, nonModularFunction: () => void) => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
consoleWarnSpy.mockRestore();
modularFunction();
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockClear();
const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(() => {});
nonModularFunction();
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
consoleWarnSpy.mockRestore();

expect(consoleWarnSpy2).toHaveBeenCalledTimes(1);
consoleWarnSpy2.mockClear();
};

export type CheckV9DeprecationFunction = (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
uniqueMessage: string = '',
) => void;

export const createCheckV9Deprecation = (moduleName: string): CheckV9DeprecationFunction => {
return (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
uniqueMessage = '',
) => {
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
consoleWarnSpy.mockReset();
modularFunction();
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockReset();
const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => {
const message = createMessage(moduleName, methodName, uniqueMessage);
expect(message).toMatch(warnMessage);
});
nonModularFunction();

expect(consoleWarnSpy2).toHaveBeenCalledTimes(1);
consoleWarnSpy2.mockReset();
};
};
30 changes: 26 additions & 4 deletions packages/app/lib/internal/registry/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
*/

import { isString } from '../../common';
import { isString, MODULAR_DEPRECATION_ARG, deprecationConsoleWarning } from '../../common';
import FirebaseApp from '../../FirebaseApp';
import SDK_VERSION from '../../version';
import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants';
Expand Down Expand Up @@ -170,10 +170,10 @@ function getOrCreateModuleForRoot(moduleNamespace) {
}

if (!APP_MODULE_INSTANCE[_app.name][moduleNamespace]) {
APP_MODULE_INSTANCE[_app.name][moduleNamespace] = new ModuleClass(
_app,
NAMESPACE_REGISTRY[moduleNamespace],
const module = createDeprecationProxy(
new ModuleClass(_app, NAMESPACE_REGISTRY[moduleNamespace]),
);
APP_MODULE_INSTANCE[_app.name][moduleNamespace] = module;
}

return APP_MODULE_INSTANCE[_app.name][moduleNamespace];
Expand Down Expand Up @@ -277,6 +277,28 @@ export function getFirebaseRoot() {
return createFirebaseRoot();
}

function createDeprecationProxy(instance) {
return new Proxy(instance, {
get(target, prop, receiver) {
const originalMethod = target[prop];
if (prop === 'constructor') {
return target.constructor;
}
if (typeof originalMethod === 'function') {
return function (...args) {
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
const moduleName = receiver._config.namespace;

deprecationConsoleWarning(moduleName, prop, isModularMethod);

return originalMethod.apply(target, args);
};
}
return Reflect.get(target, prop, receiver);
},
});
}

/**
*
* @param options
Expand Down
137 changes: 135 additions & 2 deletions packages/crashlytics/__tests__/crashlytics.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from '@jest/globals';

import { describe, expect, it, jest, beforeEach } from '@jest/globals';
// @ts-ignore test
import FirebaseModule from '../../app/lib/internal/FirebaseModule';
import {
createCheckV9Deprecation,
CheckV9DeprecationFunction,
} from '../../app/lib/common/unitTestUtils';
import {
firebase,
getCrashlytics,
Expand Down Expand Up @@ -79,4 +84,132 @@ describe('Crashlytics', function () {
expect(setCrashlyticsCollectionEnabled).toBeDefined();
});
});

describe('test `console.warn` is called for RNFB v8 API & not called for v9 API', function () {
let checkV9Deprecation: CheckV9DeprecationFunction;

beforeEach(function () {
checkV9Deprecation = createCheckV9Deprecation('crashlytics');

// @ts-ignore test
jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => {
return new Proxy(
{},
{
get: () => jest.fn(),
},
);
});
});

it('checkForUnsentReports', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => checkForUnsentReports(crashlytics),
() => crashlytics.checkForUnsentReports(),
'checkForUnsentReports',
);
});

it('crash', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => crash(crashlytics),
() => crashlytics.crash(),
'crash',
);
});

it('deleteUnsentReports', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => deleteUnsentReports(crashlytics),
() => crashlytics.deleteUnsentReports(),
'deleteUnsentReports',
);
});

it('didCrashOnPreviousExecution', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => didCrashOnPreviousExecution(crashlytics),
() => crashlytics.didCrashOnPreviousExecution(),
'didCrashOnPreviousExecution',
);
});

it('log', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => log(crashlytics, 'message'),
() => crashlytics.log('message'),
'log',
);
});

it('setAttribute', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => setAttribute(crashlytics, 'name', 'value'),
() => crashlytics.setAttribute('name', 'value'),
'setAttribute',
);
});

it('setAttributes', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => setAttributes(crashlytics, {}),
() => crashlytics.setAttributes({}),
'setAttributes',
);
});

it('setUserId', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => setUserId(crashlytics, 'id'),
() => crashlytics.setUserId('id'),
'setUserId',
);
});

it('recordError', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => recordError(crashlytics, new Error(), 'name'),
() => crashlytics.recordError(new Error(), 'name'),
'recordError',
);
});

it('sendUnsentReports', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => sendUnsentReports(crashlytics),
() => crashlytics.sendUnsentReports(),
'sendUnsentReports',
);
});

it('setCrashlyticsCollectionEnabled', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
() => setCrashlyticsCollectionEnabled(crashlytics, true),
() => crashlytics.setCrashlyticsCollectionEnabled(true),
'setCrashlyticsCollectionEnabled',
);
});

it('isCrashlyticsCollectionEnabled', function () {
const crashlytics = getCrashlytics();
checkV9Deprecation(
// swapped order here because we're deprecating the modular method and keeping the property on Crashlytics instance
() => crashlytics.isCrashlyticsCollectionEnabled,
() => isCrashlyticsCollectionEnabled(crashlytics),
'',
'`isCrashlyticsCollectionEnabled()` is deprecated, please use `Crashlytics.isCrashlyticsCollectionEnabled` property instead',
);
});
});
});
1 change: 1 addition & 0 deletions packages/crashlytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class FirebaseCrashlyticsModule extends FirebaseModule {
}

get isCrashlyticsCollectionEnabled() {
// Purposefully did not deprecate this as I think it should remain a property rather than a method.
return this._isCrashlyticsCollectionEnabled;
}

Expand Down
Loading

0 comments on commit 5e5072d

Please sign in to comment.