diff --git a/packages/react-native/__tests__/apis.test.ts b/packages/react-native/__tests__/apis.test.ts new file mode 100644 index 00000000000..d8969513838 --- /dev/null +++ b/packages/react-native/__tests__/apis.test.ts @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + computeModPow, + computeS, + getDeviceName, + getOperatingSystem, +} from '../src/apis'; +import { nativeModule } from '../src/nativeModule'; + +jest.mock('react-native', () => ({ + Platform: { OS: 'ios' }, + NativeModules: {}, +})); + +jest.mock('../src/nativeModule', () => ({ + nativeModule: { + computeModPow: jest.fn(), + computeS: jest.fn(), + getDeviceName: jest.fn(), + }, +})); + +describe('APIs', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('getOperatingSystem', () => { + it('should return Platform.OS', () => { + expect(getOperatingSystem()).toBe('ios'); + }); + }); + + describe('computeModPow', () => { + it('should call nativeModule.computeModPow with payload', () => { + const payload = { base: '2', exponent: '3', divisor: '5' }; + computeModPow(payload); + expect(nativeModule.computeModPow).toHaveBeenCalledWith(payload); + }); + }); + + describe('computeS', () => { + it('should call nativeModule.computeS with payload', () => { + const payload = { g: '1', x: '2', k: '3', a: '4', b: '5', u: '6' }; + computeS(payload); + expect(nativeModule.computeS).toHaveBeenCalledWith(payload); + }); + }); + + describe('getDeviceName', () => { + it('should call nativeModule.getDeviceName', () => { + getDeviceName(); + expect(nativeModule.getDeviceName).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/react-native/__tests__/index.test.ts b/packages/react-native/__tests__/index.test.ts new file mode 100644 index 00000000000..fa72d6a82af --- /dev/null +++ b/packages/react-native/__tests__/index.test.ts @@ -0,0 +1,40 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +jest.mock('react-native', () => ({ + Platform: { OS: 'ios', select: jest.fn(() => '') }, + NativeModules: { + AmplifyRTNCore: { + computeModPow: jest.fn(), + computeS: jest.fn(), + getDeviceName: jest.fn(), + }, + }, + AppState: { currentState: 'active' }, +})); + +jest.mock('buffer', () => ({ Buffer: {} })); +jest.mock('base-64', () => ({ decode: jest.fn(), encode: jest.fn() })); + +describe('@aws-amplify/react-native', () => { + it('should export all APIs', () => { + const ReactNativeModule = require('../src/index'); + expect(ReactNativeModule.computeModPow).toBeDefined(); + expect(ReactNativeModule.computeS).toBeDefined(); + expect(ReactNativeModule.getOperatingSystem).toBeDefined(); + expect(ReactNativeModule.getDeviceName).toBeDefined(); + }); + + it('should export all module loaders', () => { + const ReactNativeModule = require('../src/index'); + expect(ReactNativeModule.loadAmplifyPushNotification).toBeDefined(); + expect(ReactNativeModule.loadAmplifyWebBrowser).toBeDefined(); + expect(ReactNativeModule.loadAsyncStorage).toBeDefined(); + expect(ReactNativeModule.loadNetInfo).toBeDefined(); + expect(ReactNativeModule.loadBuffer).toBeDefined(); + expect(ReactNativeModule.loadUrlPolyfill).toBeDefined(); + expect(ReactNativeModule.loadGetRandomValues).toBeDefined(); + expect(ReactNativeModule.loadBase64).toBeDefined(); + expect(ReactNativeModule.loadAppState).toBeDefined(); + }); +}); diff --git a/packages/react-native/__tests__/moduleLoaders.test.ts b/packages/react-native/__tests__/moduleLoaders.test.ts new file mode 100644 index 00000000000..5baa1effae1 --- /dev/null +++ b/packages/react-native/__tests__/moduleLoaders.test.ts @@ -0,0 +1,240 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +describe('Module Loaders', () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + describe('loadBuffer', () => { + it('should return Buffer', () => { + const mockBuffer = { from: jest.fn() }; + jest.doMock('buffer', () => ({ Buffer: mockBuffer })); + const { loadBuffer } = require('../src/moduleLoaders/loadBuffer'); + expect(loadBuffer()).toBe(mockBuffer); + }); + }); + + describe('loadBase64', () => { + it('should return decode and encode functions', () => { + const mockDecode = jest.fn(); + const mockEncode = jest.fn(); + jest.doMock('base-64', () => ({ + decode: mockDecode, + encode: mockEncode, + })); + const { loadBase64 } = require('../src/moduleLoaders/loadBase64'); + const result = loadBase64(); + expect(result.decode).toBe(mockDecode); + expect(result.encode).toBe(mockEncode); + }); + }); + + describe('loadAppState', () => { + it('should return AppState', () => { + const mockAppState = { currentState: 'active' }; + jest.doMock('react-native', () => ({ AppState: mockAppState })); + const { loadAppState } = require('../src/moduleLoaders/loadAppState'); + expect(loadAppState()).toBe(mockAppState); + }); + }); + + describe('loadAsyncStorage', () => { + it('should return AsyncStorage module when available', () => { + const mockAsyncStorage = { getItem: jest.fn() }; + jest.doMock('@react-native-async-storage/async-storage', () => ({ + default: mockAsyncStorage, + })); + + const { + loadAsyncStorage, + } = require('../src/moduleLoaders/loadAsyncStorage'); + expect(loadAsyncStorage()).toBe(mockAsyncStorage); + }); + + it('should throw error when AsyncStorage is not available', () => { + jest.doMock('@react-native-async-storage/async-storage', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { + loadAsyncStorage, + } = require('../src/moduleLoaders/loadAsyncStorage'); + expect(() => loadAsyncStorage()).toThrow( + '@react-native-async-storage/async-storage', + ); + }); + + it('should throw error when AsyncStorage default is null', () => { + jest.doMock('@react-native-async-storage/async-storage', () => ({ + default: null, + })); + + const { + loadAsyncStorage, + } = require('../src/moduleLoaders/loadAsyncStorage'); + expect(() => loadAsyncStorage()).toThrow( + 'Ensure `@react-native-async-storage/async-storage` is installed and linked.', + ); + }); + }); + + describe('loadNetInfo', () => { + it('should return NetInfo module when available', () => { + const mockNetInfo = { fetch: jest.fn() }; + jest.doMock('@react-native-community/netinfo', () => ({ + default: mockNetInfo, + })); + + const { loadNetInfo } = require('../src/moduleLoaders/loadNetInfo'); + expect(loadNetInfo()).toBe(mockNetInfo); + }); + + it('should throw error when NetInfo is not available', () => { + jest.doMock('@react-native-community/netinfo', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { loadNetInfo } = require('../src/moduleLoaders/loadNetInfo'); + expect(() => loadNetInfo()).toThrow('@react-native-community/netinfo'); + }); + + it('should throw error when NetInfo default is null', () => { + jest.doMock('@react-native-community/netinfo', () => ({ + default: null, + })); + + const { loadNetInfo } = require('../src/moduleLoaders/loadNetInfo'); + expect(() => loadNetInfo()).toThrow( + 'Ensure `@react-native-community/netinfo` is installed and linked.', + ); + }); + }); + + describe('loadAmplifyPushNotification', () => { + it('should return push notification module when available', () => { + const mockModule = { initialize: jest.fn() }; + jest.doMock('@aws-amplify/rtn-push-notification', () => ({ + module: mockModule, + })); + + const { + loadAmplifyPushNotification, + } = require('../src/moduleLoaders/loadAmplifyPushNotification'); + expect(loadAmplifyPushNotification()).toBe(mockModule); + }); + + it('should throw error when push notification module is not available', () => { + jest.doMock('@aws-amplify/rtn-push-notification', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { + loadAmplifyPushNotification, + } = require('../src/moduleLoaders/loadAmplifyPushNotification'); + expect(() => loadAmplifyPushNotification()).toThrow( + '@aws-amplify/rtn-push-notification', + ); + }); + + it('should throw error when push notification module is null', () => { + jest.doMock('@aws-amplify/rtn-push-notification', () => ({ + module: null, + })); + + const { + loadAmplifyPushNotification, + } = require('../src/moduleLoaders/loadAmplifyPushNotification'); + expect(() => loadAmplifyPushNotification()).toThrow( + 'Ensure `@aws-amplify/rtn-push-notification` is installed and linked.', + ); + }); + }); + + describe('loadAmplifyWebBrowser', () => { + it('should return web browser module when available', () => { + const mockModule = { openAuthSessionAsync: jest.fn() }; + jest.doMock('@aws-amplify/rtn-web-browser', () => ({ + module: mockModule, + })); + + const { + loadAmplifyWebBrowser, + } = require('../src/moduleLoaders/loadAmplifyWebBrowser'); + expect(loadAmplifyWebBrowser()).toBe(mockModule); + }); + + it('should throw error when web browser module is not available', () => { + jest.doMock('@aws-amplify/rtn-web-browser', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { + loadAmplifyWebBrowser, + } = require('../src/moduleLoaders/loadAmplifyWebBrowser'); + expect(() => loadAmplifyWebBrowser()).toThrow( + '@aws-amplify/rtn-web-browser', + ); + }); + + it('should throw error when web browser module is null', () => { + jest.doMock('@aws-amplify/rtn-web-browser', () => ({ + module: null, + })); + + const { + loadAmplifyWebBrowser, + } = require('../src/moduleLoaders/loadAmplifyWebBrowser'); + expect(() => loadAmplifyWebBrowser()).toThrow( + 'Ensure `@aws-amplify/rtn-web-browser` is installed and linked.', + ); + }); + }); + + describe('loadUrlPolyfill', () => { + it('should require react-native-url-polyfill/auto successfully', () => { + jest.doMock('react-native-url-polyfill/auto', () => ({})); + + const { + loadUrlPolyfill, + } = require('../src/moduleLoaders/loadUrlPolyfill'); + expect(() => loadUrlPolyfill()).not.toThrow(); + }); + + it('should throw error when url polyfill is not available', () => { + jest.doMock('react-native-url-polyfill/auto', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { + loadUrlPolyfill, + } = require('../src/moduleLoaders/loadUrlPolyfill'); + expect(() => loadUrlPolyfill()).toThrow('react-native-url-polyfill'); + }); + }); + + describe('loadGetRandomValues', () => { + it('should require react-native-get-random-values successfully', () => { + jest.doMock('react-native-get-random-values', () => ({})); + + const { + loadGetRandomValues, + } = require('../src/moduleLoaders/loadGetRandomValues'); + expect(() => loadGetRandomValues()).not.toThrow(); + }); + + it('should throw error when get random values is not available', () => { + jest.doMock('react-native-get-random-values', () => { + throw new Error('Cannot resolve module undefined'); + }); + + const { + loadGetRandomValues, + } = require('../src/moduleLoaders/loadGetRandomValues'); + expect(() => loadGetRandomValues()).toThrow( + 'react-native-get-random-values', + ); + }); + }); +}); diff --git a/packages/react-native/__tests__/nativeModule.test.ts b/packages/react-native/__tests__/nativeModule.test.ts new file mode 100644 index 00000000000..f819336f265 --- /dev/null +++ b/packages/react-native/__tests__/nativeModule.test.ts @@ -0,0 +1,57 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +describe('nativeModule', () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + it('should return NativeModules.AmplifyRTNCore when available', () => { + const mockModule = { computeModPow: jest.fn() }; + jest.doMock('react-native', () => ({ + NativeModules: { AmplifyRTNCore: mockModule }, + Platform: { select: jest.fn() }, + })); + + const { nativeModule } = require('../src/nativeModule'); + expect(nativeModule).toBe(mockModule); + }); + + it('should return NativeModules.AmplifyRTNCore when it exists as empty object', () => { + const mockModule = {}; + jest.doMock('react-native', () => ({ + NativeModules: { AmplifyRTNCore: mockModule }, + Platform: { select: jest.fn() }, + })); + + const { nativeModule } = require('../src/nativeModule'); + expect(nativeModule).toBe(mockModule); + }); + + it('should return proxy that throws error when AmplifyRTNCore is not available', () => { + jest.doMock('react-native', () => ({ + NativeModules: {}, + Platform: { select: () => "- You have run 'pod install'\n" }, + })); + + const { nativeModule } = require('../src/nativeModule'); + + expect(() => nativeModule.computeModPow()).toThrow( + "The package '@aws-amplify/react-native' doesn't seem to be linked.", + ); + }); + + it('should handle Platform.select returning empty string', () => { + jest.doMock('react-native', () => ({ + NativeModules: {}, + Platform: { select: () => '' }, + })); + + const { nativeModule } = require('../src/nativeModule'); + + expect(() => nativeModule.anyMethod()).toThrow( + "The package '@aws-amplify/react-native' doesn't seem to be linked.", + ); + }); +}); diff --git a/packages/react-native/package.json b/packages/react-native/package.json index ad3616afb7b..a88fb07c984 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -13,7 +13,7 @@ "scripts": { "prepare:ios": "cd example && npx pod-install", "prepare:android": "echo 'no-op'", - "test": "echo 'no-op'", + "test": "jest", "test:ios": "echo 'no-op'", "test:android": "cd ./example/android && ./gradlew test -i", "build-with-test": "npm run clean && npm test && tsc",