From 0e63b09814378bab9d216a84c77eeebc5b252808 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Sat, 30 Nov 2024 00:25:31 +0600 Subject: [PATCH 01/10] [FSSDK-10936] async storage dynamic import --- .../reactNativeAsyncStorageCache.ts | 10 +++---- .../config_manager_factory.react_native.ts | 2 -- lib/project_config/config_manager_factory.ts | 3 +- .../cache/async_storage_cache.react_native.ts | 14 ++++----- .../async-storage.ts | 30 +++++++++++++++++++ 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 lib/utils/import.react_native/@react-native-async-storage/async-storage.ts diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts index 80930cfb6..d0d52f653 100644 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts @@ -14,27 +14,27 @@ * limitations under the License. */ -import AsyncStorage from '@react-native-async-storage/async-storage'; import PersistentKeyValueCache from './persistentKeyValueCache'; +import { asyncStorage } from '../../utils/import.react_native/@react-native-async-storage/async-storage'; export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { async contains(key: string): Promise { - return await AsyncStorage.getItem(key) !== null; + return (await asyncStorage.getItem(key)) !== null; } async get(key: string): Promise { - return (await AsyncStorage.getItem(key) || undefined); + return (await asyncStorage.getItem(key)) || undefined; } async remove(key: string): Promise { if (await this.contains(key)) { - await AsyncStorage.removeItem(key); + await asyncStorage.removeItem(key); return true; } return false; } set(key: string, val: string): Promise { - return AsyncStorage.setItem(key, val); + return asyncStorage.setItem(key, val); } } diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 6978ac61e..582e530f5 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -17,13 +17,11 @@ import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; import { ProjectConfigManager } from "./project_config_manager"; -import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { const defaultConfig = { autoUpdate: true, requestHandler: new BrowserRequestHandler(), - cache: new ReactNativeAsyncStorageCache(), }; return getPollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 4d1977663..8b8914b02 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -22,6 +22,7 @@ import { PollingDatafileManager } from "./polling_datafile_manager"; import PersistentKeyValueCache from "../plugins/key_value_cache/persistentKeyValueCache"; import { DEFAULT_UPDATE_INTERVAL } from './constant'; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; export type StaticConfigManagerConfig = { datafile: string, @@ -62,7 +63,7 @@ export const getPollingConfigManager = ( urlTemplate: opt.urlTemplate, datafileAccessToken: opt.datafileAccessToken, requestHandler: opt.requestHandler, - cache: opt.cache, + cache: opt.cache || new ReactNativeAsyncStorageCache(), repeater, }; diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts index 529287a6c..e674f1b15 100644 --- a/lib/utils/cache/async_storage_cache.react_native.ts +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -16,34 +16,34 @@ import { Maybe } from "../type"; import { AsyncCache } from "./cache"; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { asyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; export class AsyncStorageCache implements AsyncCache { public readonly operation = 'async'; async get(key: string): Promise { - const value = await AsyncStorage.getItem(key); + const value = await asyncStorage.getItem(key); return value ? JSON.parse(value) : undefined; } async remove(key: string): Promise { - return AsyncStorage.removeItem(key); + return asyncStorage.removeItem(key); } async set(key: string, val: V): Promise { - return AsyncStorage.setItem(key, JSON.stringify(val)); + return asyncStorage.setItem(key, JSON.stringify(val)); } async clear(): Promise { - return AsyncStorage.clear(); + return asyncStorage.clear(); } async getKeys(): Promise { - return [... await AsyncStorage.getAllKeys()]; + return [... await asyncStorage.getAllKeys()]; } async getBatched(keys: string[]): Promise[]> { - const items = await AsyncStorage.multiGet(keys); + const items = await asyncStorage.multiGet(keys); return items.map(([key, value]) => value ? JSON.parse(value) : undefined); } } diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts new file mode 100644 index 000000000..37a6be055 --- /dev/null +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' + +const requireAsyncStorage = () => { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('@react-native-async-storage/async-storage').default as AsyncStorageStatic; + } catch (e) { + throw new Error('@react-native-async-storage/async-storage is not available'); + } +}; + +const asyncStorage = requireAsyncStorage(); + +export { asyncStorage } From 2ada90313f5798704e205f14bbcb32a202f666d3 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:32:11 +0600 Subject: [PATCH 02/10] [FSSDK-10936] improvement --- .../reactNativeAsyncStorageCache.ts | 12 +++++++----- .../cache/async_storage_cache.react_native.ts | 15 ++++++++------- .../@react-native-async-storage/async-storage.ts | 9 +++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts index d0d52f653..275dcff0c 100644 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts @@ -15,26 +15,28 @@ */ import PersistentKeyValueCache from './persistentKeyValueCache'; -import { asyncStorage } from '../../utils/import.react_native/@react-native-async-storage/async-storage'; +import { getDefaultAsyncStorage } from '../../utils/import.react_native/@react-native-async-storage/async-storage'; export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { + private asyncStorage = getDefaultAsyncStorage(); + async contains(key: string): Promise { - return (await asyncStorage.getItem(key)) !== null; + return (await this.asyncStorage.getItem(key)) !== null; } async get(key: string): Promise { - return (await asyncStorage.getItem(key)) || undefined; + return (await this.asyncStorage.getItem(key)) || undefined; } async remove(key: string): Promise { if (await this.contains(key)) { - await asyncStorage.removeItem(key); + await this.asyncStorage.removeItem(key); return true; } return false; } set(key: string, val: string): Promise { - return asyncStorage.setItem(key, val); + return this.asyncStorage.setItem(key, val); } } diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts index e674f1b15..4656496d2 100644 --- a/lib/utils/cache/async_storage_cache.react_native.ts +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -16,34 +16,35 @@ import { Maybe } from "../type"; import { AsyncCache } from "./cache"; -import { asyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; +import { getDefaultAsyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; export class AsyncStorageCache implements AsyncCache { public readonly operation = 'async'; + private asyncStorage = getDefaultAsyncStorage(); async get(key: string): Promise { - const value = await asyncStorage.getItem(key); + const value = await this.asyncStorage.getItem(key); return value ? JSON.parse(value) : undefined; } async remove(key: string): Promise { - return asyncStorage.removeItem(key); + return this.asyncStorage.removeItem(key); } async set(key: string, val: V): Promise { - return asyncStorage.setItem(key, JSON.stringify(val)); + return this.asyncStorage.setItem(key, JSON.stringify(val)); } async clear(): Promise { - return asyncStorage.clear(); + return this.asyncStorage.clear(); } async getKeys(): Promise { - return [... await asyncStorage.getAllKeys()]; + return [... await this.asyncStorage.getAllKeys()]; } async getBatched(keys: string[]): Promise[]> { - const items = await asyncStorage.multiGet(keys); + const items = await this.asyncStorage.multiGet(keys); return items.map(([key, value]) => value ? JSON.parse(value) : undefined); } } diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts index 37a6be055..74dc0b92c 100644 --- a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -16,15 +16,12 @@ import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' -const requireAsyncStorage = () => { +export const getDefaultAsyncStorage = (): AsyncStorageStatic => { try { // eslint-disable-next-line @typescript-eslint/no-var-requires - return require('@react-native-async-storage/async-storage').default as AsyncStorageStatic; + return require('@react-native-async-storage/async-storage').default; } catch (e) { + // Better error message than unknown module not found throw new Error('@react-native-async-storage/async-storage is not available'); } }; - -const asyncStorage = requireAsyncStorage(); - -export { asyncStorage } From 170198117a12ff11d7ea8f7c3e053ded13fd500c Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:40:04 +0600 Subject: [PATCH 03/10] [FSSDK-10936] tab to space --- .../@react-native-async-storage/async-storage.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts index 74dc0b92c..f6a3e6cd8 100644 --- a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -17,11 +17,11 @@ import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' export const getDefaultAsyncStorage = (): AsyncStorageStatic => { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - return require('@react-native-async-storage/async-storage').default; - } catch (e) { - // Better error message than unknown module not found - throw new Error('@react-native-async-storage/async-storage is not available'); - } + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('@react-native-async-storage/async-storage').default; + } catch (e) { + // Better error message than unknown module not found + throw new Error('@react-native-async-storage/async-storage is not available'); + } }; From 347ac77cd661c0fca3d701ffacfd749745ba9dfa Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:04:15 +0600 Subject: [PATCH 04/10] [FSSDK-10936] test adjustment --- .../async-storage.ts | 80 +++++++++++-------- tests/reactNativeAsyncStorageCache.spec.ts | 33 +++----- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/__mocks__/@react-native-async-storage/async-storage.ts b/__mocks__/@react-native-async-storage/async-storage.ts index 2cbc7fd9a..4a670dcfd 100644 --- a/__mocks__/@react-native-async-storage/async-storage.ts +++ b/__mocks__/@react-native-async-storage/async-storage.ts @@ -14,50 +14,62 @@ * limitations under the License. */ - let items: {[key: string]: string} = {} export default class AsyncStorage { - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise { - return new Promise((resolve, reject) => { - switch (key) { - case 'keyThatExists': - resolve('{ "name": "Awesome Object" }') - break - case 'keyThatDoesNotExist': - resolve(null) - break - case 'keyWithInvalidJsonObject': - resolve('bad json }') - break - default: - setTimeout(() => resolve(items[key] || null), 1) - } - }) + private static items: Record = {}; + + static getItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise { + return new Promise((resolve) => { + setTimeout(() => { + const value = AsyncStorage.items[key] || null; + callback?.(undefined, value); + resolve(value); + }, 1); + }); } - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise { + static setItem( + key: string, + value: string, + callback?: (error?: Error) => void + ): Promise { return new Promise((resolve) => { setTimeout(() => { - items[key] = value - resolve() - }, 1) - }) + AsyncStorage.items[key] = value; + callback?.(undefined); + resolve(); + }, 1); + }); } - static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise { - return new Promise(resolve => { + static removeItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise { + return new Promise((resolve) => { setTimeout(() => { - items[key] && delete items[key] - // @ts-ignore - resolve() - }, 1) - }) + const value = AsyncStorage.items[key] || null; + if (key in AsyncStorage.items) { + delete AsyncStorage.items[key]; + } + callback?.(undefined, value); + resolve(value); + }, 1); + }); } - static dumpItems(): {[key: string]: string} { - return items + static dumpItems(): Record { + return { ...AsyncStorage.items }; // Return a copy for immutability } - - static clearStore(): void { - items = {} + + static clearStore(): Promise { + return new Promise((resolve) => { + setTimeout(() => { + AsyncStorage.items = {}; + resolve(); + }, 1); + }); } } diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts index a7d1a936e..2e49608e4 100644 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ b/tests/reactNativeAsyncStorageCache.spec.ts @@ -14,25 +14,24 @@ * limitations under the License. */ -import { describe, beforeEach, beforeAll, it, vi, expect } from 'vitest'; - -vi.mock('@react-native-async-storage/async-storage'); - +import { describe, beforeEach, it, vi, expect } from 'vitest'; import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; +import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; + +vi.mock('../lib/utils/import.react_native/@react-native-async-storage/async-storage', () => { + return { + getDefaultAsyncStorage: () => AsyncStorage, + }; +}); describe('ReactNativeAsyncStorageCache', () => { const TEST_OBJECT_KEY = 'testObject'; const testObject = { name: 'An object', with: { some: 2, properties: ['one', 'two'] } }; let cacheInstance: ReactNativeAsyncStorageCache; - beforeAll(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - }); - beforeEach(() => { - AsyncStorage.clearStore(); - AsyncStorage.setItem(TEST_OBJECT_KEY, JSON.stringify(testObject)); + cacheInstance = new ReactNativeAsyncStorageCache(); + cacheInstance.set(TEST_OBJECT_KEY, JSON.stringify(testObject)); }); describe('contains', () => { @@ -77,16 +76,4 @@ describe('ReactNativeAsyncStorageCache', () => { expect(wasSuccessful).toBe(false); }); }); - - describe('set', () => { - it('should resolve promise if item was successfully set in the cache', async () => { - const anotherTestStringValue = 'This should be found too.'; - - await cacheInstance.set('anotherTestStringValue', anotherTestStringValue); - - const itemsInReactAsyncStorage = AsyncStorage.dumpItems(); - expect(itemsInReactAsyncStorage['anotherTestStringValue']).toEqual(anotherTestStringValue); - expect(itemsInReactAsyncStorage[TEST_OBJECT_KEY]).toEqual(JSON.stringify(testObject)); - }); - }); }); From 07052b65fbf9939d4483f380a60ad11eb06c4db1 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:51:01 +0600 Subject: [PATCH 05/10] [FSSDK-10936] test adjustment --- .../async-storage.ts | 55 +++++--------- .../async_storage_cache.react_native.spec.ts | 71 +++++++++---------- 2 files changed, 50 insertions(+), 76 deletions(-) diff --git a/__mocks__/@react-native-async-storage/async-storage.ts b/__mocks__/@react-native-async-storage/async-storage.ts index 4a670dcfd..36d3cf85d 100644 --- a/__mocks__/@react-native-async-storage/async-storage.ts +++ b/__mocks__/@react-native-async-storage/async-storage.ts @@ -21,55 +21,36 @@ export default class AsyncStorage { key: string, callback?: (error?: Error, result?: string | null) => void ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - const value = AsyncStorage.items[key] || null; - callback?.(undefined, value); - resolve(value); - }, 1); - }); + const value = AsyncStorage.items[key] || null; + callback?.(undefined, value); + return Promise.resolve(value); } - + static setItem( key: string, value: string, callback?: (error?: Error) => void ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - AsyncStorage.items[key] = value; - callback?.(undefined); - resolve(); - }, 1); - }); + AsyncStorage.items[key] = value; + callback?.(undefined); + return Promise.resolve(); } - + static removeItem( key: string, callback?: (error?: Error, result?: string | null) => void ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - const value = AsyncStorage.items[key] || null; - if (key in AsyncStorage.items) { - delete AsyncStorage.items[key]; - } - callback?.(undefined, value); - resolve(value); - }, 1); - }); + const value = AsyncStorage.items[key] || null; + if (key in AsyncStorage.items) { + delete AsyncStorage.items[key]; + } + callback?.(undefined, value); + return Promise.resolve(value); } - - static dumpItems(): Record { - return { ...AsyncStorage.items }; // Return a copy for immutability - } - + static clearStore(): Promise { - return new Promise((resolve) => { - setTimeout(() => { - AsyncStorage.items = {}; - resolve(); - }, 1); - }); + AsyncStorage.items = {}; + return Promise.resolve(); } + } diff --git a/lib/utils/cache/async_storage_cache.react_native.spec.ts b/lib/utils/cache/async_storage_cache.react_native.spec.ts index d1a7954e4..1b6859ba2 100644 --- a/lib/utils/cache/async_storage_cache.react_native.spec.ts +++ b/lib/utils/cache/async_storage_cache.react_native.spec.ts @@ -1,4 +1,3 @@ - /** * Copyright 2022-2024, Optimizely * @@ -15,62 +14,45 @@ * limitations under the License. */ -vi.mock('@react-native-async-storage/async-storage', () => { - const MockAsyncStorage = { - data: new Map(), - async setItem(key: string, value: string) { - this.data.set(key, value); - }, - async getItem(key: string) { - return this.data.get(key) || null; - }, - async removeItem(key: string) { - this.data.delete(key); - }, - async getAllKeys() { - return Array.from(this.data.keys()); - }, - async clear() { - this.data.clear(); - }, - async multiGet(keys: string[]) { - return keys.map(key => [key, this.data.get(key)]); - }, - } - return { default: MockAsyncStorage }; -}); - -import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { vi, describe, it, expect } from 'vitest'; import { AsyncStorageCache } from './async_storage_cache.react_native'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import AsyncStorage from '../../../__mocks__/@react-native-async-storage/async-storage'; + +vi.mock('../lib/utils/import.react_native/@react-native-async-storage/async-storage', () => { + return { + getDefaultAsyncStorage: () => AsyncStorage, + }; +}); type TestData = { a: number; b: string; d: { e: boolean }; -} - +}; describe('AsyncStorageCache', () => { - beforeEach(async () => { - await AsyncStorage.clear(); - }); - - it('should store a stringified value in asyncstorage', async () => { + it('should store a stringified value in asyncstorag', async () => { const cache = new AsyncStorageCache(); + const data = { a: 1, b: '2', d: { e: true } }; await cache.set('key', data); - expect(await AsyncStorage.getItem('key')).toBe(JSON.stringify(data)); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(await cache.asyncStorage.getItem('key')).toBe(JSON.stringify(data)); + expect(await cache.get('key')).toEqual(data); }); it('should return undefined if get is called for a nonexistent key', async () => { const cache = new AsyncStorageCache(); + expect(await cache.get('nonexistent')).toBeUndefined(); }); it('should return the value if get is called for an existing key', async () => { const cache = new AsyncStorageCache(); await cache.set('key', 'value'); + expect(await cache.get('key')).toBe('value'); }); @@ -78,6 +60,7 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache(); const data = { a: 1, b: '2', d: { e: true } }; await cache.set('key', data); + expect(await cache.get('key')).toEqual(data); }); @@ -85,22 +68,31 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache(); await cache.set('key', 'value'); await cache.remove('key'); - expect(await AsyncStorage.getItem('key')).toBeNull(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(await cache.asyncStorage.getItem('key')).toBeNull(); }); it('should remove all keys from async storage when clear is called', async () => { const cache = new AsyncStorageCache(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); - expect((await AsyncStorage.getAllKeys()).length).toBe(2); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect((await cache.asyncStorage.getAllKeys()).length).toBe(2); cache.clear(); - expect((await AsyncStorage.getAllKeys()).length).toBe(0); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect((await cache.asyncStorage.getAllKeys()).length).toBe(0); }); it('should return all keys when getKeys is called', async () => { const cache = new AsyncStorageCache(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); + expect(await cache.getKeys()).toEqual(['key1', 'key2']); }); @@ -108,6 +100,7 @@ describe('AsyncStorageCache', () => { const cache = new AsyncStorageCache(); await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); + expect(await cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); }); }); From 794c9c7b3ed99f3e624069725b38783dde83880c Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 02:27:26 +0600 Subject: [PATCH 06/10] [FSSDK-10936] test addition --- ...onfig_manager_factory.react_native.spec.ts | 88 +++++++++++++++++-- .../async-storage.ts | 3 +- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index a01b36c11..c09142be0 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -16,6 +16,26 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; +await vi.hoisted(async () => { + await mockRequireNetInfo(); +}); + +let isAsyncStorageAvailable = true; + +async function mockRequireNetInfo() { + const { Module } = await import('module'); + const M: any = Module; + + M._load_original = M._load; + M._load = (uri: string, parent: string) => { + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return {}; + throw new Error('Module not found: @react-native-async-storage/async-storage'); + } + return M._load_original(uri, parent); + }; +} + vi.mock('./config_manager_factory', () => { return { getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), @@ -29,10 +49,10 @@ vi.mock('../utils/http_request_handler/browser_request_handler', () => { vi.mock('../plugins/key_value_cache/reactNativeAsyncStorageCache', () => { const ReactNativeAsyncStorageCache = vi.fn(); - return { 'default': ReactNativeAsyncStorageCache }; + return { default: ReactNativeAsyncStorageCache }; }); -import { getPollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; import { BrowserRequestHandler } from '../utils/http_request_handler/browser_request_handler'; import ReactNativeAsyncStorageCache from '../plugins/key_value_cache/reactNativeAsyncStorageCache'; @@ -63,7 +83,12 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + expect( + Object.is( + mockGetPollingConfigManager.mock.calls[0][0].requestHandler, + MockBrowserRequestHandler.mock.instances[0] + ) + ).toBe(true); }); it('uses uses autoUpdate = true by default', () => { @@ -81,7 +106,9 @@ describe('createPollingConfigManager', () => { }; const projectConfigManager = createPollingProjectConfigManager(config); - expect(Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0])).toBe(true); + expect( + Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0]) + ).toBe(true); }); it('uses the provided options', () => { @@ -98,5 +125,56 @@ describe('createPollingConfigManager', () => { const projectConfigManager = createPollingProjectConfigManager(config); expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); - }); + }); + + it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + const { getPollingConfigManager } = await vi.importActual( + './config_manager_factory' + ); + const { default: ReactNativeAsyncStorageCache } = await vi.importActual< + typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') + >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, + }; + + mockGetPollingConfigManager.mockImplementationOnce(() => { + return getPollingConfigManager(config); + }); + + MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { + return new ReactNativeAsyncStorageCache(); + }); + + expect(() => createPollingProjectConfigManager(config)).not.toThrow(); + }); + + it('should throw an error if cache is not present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + const { getPollingConfigManager } = await vi.importActual( + './config_manager_factory' + ); + const { default: ReactNativeAsyncStorageCache } = await vi.importActual< + typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') + >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + + mockGetPollingConfigManager.mockImplementationOnce(() => { + return getPollingConfigManager(config); + }); + + MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { + return new ReactNativeAsyncStorageCache(); + }); + + expect(() => createPollingProjectConfigManager(config)).toThrowError( + 'Module not found: @react-native-async-storage/async-storage' + ); + }); }); diff --git a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts index f6a3e6cd8..78deb7f2d 100644 --- a/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -21,7 +21,6 @@ export const getDefaultAsyncStorage = (): AsyncStorageStatic => { // eslint-disable-next-line @typescript-eslint/no-var-requires return require('@react-native-async-storage/async-storage').default; } catch (e) { - // Better error message than unknown module not found - throw new Error('@react-native-async-storage/async-storage is not available'); + throw new Error('Module not found: @react-native-async-storage/async-storage'); } }; From df273d1242c4186660cd5876ced89f46946b3098 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 02:28:39 +0600 Subject: [PATCH 07/10] [FSSDK-10936] name fix --- .../config_manager_factory.react_native.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index c09142be0..4c0505454 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -17,12 +17,12 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; await vi.hoisted(async () => { - await mockRequireNetInfo(); + await mockRequireAsyncStorage(); }); let isAsyncStorageAvailable = true; -async function mockRequireNetInfo() { +async function mockRequireAsyncStorage() { const { Module } = await import('module'); const M: any = Module; From 03e4bb4af9da52169c6eb329c7c396afc81279ac Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:51:25 +0600 Subject: [PATCH 08/10] [FSSDK-10936] test addition --- .../async-storage-event-processor.ts | 1 + ...ent_processor_factory.react_native.spec.ts | 103 +++++++++++++++--- ...onfig_manager_factory.react_native.spec.ts | 14 ++- 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/__mocks__/@react-native-async-storage/async-storage-event-processor.ts b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts index 1ba23231b..ad40f0152 100644 --- a/__mocks__/@react-native-async-storage/async-storage-event-processor.ts +++ b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts @@ -36,6 +36,7 @@ export default class AsyncStorage { return new Promise(resolve => { setTimeout(() => { items[key] && delete items[key] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore resolve() }, 1) diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 1ef075cd4..4ce182793 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -25,7 +25,7 @@ vi.mock('./forwarding_event_processor', () => { return { getForwardingEventProcessor }; }); -vi.mock('./event_processor_factory', async (importOriginal) => { +vi.mock('./event_processor_factory', async importOriginal => { const getBatchEventProcessor = vi.fn().mockImplementation(() => { return {}; }); @@ -46,13 +46,14 @@ vi.mock('@react-native-community/netinfo', () => { }); let isNetInfoAvailable = false; +let isAsyncStorageAvailable = true; await vi.hoisted(async () => { await mockRequireNetInfo(); }); async function mockRequireNetInfo() { - const {Module} = await import('module'); + const { Module } = await import('module'); const M: any = Module; M._load_original = M._load; @@ -61,6 +62,11 @@ async function mockRequireNetInfo() { if (isNetInfoAvailable) return {}; throw new Error('Module not found: @react-native-community/netinfo'); } + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return {}; + throw new Error('Module not found: @react-native-async-storage/async-storage'); + } + return M._load_original(uri, parent); }; } @@ -68,7 +74,7 @@ async function mockRequireNetInfo() { import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; import { getForwardingEventProcessor } from './forwarding_event_processor'; import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; -import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL, getPrefixEventStore } from './event_processor_factory'; import { getBatchEventProcessor } from './event_processor_factory'; import { AsyncCache, AsyncPrefixCache, SyncCache, SyncPrefixCache } from '../utils/cache/cache'; import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; @@ -96,7 +102,7 @@ describe('createForwardingEventProcessor', () => { it('uses the browser default event dispatcher if none is provided', () => { const processor = createForwardingEventProcessor(); - + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, defaultEventDispatcher); }); @@ -146,6 +152,78 @@ describe('createBatchEventProcessor', () => { expect(transformSet('value')).toBe('value'); }); + it('should throw error if @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const { getBatchEventProcessor } = await vi.importActual( + './event_processor_factory' + ); + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + mockGetBatchEventProcessor.mockImplementationOnce(() => { + return getBatchEventProcessor( + { + eventDispatcher: defaultEventDispatcher, + flushInterval: 1000, + batchSize: 10, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + }, + BatchEventProcessor + ); + }); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({})).toThrowError( + 'Module not found: @react-native-async-storage/async-storage' + ); + + isAsyncStorageAvailable = true; + }); + + it('should not throw error if eventStore is provided and @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const eventStore = { + operation: 'sync', + } as SyncCache; + const { getBatchEventProcessor } = await vi.importActual( + './event_processor_factory' + ); + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + mockGetBatchEventProcessor.mockImplementationOnce(() => { + return getBatchEventProcessor( + { + eventDispatcher: defaultEventDispatcher, + flushInterval: 1000, + batchSize: 10, + eventStore: getPrefixEventStore(eventStore), + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + }, + BatchEventProcessor + ); + }); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({ eventStore })).not.toThrow(); + + isAsyncStorageAvailable = true; + }); + it('wraps the provided eventStore in a SyncPrefixCache if a SyncCache is provided as eventStore', () => { const eventStore = { operation: 'sync', @@ -153,7 +231,7 @@ describe('createBatchEventProcessor', () => { const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockSyncPrefixCache.mock.calls[0]; @@ -172,7 +250,7 @@ describe('createBatchEventProcessor', () => { const processor = createBatchEventProcessor({ eventStore }); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); - + expect(mockGetBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixCache.mock.results[0].value); const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixCache.mock.calls[0]; @@ -184,7 +262,6 @@ describe('createBatchEventProcessor', () => { expect(transformSet({ value: 1 })).toBe('{"value":1}'); }); - it('uses the provided eventDispatcher', () => { const eventDispatcher = { dispatchEvent: vi.fn(), @@ -196,7 +273,7 @@ describe('createBatchEventProcessor', () => { }); it('uses the default browser event dispatcher if none is provided', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); }); @@ -210,7 +287,7 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); }); @@ -220,7 +297,7 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); }); @@ -230,19 +307,19 @@ describe('createBatchEventProcessor', () => { expect(Object.is(processor1, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); - const processor2 = createBatchEventProcessor({ }); + const processor2 = createBatchEventProcessor({}); expect(Object.is(processor2, mockGetBatchEventProcessor.mock.results[1].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); }); it('uses maxRetries value of 5', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); }); it('uses the default failedEventRetryInterval', () => { - const processor = createBatchEventProcessor({ }); + const processor = createBatchEventProcessor({}); expect(Object.is(processor, mockGetBatchEventProcessor.mock.results[0].value)).toBe(true); expect(mockGetBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); }); diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index 4c0505454..5c0e420a8 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -82,7 +82,8 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect( Object.is( mockGetPollingConfigManager.mock.calls[0][0].requestHandler, @@ -96,7 +97,8 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); }); @@ -105,7 +107,8 @@ describe('createPollingConfigManager', () => { sdkKey: 'sdkKey', }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect( Object.is(mockGetPollingConfigManager.mock.calls[0][0].cache, MockReactNativeAsyncStorageCache.mock.instances[0]) ).toBe(true); @@ -123,7 +126,8 @@ describe('createPollingConfigManager', () => { cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, }; - const projectConfigManager = createPollingProjectConfigManager(config); + createPollingProjectConfigManager(config); + expect(mockGetPollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); }); @@ -150,6 +154,7 @@ describe('createPollingConfigManager', () => { }); expect(() => createPollingProjectConfigManager(config)).not.toThrow(); + isAsyncStorageAvailable = true; }); it('should throw an error if cache is not present in the config, and async storage is not available', async () => { @@ -176,5 +181,6 @@ describe('createPollingConfigManager', () => { expect(() => createPollingProjectConfigManager(config)).toThrowError( 'Module not found: @react-native-async-storage/async-storage' ); + isAsyncStorageAvailable = true; }); }); From 960eaec7514362663e13a1981bb331664a93c6be Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:53:56 +0600 Subject: [PATCH 09/10] [FSSDK-10936] test adjustment --- ...ent_processor_factory.react_native.spec.ts | 38 +------------------ .../reactNativeAsyncStorageCache.ts | 2 +- ...onfig_manager_factory.react_native.spec.ts | 15 +------- .../config_manager_factory.react_native.ts | 3 ++ lib/project_config/config_manager_factory.ts | 3 +- 5 files changed, 7 insertions(+), 54 deletions(-) diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts index 4ce182793..18d066366 100644 --- a/lib/event_processor/event_processor_factory.react_native.spec.ts +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -154,28 +154,10 @@ describe('createBatchEventProcessor', () => { it('should throw error if @react-native-async-storage/async-storage is not available', async () => { isAsyncStorageAvailable = false; - const { getBatchEventProcessor } = await vi.importActual( - './event_processor_factory' - ); const { AsyncStorageCache } = await vi.importActual< typeof import('../utils/cache/async_storage_cache.react_native') >('../utils/cache/async_storage_cache.react_native'); - mockGetBatchEventProcessor.mockImplementationOnce(() => { - return getBatchEventProcessor( - { - eventDispatcher: defaultEventDispatcher, - flushInterval: 1000, - batchSize: 10, - retryOptions: { - maxRetries: 5, - }, - failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, - }, - BatchEventProcessor - ); - }); - MockAsyncStorageCache.mockImplementationOnce(() => { return new AsyncStorageCache(); }); @@ -192,29 +174,11 @@ describe('createBatchEventProcessor', () => { const eventStore = { operation: 'sync', } as SyncCache; - const { getBatchEventProcessor } = await vi.importActual( - './event_processor_factory' - ); + const { AsyncStorageCache } = await vi.importActual< typeof import('../utils/cache/async_storage_cache.react_native') >('../utils/cache/async_storage_cache.react_native'); - mockGetBatchEventProcessor.mockImplementationOnce(() => { - return getBatchEventProcessor( - { - eventDispatcher: defaultEventDispatcher, - flushInterval: 1000, - batchSize: 10, - eventStore: getPrefixEventStore(eventStore), - retryOptions: { - maxRetries: 5, - }, - failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, - }, - BatchEventProcessor - ); - }); - MockAsyncStorageCache.mockImplementationOnce(() => { return new AsyncStorageCache(); }); diff --git a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts index 275dcff0c..9529595be 100644 --- a/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ b/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts @@ -19,7 +19,7 @@ import { getDefaultAsyncStorage } from '../../utils/import.react_native/@react-n export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { private asyncStorage = getDefaultAsyncStorage(); - + async contains(key: string): Promise { return (await this.asyncStorage.getItem(key)) !== null; } diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts index 5c0e420a8..0ead808de 100644 --- a/lib/project_config/config_manager_factory.react_native.spec.ts +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -133,9 +133,6 @@ describe('createPollingConfigManager', () => { it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { isAsyncStorageAvailable = false; - const { getPollingConfigManager } = await vi.importActual( - './config_manager_factory' - ); const { default: ReactNativeAsyncStorageCache } = await vi.importActual< typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); @@ -145,10 +142,6 @@ describe('createPollingConfigManager', () => { cache: { get: vi.fn(), set: vi.fn(), contains: vi.fn(), remove: vi.fn() }, }; - mockGetPollingConfigManager.mockImplementationOnce(() => { - return getPollingConfigManager(config); - }); - MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { return new ReactNativeAsyncStorageCache(); }); @@ -159,9 +152,7 @@ describe('createPollingConfigManager', () => { it('should throw an error if cache is not present in the config, and async storage is not available', async () => { isAsyncStorageAvailable = false; - const { getPollingConfigManager } = await vi.importActual( - './config_manager_factory' - ); + const { default: ReactNativeAsyncStorageCache } = await vi.importActual< typeof import('../plugins/key_value_cache/reactNativeAsyncStorageCache') >('../plugins/key_value_cache/reactNativeAsyncStorageCache'); @@ -170,10 +161,6 @@ describe('createPollingConfigManager', () => { requestHandler: { makeRequest: vi.fn() }, }; - mockGetPollingConfigManager.mockImplementationOnce(() => { - return getPollingConfigManager(config); - }); - MockReactNativeAsyncStorageCache.mockImplementationOnce(() => { return new ReactNativeAsyncStorageCache(); }); diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts index 582e530f5..984f3c9d0 100644 --- a/lib/project_config/config_manager_factory.react_native.ts +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -17,11 +17,14 @@ import { getPollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; import { BrowserRequestHandler } from "../utils/http_request_handler/browser_request_handler"; import { ProjectConfigManager } from "./project_config_manager"; +import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): ProjectConfigManager => { const defaultConfig = { autoUpdate: true, requestHandler: new BrowserRequestHandler(), + cache: config.cache || new ReactNativeAsyncStorageCache() }; + return getPollingConfigManager({ ...defaultConfig, ...config }); }; diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts index 8b8914b02..4d1977663 100644 --- a/lib/project_config/config_manager_factory.ts +++ b/lib/project_config/config_manager_factory.ts @@ -22,7 +22,6 @@ import { PollingDatafileManager } from "./polling_datafile_manager"; import PersistentKeyValueCache from "../plugins/key_value_cache/persistentKeyValueCache"; import { DEFAULT_UPDATE_INTERVAL } from './constant'; import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; -import ReactNativeAsyncStorageCache from "../plugins/key_value_cache/reactNativeAsyncStorageCache"; export type StaticConfigManagerConfig = { datafile: string, @@ -63,7 +62,7 @@ export const getPollingConfigManager = ( urlTemplate: opt.urlTemplate, datafileAccessToken: opt.datafileAccessToken, requestHandler: opt.requestHandler, - cache: opt.cache || new ReactNativeAsyncStorageCache(), + cache: opt.cache, repeater, }; From 6192b13dadd6fc2520e7034e68c691abb0adcf73 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:02:29 +0600 Subject: [PATCH 10/10] [FSSDK-10936] test improvement --- .../async_storage_cache.react_native.spec.ts | 28 ++++++------------- tests/reactNativeAsyncStorageCache.spec.ts | 7 +---- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/lib/utils/cache/async_storage_cache.react_native.spec.ts b/lib/utils/cache/async_storage_cache.react_native.spec.ts index 1b6859ba2..f67fca7bf 100644 --- a/lib/utils/cache/async_storage_cache.react_native.spec.ts +++ b/lib/utils/cache/async_storage_cache.react_native.spec.ts @@ -16,13 +16,9 @@ import { vi, describe, it, expect } from 'vitest'; import { AsyncStorageCache } from './async_storage_cache.react_native'; -import AsyncStorage from '../../../__mocks__/@react-native-async-storage/async-storage'; +import { getDefaultAsyncStorage } from '../import.react_native/@react-native-async-storage/async-storage'; -vi.mock('../lib/utils/import.react_native/@react-native-async-storage/async-storage', () => { - return { - getDefaultAsyncStorage: () => AsyncStorage, - }; -}); +vi.mock('@react-native-async-storage/async-storage'); type TestData = { a: number; @@ -31,15 +27,15 @@ type TestData = { }; describe('AsyncStorageCache', () => { - it('should store a stringified value in asyncstorag', async () => { + const asyncStorage = getDefaultAsyncStorage(); + + it('should store a stringified value in async storage', async () => { const cache = new AsyncStorageCache(); const data = { a: 1, b: '2', d: { e: true } }; await cache.set('key', data); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(await cache.asyncStorage.getItem('key')).toBe(JSON.stringify(data)); + expect(await asyncStorage.getItem('key')).toBe(JSON.stringify(data)); expect(await cache.get('key')).toEqual(data); }); @@ -69,9 +65,7 @@ describe('AsyncStorageCache', () => { await cache.set('key', 'value'); await cache.remove('key'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(await cache.asyncStorage.getItem('key')).toBeNull(); + expect(await asyncStorage.getItem('key')).toBeNull(); }); it('should remove all keys from async storage when clear is called', async () => { @@ -79,13 +73,9 @@ describe('AsyncStorageCache', () => { await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect((await cache.asyncStorage.getAllKeys()).length).toBe(2); + expect((await asyncStorage.getAllKeys()).length).toBe(2); cache.clear(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect((await cache.asyncStorage.getAllKeys()).length).toBe(0); + expect((await asyncStorage.getAllKeys()).length).toBe(0); }); it('should return all keys when getKeys is called', async () => { diff --git a/tests/reactNativeAsyncStorageCache.spec.ts b/tests/reactNativeAsyncStorageCache.spec.ts index 2e49608e4..559c1f071 100644 --- a/tests/reactNativeAsyncStorageCache.spec.ts +++ b/tests/reactNativeAsyncStorageCache.spec.ts @@ -16,13 +16,8 @@ import { describe, beforeEach, it, vi, expect } from 'vitest'; import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; -vi.mock('../lib/utils/import.react_native/@react-native-async-storage/async-storage', () => { - return { - getDefaultAsyncStorage: () => AsyncStorage, - }; -}); +vi.mock('@react-native-async-storage/async-storage') describe('ReactNativeAsyncStorageCache', () => { const TEST_OBJECT_KEY = 'testObject';