Skip to content

Commit dc9a8dd

Browse files
committed
adds the ability to run example app tests against d14n testnet staging
1 parent 1725e55 commit dc9a8dd

8 files changed

Lines changed: 162 additions & 9 deletions

File tree

example/EXAMPLE.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
TEST_PRIVATE_KEY=INSERT_TEST_PRIVATE_KEY_HERE
22
THIRD_WEB_CLIENT_ID=INSERT_CLIENT_ID_HERE
3+
GATEWAY_HOST=D14N_GATEWAY_PAYER_HOST_URL_HERE

example/app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"cameraPermission": "The app accesses your camera to let you attach photos as messages."
2323
}
2424
],
25-
"expo-secure-store"
25+
"expo-secure-store",
26+
"./plugins/withReactNativeConfigEnv.js"
2627
],
2728
"ios": {
2829
"supportsTablet": true,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Expo config plugin: injects react-native-config's dotenv.gradle into the Android app build
3+
* so that .env is loaded into BuildConfig and Config.* works on Android (same as iOS).
4+
* Without this, env vars work on iOS but not Android after prebuild.
5+
*
6+
* @see https://github.com/luggit/react-native-config#android
7+
*/
8+
const { withAppBuildGradle } = require('expo/config-plugins')
9+
10+
const DOTENV_LINE =
11+
'apply from: project.file("../../node_modules/react-native-config/android/dotenv.gradle")'
12+
const COMMENT =
13+
'// Load .env into BuildConfig so react-native-config Config.* works on Android (required for env vars in JS)'
14+
15+
function withReactNativeConfigEnv(config) {
16+
return withAppBuildGradle(config, (config) => {
17+
const contents = config.modResults.contents
18+
if (contents.includes('react-native-config/android/dotenv.gradle')) {
19+
return config
20+
}
21+
const afterPlugins = contents.indexOf('apply plugin: "com.facebook.react"')
22+
if (afterPlugins === -1) {
23+
return config
24+
}
25+
const insertAt = contents.indexOf('\n', afterPlugins) + 1
26+
const before = contents.slice(0, insertAt)
27+
const after = contents.slice(insertAt)
28+
config.modResults.contents =
29+
before + '\n' + COMMENT + '\n' + DOTENV_LINE + '\n\n' + after
30+
return config
31+
})
32+
}
33+
34+
module.exports = withReactNativeConfigEnv

example/src/LaunchScreen.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NativeStackScreenProps } from '@react-navigation/native-stack'
22
// import { ConnectWallet, useSigner } from '@thirdweb-dev/react-native'
3-
import React, { useCallback, useState } from 'react'
3+
import React, { useCallback, useEffect, useState } from 'react'
44
import {
55
Button,
66
ScrollView,
@@ -18,6 +18,7 @@ import { NavigationParamList } from './Navigation'
1818
import { TestCategory } from './TestScreen'
1919
import { supportedCodecs } from './contentTypes/contentTypes'
2020
import { getDbEncryptionKey } from './hooks'
21+
import { setTestEnv, type TestEnvOption } from './testEnv'
2122

2223
// Custom Modal Picker Component
2324
const CustomPicker = ({
@@ -105,9 +106,15 @@ export default function LaunchScreen(
105106
const [selectedTest, setSelectedTest] = useState<TestCategory>(
106107
TestCategory.all
107108
)
109+
const [selectedTestEnv, setSelectedTestEnv] = useState<TestEnvOption>('local')
108110
const [selectedNetwork, setSelectedNetwork] = useState<
109111
'dev' | 'local' | 'production'
110112
>('dev')
113+
114+
useEffect(() => {
115+
setTestEnv(selectedTestEnv)
116+
}, [selectedTestEnv])
117+
111118
// const signer = useSigner()
112119
// const [signerAddressDisplay, setSignerAddressDisplay] = useState<string>()
113120
const { setClient } = useXmtp()
@@ -170,6 +177,12 @@ export default function LaunchScreen(
170177
label,
171178
}))
172179

180+
const testEnvOptions: { id: TestEnvOption; label: string }[] = [
181+
{ id: 'local', label: 'local' },
182+
{ id: 'dev', label: 'dev' },
183+
{ id: 'd14n', label: 'd14n (dev + gateway)' },
184+
]
185+
173186
return (
174187
<ScrollView>
175188
<Text style={styles.title}>Automated Tests</Text>
@@ -182,6 +195,23 @@ export default function LaunchScreen(
182195
placeholder="Select Test"
183196
/>
184197
</View>
198+
<View style={styles.row}>
199+
<Text style={styles.label}>Test environment:</Text>
200+
<CustomPicker
201+
value={
202+
testEnvOptions.find((o) => o.id === selectedTestEnv)?.label ??
203+
selectedTestEnv
204+
}
205+
onValueChange={(value) => {
206+
const option = testEnvOptions.find((o) => o.label === value)
207+
const env = (option?.id ?? value) as TestEnvOption
208+
setSelectedTestEnv(env)
209+
setTestEnv(env)
210+
}}
211+
options={testEnvOptions}
212+
placeholder="Test environment"
213+
/>
214+
</View>
185215
<View key="run-tests" style={{ margin: 16 }}>
186216
<Button
187217
title={`Run Selected Tests: ${selectedTest}`}

example/src/testEnv.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Test environment selection for createClients() when no env is passed.
3+
* Set from LaunchScreen; read from test-utils. Default is 'local'.
4+
* - local: XMTP local network
5+
* - dev: XMTP dev network
6+
* - d14n: dev network with custom gateway (uses GATEWAY_HOST from .env)
7+
*/
8+
export type TestEnvOption = 'local' | 'dev' | 'd14n'
9+
10+
let testEnv: TestEnvOption = 'local'
11+
12+
export function getTestEnv(): TestEnvOption {
13+
return testEnv
14+
}
15+
16+
export function setTestEnv(value: TestEnvOption): void {
17+
testEnv = value
18+
}

example/src/tests/clientTests.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ethers, Wallet } from 'ethers'
2+
import Config from 'react-native-config'
23
import RNFS from 'react-native-fs'
34
import { ArchiveOptions } from 'xmtp-react-native-sdk/lib/ArchiveOptions'
4-
import { InstallationId } from 'xmtp-react-native-sdk/lib/Client'
5+
import { InstallationId, XMTPEnvironment } from 'xmtp-react-native-sdk/lib/Client'
56

67
import {
78
Test,
@@ -1330,3 +1331,46 @@ test('can import archive on top of full database', async () => {
13301331

13311332
return true
13321333
})
1334+
1335+
test('can make a new client and build existing from existing db using d14n staging testnet', async () => {
1336+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1337+
const keyBytes = new Uint8Array([
1338+
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
1339+
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145,
1340+
])
1341+
const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db`
1342+
const directoryExists = await RNFS.exists(dbDirPath)
1343+
if (!directoryExists) {
1344+
await RNFS.mkdir(dbDirPath)
1345+
}
1346+
1347+
const options = {
1348+
env: 'dev' as XMTPEnvironment,
1349+
dbEncryptionKey: keyBytes,
1350+
dbDirectory: dbDirPath,
1351+
deviceSyncEnabled: false,
1352+
appVersion: '0.0.0',
1353+
gatewayHost: Config.GATEWAY_HOST
1354+
}
1355+
1356+
const client = await Client.createRandom(options)
1357+
1358+
const inboxId = await Client.getOrCreateInboxId(
1359+
client.publicIdentity,
1360+
'dev'
1361+
)
1362+
1363+
assert(
1364+
client.inboxId === inboxId,
1365+
`inboxIds should match but were ${client.inboxId} and ${inboxId}`
1366+
)
1367+
1368+
const clientFromBundle = await Client.build(client.publicIdentity, options)
1369+
1370+
assert(
1371+
clientFromBundle.inboxId === client.inboxId,
1372+
`inboxIds should match but were ${clientFromBundle.inboxId} and ${client.inboxId}`
1373+
)
1374+
1375+
return true
1376+
})

example/src/tests/test-utils.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Wallet } from 'ethers'
22
import { Platform } from 'expo-modules-core'
3+
import Config from 'react-native-config'
34
import {
45
Client,
56
GroupUpdatedCodec,
@@ -17,6 +18,8 @@ import {
1718
ReplyCodec,
1819
} from 'xmtp-react-native-sdk'
1920

21+
import { getTestEnv } from '../testEnv'
22+
2023
// Debug logging state
2124
let debugLoggingEnabled = false
2225

@@ -59,16 +62,37 @@ export async function createClients(
5962
env?: XMTPEnvironment | undefined,
6063
customCodecs?: JSContentCodec<any>[]
6164
): Promise<Client[]> {
65+
const keyBytes = new Uint8Array([
66+
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
67+
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145,
68+
])
69+
70+
let resolvedEnv: XMTPEnvironment
71+
let gatewayHost: string | undefined
72+
if (env !== undefined) {
73+
resolvedEnv = env
74+
} else {
75+
const testEnvOption = getTestEnv()
76+
if (testEnvOption === 'd14n') {
77+
debugLog('Using d14n test environment')
78+
resolvedEnv = 'dev'
79+
gatewayHost = Config.GATEWAY_HOST
80+
if (!gatewayHost) {
81+
throw new Error(
82+
'Test env is "d14n" but GATEWAY_HOST is not set. Copy EXAMPLE.env to .env, set GATEWAY_HOST, and rebuild the app.'
83+
)
84+
}
85+
} else {
86+
resolvedEnv = testEnvOption
87+
}
88+
}
89+
6290
const clients = []
6391
for (let i = 0; i < numClients; i++) {
64-
const keyBytes = new Uint8Array([
65-
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
66-
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135,
67-
145,
68-
])
6992
const client = await Client.createRandom({
70-
env: env ?? 'local',
93+
env: resolvedEnv,
7194
dbEncryptionKey: keyBytes,
95+
...(gatewayHost !== undefined && { gatewayHost }),
7296
})
7397
Client.register(new GroupUpdatedCodec())
7498
Client.register(new RemoteAttachmentCodec())

example/src/types/react-native-config.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ declare module 'react-native-config' {
33
THIRD_WEB_CLIENT_ID?: string
44
TEST_PRIVATE_KEY?: string
55
TEST_V3_PRIVATE_KEY?: string
6+
GATEWAY_HOST?: string
67
}
78

89
export const Config: NativeConfig

0 commit comments

Comments
 (0)