From 9175b69ce3e9b8992e7b660c27f662adfc7020e4 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy <6444719+krizzu@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:54:08 +0100 Subject: [PATCH 1/3] mock --- packages/async-storage/package.json | 7 ++- .../src/jest/AsyncStorageMock.ts | 59 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 packages/async-storage/src/jest/AsyncStorageMock.ts diff --git a/packages/async-storage/package.json b/packages/async-storage/package.json index e0d2a092..e6147a90 100644 --- a/packages/async-storage/package.json +++ b/packages/async-storage/package.json @@ -10,7 +10,12 @@ "types": "./lib/typescript/index.d.ts", "default": "./lib/module/index.js" }, - "./package.json": "./package.json" + "./package.json": "./package.json", + "./jest": { + "source": "./src/jest/AsyncStorageMock.ts", + "types": "./lib/typescript/jest/AsyncStorageMock.d.ts", + "default": "./lib/module/jest/AsyncStorageMock.js" + } }, "scripts": { "prepare": "yarn build", diff --git a/packages/async-storage/src/jest/AsyncStorageMock.ts b/packages/async-storage/src/jest/AsyncStorageMock.ts new file mode 100644 index 00000000..70bc9ee6 --- /dev/null +++ b/packages/async-storage/src/jest/AsyncStorageMock.ts @@ -0,0 +1,59 @@ +import type { AsyncStorage } from "@react-native-async-storage/async-storage"; + +class AsyncStorageMemoryImpl implements AsyncStorage { + private store = new Map(); + + getItem = async (key: string): Promise => { + return this.store.get(key) ?? null; + }; + + setItem = async (key: string, value: string): Promise => { + this.store.set(key, value); + }; + + removeItem = async (key: string): Promise => { + this.store.delete(key); + }; + + getMany = async (keys: string[]): Promise> => { + return keys.reduce>((result, key) => { + result[key] = this.store.get(key) ?? null; + return result; + }, {}); + }; + + setMany = async (entries: Record): Promise => { + for (const [key, value] of Object.entries(entries)) { + this.store.set(key, value); + } + }; + + removeMany = async (keys: string[]): Promise => { + for (const key of keys) { + this.store.delete(key); + } + }; + + getAllKeys = async (): Promise => { + return Array.from(this.store.keys()); + }; + + clear = async (): Promise => { + this.store.clear(); + }; +} + +const inMemoryDbRegistry = new Map(); + +export function createAsyncStorage(databaseName: string): AsyncStorage { + if (!inMemoryDbRegistry.has(databaseName)) { + inMemoryDbRegistry.set(databaseName, new AsyncStorageMemoryImpl()); + } + return inMemoryDbRegistry.get(databaseName)!; +} + +export function clearAllMockStorages(): void { + inMemoryDbRegistry.clear(); +} + +export default createAsyncStorage("legacy"); From 19d779e657f1adbe8c8674713c7dac0e44aafa7f Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy <6444719+krizzu@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:30:10 +0100 Subject: [PATCH 2/3] docs --- docs/api/brownfield.md | 52 +-------------------------- docs/integrations/brownfield.md | 57 +++++++++++++++++++++++++++++ docs/integrations/jest.md | 63 +++++++++++++++++++++++++++++++++ mkdocs.yml | 2 ++ 4 files changed, 123 insertions(+), 51 deletions(-) create mode 100644 docs/integrations/brownfield.md create mode 100644 docs/integrations/jest.md diff --git a/docs/api/brownfield.md b/docs/api/brownfield.md index 536a0a1a..4f346368 100644 --- a/docs/api/brownfield.md +++ b/docs/api/brownfield.md @@ -1,51 +1 @@ -!!! info - - Brownfield integration is supported on **Android**, **iOS**, and **macOS**. - -`AsyncStorage` is built on a shared storage layer (`SharedStorage`) that can also be accessed directly from native -code. -This is especially useful in brownfield scenarios, where your app combines React Native and native code, allowing both -layers to read from and write to the same storage consistently. - -All platforms provide a thread-safe singleton registry called `StorageRegistry` to manage storage instances. - -### Android - -On Android, `StorageRegistry` is a public singleton, which is used to share `SharedStorage` instances with the native module. -Multiple calls with the same name return the same singleton instance, ensuring consistent access. - -```kotlin -import android.content.Context -import kotlinx.coroutines.runBlocking -import org.asyncstorage.shared_storage.Entry -import org.asyncstorage.shared_storage.SharedStorage -import org.asyncstorage.storage.StorageRegistry - -// access shared storage via StorageRegistry -val storage: SharedStorage = StorageRegistry.getStorage(ctx, "my-users") - -// runBlocking only for a demonstration -runBlocking { - storage.setValues(listOf(Entry("email", "john@example.com"))) - val values = storage.getValues(listOf("email")) - println("Stored email: ${values.firstOrNull()?.value}") -} -``` - -### iOS / macOS - -On iOS and macOS, the `StorageRegistry` singleton provides the same functionality in Swift and Objective-C. - -```swift -import AsyncStorage -import SharedAsyncStorage - -// access shared storage via StorageRegistry -let storage: SharedStorage = StorageRegistry.shared.getStorage(dbName: "my-users") - -Task { - storage.setValues([Entry(key: "email", value: "john@example.com")]) - let values = storage.getValues(keys: ["email"]) - print("Stored email: \(values.first?.value ?? "none")") -} -``` +Documentation moved to [integrations/brownfield](../integrations/brownfield.md) diff --git a/docs/integrations/brownfield.md b/docs/integrations/brownfield.md new file mode 100644 index 00000000..5372aef5 --- /dev/null +++ b/docs/integrations/brownfield.md @@ -0,0 +1,57 @@ +--- +title: Brownfield integration +--- + +# Brownfield integration + +!!! info + + Brownfield integration is supported on **Android**, **iOS**, and **macOS**. + +`AsyncStorage` is built on a shared storage layer (`SharedStorage`) that can also be accessed directly from native +code. +This is especially useful in brownfield scenarios, where your app combines React Native and native code, allowing both +layers to read from and write to the same storage consistently. + +All platforms provide a thread-safe singleton registry called `StorageRegistry` to manage storage instances. + +### Android + +On Android, `StorageRegistry` is a public singleton, which is used to share `SharedStorage` instances with the native module. +Multiple calls with the same name return the same singleton instance, ensuring consistent access. + +```kotlin +import android.content.Context +import kotlinx.coroutines.runBlocking +import org.asyncstorage.shared_storage.Entry +import org.asyncstorage.shared_storage.SharedStorage +import org.asyncstorage.storage.StorageRegistry + +// access shared storage via StorageRegistry +val storage: SharedStorage = StorageRegistry.getStorage(ctx, "my-users") + +// runBlocking only for a demonstration +runBlocking { + storage.setValues(listOf(Entry("email", "john@example.com"))) + val values = storage.getValues(listOf("email")) + println("Stored email: ${values.firstOrNull()?.value}") +} +``` + +### iOS / macOS + +On iOS and macOS, the `StorageRegistry` singleton provides the same functionality in Swift and Objective-C. + +```swift +import AsyncStorage +import SharedAsyncStorage + +// access shared storage via StorageRegistry +let storage: SharedStorage = StorageRegistry.shared.getStorage(dbName: "my-users") + +Task { + storage.setValues([Entry(key: "email", value: "john@example.com")]) + let values = storage.getValues(keys: ["email"]) + print("Stored email: \(values.first?.value ?? "none")") +} +``` diff --git a/docs/integrations/jest.md b/docs/integrations/jest.md new file mode 100644 index 00000000..9446ad23 --- /dev/null +++ b/docs/integrations/jest.md @@ -0,0 +1,63 @@ +--- +title: Jest integration +--- + +# Jest integration + +AsyncStorage requires a native module that is not available in a Jest environment. The package ships a built-in +in-memory mock at `@react-native-async-storage/async-storage/jest` that replaces the native implementation during +tests. + +## Setup + +### Transform configuration + +The package ships as ESM source, so Jest must be configured to transform it. Add it to the `transformIgnorePatterns` in +your Jest config: + +```js +transformIgnorePatterns: [ + 'node_modules/(?!@react-native-async-storage/)', +], +``` + +### Automatic mock file + +Create a manual mock file that Jest will pick up automatically for every test: + +``` +__mocks__/@react-native-async-storage/async-storage.js +``` + +With content: + +```js +module.exports = require("@react-native-async-storage/async-storage/jest"); +``` + +Jest resolves files under `__mocks__/` automatically when the module is imported, so no additional configuration is +needed. + +### Inline mock + +To mock the module for a specific test file, call `jest.mock` at the top of the file: + +```js +jest.mock("@react-native-async-storage/async-storage", () => + require("@react-native-async-storage/async-storage/jest") +); +``` + +## What the mock provides + +The mock is a full in-memory implementation of the `AsyncStorage` interface. Additionally, it exports +`clearAllMockStorages` to clear all in-memory storages. +Example usage: + +```ts +import {clearAllMockStorages} from "@react-native-async-storage/async-storage/jest"; + +beforeEach(() => { + clearAllMockStorages(); +}); +``` diff --git a/mkdocs.yml b/mkdocs.yml index b4a80f1f..f7a42bcf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,8 @@ nav: - "Brownfield integration": api/brownfield.md - Integrations: - Expo: integrations/expo.md + - Jest: integrations/jest.md + - Brownfield: integrations/brownfield.md - "Migration to v3": migration-to-3.md - FAQ: faq.md - Contributing: contributing.md From 797fb53cc62de5eb052c28f4b62fc46e55f9ee01 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy <6444719+krizzu@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:34:35 +0100 Subject: [PATCH 3/3] fix: format --- docs/integrations/jest.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/integrations/jest.md b/docs/integrations/jest.md index 9446ad23..7759c00b 100644 --- a/docs/integrations/jest.md +++ b/docs/integrations/jest.md @@ -44,7 +44,7 @@ To mock the module for a specific test file, call `jest.mock` at the top of the ```js jest.mock("@react-native-async-storage/async-storage", () => - require("@react-native-async-storage/async-storage/jest") + require("@react-native-async-storage/async-storage/jest") ); ``` @@ -55,9 +55,9 @@ The mock is a full in-memory implementation of the `AsyncStorage` interface. Add Example usage: ```ts -import {clearAllMockStorages} from "@react-native-async-storage/async-storage/jest"; +import { clearAllMockStorages } from "@react-native-async-storage/async-storage/jest"; beforeEach(() => { - clearAllMockStorages(); + clearAllMockStorages(); }); ```