Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ We use [changesets](https://github.com/changesets/changesets) to make it easier
- `lint` - runs linting on all JS/TS source files in the monorepo _[Turbo]_
- `gradle-plugin:lint` - runs linting on the Brownfield Gradle plugin source code
- `typecheck` - runs TypeScript type checking on all TS source files in the monorepo _[Turbo]_
- `test:apps` - runs Jest for the React Native example apps under `apps/` (Expo 54, Expo 55, plain RN) _[Turbo]_
- `build` - runs all `build*` tasks in the Turbo repo - see below for more details _[Turbo]_
- `dev` - runs all `dev` tasks in all workspaces
- `brownfield:plugin:publish:local` - publishes the Brownfield Gradle plugin to your local Maven repository for testing purposes
Expand All @@ -27,8 +28,30 @@ We use [changesets](https://github.com/changesets/changesets) to make it easier
- `build:example:android-rn` - builds the example React Native app for Android (`apps/RNApp/android`)
- `build:example:ios-rn` - builds the example React Native app for iOS (`apps/RNApp/ios`)
- `build:example:android-consumer:expo55` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the Expo 55 RN app (`apps/ExpoApp55`) artifact
- - `build:example:android-consumer:expo54` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the Expo 54 RN app (`apps/ExpoApp54`) artifact
- - `build:example:android-consumer:vanilla` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the vanilla RN app (`apps/RNApp`) artifact
- `build:example:android-consumer:expo54` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the Expo 54 RN app (`apps/ExpoApp54`) artifact
- `build:example:android-consumer:vanilla` - builds the example native Android consumer (`apps/AndroidApp`) app's flavor consuming the vanilla RN app (`apps/RNApp`) artifact
- `build:example:ios-consumer:expo55` - builds the example native iOS consumer app (`apps/AppleApp`) consuming the Expo 55 RN app (`apps/ExpoApp55`) artifact
- `build:example:ios-consumer:expo54` - builds the example native iOS consumer app (`apps/AppleApp`) consuming the Expo 54 RN app (`apps/ExpoApp54`) artifact
- `build:example:ios-consumer:vanilla` - builds the example native iOS consumer (`apps/AppleApp`) app's flavor consuming the vanilla RN app (`apps/RNApp`) artifact

## Example app tests

The React Native example apps share Jest utilities and test suites from `apps/brownfield-example-shared-tests`. Tests exercise integration with `@callstack/react-native-brownfield`, `@callstack/brownfield-navigation`, and `@callstack/brownie` as used in each demo.

From the repository root:

| Command | Description |
| --- | --- |
| `yarn test:apps` | Runs `test` in all workspaces under `apps/` that define it (via Turbo). |

Per example app (run from the repo root):

| Command | App |
| --- | --- |
| `yarn workspace @callstack/brownfield-example-rn-app test` | Plain React Native (`apps/RNApp`) |
| `yarn workspace @callstack/brownfield-example-expo-app-54 test` | Expo SDK 54 (`apps/ExpoApp54`) |
| `yarn workspace @callstack/brownfield-example-expo-app-55 test` | Expo SDK 55 (`apps/ExpoApp55`) |

Package-level scripts (`yarn test` inside `apps/RNApp`, `apps/ExpoApp54`, or `apps/ExpoApp55`) invoke Jest with each app’s `jest.config.js`.

The native-only sample apps (`apps/AppleApp`, `apps/AndroidApp`) use their platform test runners (Xcode / Gradle), not Jest.
12 changes: 12 additions & 0 deletions apps/ExpoApp54/__tests__/brownfield.example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PostMessageTab from '../app/(tabs)/postMessage';
import Counter from '../components/counter';
import RNApp from '../RNApp';
import {
runPostMessageTabSuite,
runCounterSuite,
runExpoRnAppSuite,
} from '@callstack/brownfield-example-shared-tests';

runPostMessageTabSuite('ExpoApp54', PostMessageTab);
runCounterSuite('ExpoApp54', Counter);
runExpoRnAppSuite('ExpoApp54', RNApp);
30 changes: 30 additions & 0 deletions apps/ExpoApp54/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const path = require('path');

module.exports = {
preset: 'jest-expo',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^react$': require.resolve('react'),
'^react/jsx-runtime$': require.resolve('react/jsx-runtime'),
'^react/jsx-dev-runtime$': require.resolve('react/jsx-dev-runtime'),
'^@testing-library/react-native$': require.resolve(
'@testing-library/react-native'
),
'^@/(.*)$': '<rootDir>/$1',
'^@callstack/react-native-brownfield$': path.join(
__dirname,
'../../packages/react-native-brownfield/src/index.ts'
),
'^@callstack/brownfield-navigation$': path.join(
__dirname,
'../../packages/brownfield-navigation/src/index.ts'
),
'^@callstack/brownie$': path.join(
__dirname,
'../../packages/brownie/src/index.ts'
),
},
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@callstack/brownfield-example-shared-tests|expo|@expo|expo-modules-core)/)',
],
};
1 change: 1 addition & 0 deletions apps/ExpoApp54/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@callstack/brownfield-example-shared-tests/jest/setup');
9 changes: 8 additions & 1 deletion apps/ExpoApp54/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"lint": "expo lint --no-cache",
"test": "jest --config jest.config.js",
"prebuild": "expo prebuild",
"brownfield:prepare:android:ci": "cd .. && node --experimental-strip-types --no-warnings ./scripts/prepare-android-build-gradle-for-ci.ts ExpoApp54",
"brownfield:package:android": "brownfield package:android --module-name brownfieldlib --variant release",
Expand Down Expand Up @@ -41,9 +42,15 @@
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@callstack/brownfield-example-shared-tests": "workspace:^",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
"@types/react": "~19.1.10",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
"jest": "^29.7.0",
"jest-expo": "~54.0.16",
"react-test-renderer": "19.1.0",
"typescript": "~5.9.3"
},
"brownie": {
Expand Down
12 changes: 12 additions & 0 deletions apps/ExpoApp55/__tests__/brownfield.example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PostMessageTab from '../src/app/postMessage';
import Counter from '../src/components/counter';
import RNApp from '../RNApp';
import {
runPostMessageTabSuite,
runCounterSuite,
runExpoRnAppSuite,
} from '@callstack/brownfield-example-shared-tests';

runPostMessageTabSuite('ExpoApp55', PostMessageTab);
runCounterSuite('ExpoApp55', Counter);
runExpoRnAppSuite('ExpoApp55', RNApp);
8 changes: 8 additions & 0 deletions apps/ExpoApp55/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
// https://docs.expo.dev/guides/using-eslint/
const { defineConfig } = require('eslint/config');
const expoConfig = require('eslint-config-expo/flat');
const globals = require('globals');

module.exports = defineConfig([
expoConfig,
{
ignores: ['dist/*'],
},
// Jest config is executed by Node (CommonJS); teach ESLint Node globals like __dirname.
{
files: ['jest.config.js'],
languageOptions: {
globals: globals.node,
},
},
]);
33 changes: 33 additions & 0 deletions apps/ExpoApp55/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const path = require('path');

module.exports = {
preset: 'jest-expo',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^react$': require.resolve('react'),
'^react/jsx-runtime$': require.resolve('react/jsx-runtime'),
'^react/jsx-dev-runtime$': require.resolve('react/jsx-dev-runtime'),
'^@testing-library/react-native$': require.resolve(
'@testing-library/react-native'
),
// Match before `@/` so `@/global.css` is stubbed (Expo web styling).
'^.+\\.css$': '<rootDir>/jest/cssMock.js',
'^@/(.*)$': '<rootDir>/src/$1',
'^@/assets/(.*)$': '<rootDir>/assets/$1',
'^@callstack/react-native-brownfield$': path.join(
__dirname,
'../../packages/react-native-brownfield/src/index.ts'
),
'^@callstack/brownfield-navigation$': path.join(
__dirname,
'../../packages/brownfield-navigation/src/index.ts'
),
'^@callstack/brownie$': path.join(
__dirname,
'../../packages/brownie/src/index.ts'
),
},
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@callstack/brownfield-example-shared-tests|expo|@expo|expo-modules-core)/)',
],
};
1 change: 1 addition & 0 deletions apps/ExpoApp55/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@callstack/brownfield-example-shared-tests/jest/setup');
2 changes: 2 additions & 0 deletions apps/ExpoApp55/jest/cssMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Stub CSS imports for Jest (used by Expo Router / bundler pipeline). */
module.exports = {};
9 changes: 8 additions & 1 deletion apps/ExpoApp55/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"lint": "expo lint --no-cache",
"test": "jest --config jest.config.js",
"prebuild": "expo prebuild",
"brownfield:prepare:android:ci": "cd .. && node --experimental-strip-types --no-warnings ./scripts/prepare-android-build-gradle-for-ci.ts ExpoApp55",
"brownfield:package:android": "brownfield package:android --module-name brownfieldlib --variant release",
Expand Down Expand Up @@ -46,9 +47,15 @@
"react-native-worklets": "0.7.2"
},
"devDependencies": {
"@callstack/brownfield-example-shared-tests": "workspace:^",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
"@types/react": "~19.2.10",
"eslint": "^9.25.0",
"eslint-config-expo": "~55.0.0",
"jest": "^29.7.0",
"jest-expo": "~55.0.0",
"react-test-renderer": "19.2.0",
"typescript": "~5.9.2"
},
"private": true,
Expand Down
3 changes: 1 addition & 2 deletions apps/RNApp/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
* @format
*/

import React from 'react';
import ReactTestRenderer from 'react-test-renderer';
import App from '../App';
import App from '../src/App';

test('renders correctly', async () => {
await ReactTestRenderer.act(() => {
Expand Down
12 changes: 12 additions & 0 deletions apps/RNApp/__tests__/brownfield.example.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
runCounterSuite,
runHomeScreenSuite,
} from '@callstack/brownfield-example-shared-tests';
import { HomeScreen } from '../src/HomeScreen';
import Counter from '../src/components/counter';
import { getRandomTheme } from '../src/utils';

const theme = getRandomTheme();

runHomeScreenSuite('RNApp', HomeScreen);
runCounterSuite('RNApp', Counter, { colors: theme });
25 changes: 25 additions & 0 deletions apps/RNApp/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
const path = require('node:path');

module.exports = {
preset: '@react-native/jest-preset',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^react$': require.resolve('react'),
'^react/jsx-runtime$': require.resolve('react/jsx-runtime'),
'^react/jsx-dev-runtime$': require.resolve('react/jsx-dev-runtime'),
'^@testing-library/react-native$':
require.resolve('@testing-library/react-native'),
'^@callstack/react-native-brownfield$': path.join(
__dirname,
'../../packages/react-native-brownfield/src/index.ts'
),
'^@callstack/brownfield-navigation$': path.join(
__dirname,
'../../packages/brownfield-navigation/src/index.ts'
),
'^@callstack/brownie$': path.join(
__dirname,
'../../packages/brownie/src/index.ts'
),
},
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@callstack/brownfield-example-shared-tests|@react-navigation|react-native-screens|react-native-safe-area-context)/)',
],
};
1 change: 1 addition & 0 deletions apps/RNApp/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@callstack/brownfield-example-shared-tests/jest/setup');
4 changes: 3 additions & 1 deletion apps/RNApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"brownfield:package:ios": "brownfield package:ios --scheme BrownfieldLib --configuration Release",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"test": "jest --config jest.config.js",
"codegen": "brownfield codegen",
"codegen:navigation": "brownfield navigation:codegen brownfield.navigation.ts"
},
Expand All @@ -31,6 +31,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@callstack/brownfield-example-shared-tests": "workspace:^",
"@react-native-community/cli": "20.1.0",
"@react-native-community/cli-platform-android": "20.1.0",
"@react-native-community/cli-platform-ios": "20.1.0",
Expand All @@ -39,6 +40,7 @@
"@react-native/jest-preset": "0.85.0",
"@react-native/metro-config": "0.85.0",
"@react-native/typescript-config": "0.85.0",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
"@types/react": "^19.2.0",
"@types/react-test-renderer": "^19.1.0",
Expand Down
56 changes: 56 additions & 0 deletions apps/brownfield-example-shared-tests/jest/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Shared Jest setup for brownfield example apps. Include via setupFilesAfterEnv.
*
* @see https://jestjs.io/docs/configuration#setupfilesafterenv-array
*/

global.__brownieExampleCounter = 0;
global.__resetBrownieExampleStore = () => {
global.__brownieExampleCounter = 0;
};

jest.mock('@callstack/react-native-brownfield', () => ({
__esModule: true,
default: {
onMessage: jest.fn(() => ({ remove: jest.fn() })),
postMessage: jest.fn(),
setNativeBackGestureAndButtonEnabled: jest.fn(),
popToNative: jest.fn(),
},
}));

jest.mock('@callstack/brownfield-navigation', () => ({
__esModule: true,
default: {
navigateToSettings: jest.fn(),
navigateToReferrals: jest.fn(),
},
}));

jest.mock('@callstack/brownie', () => ({
__esModule: true,
/** Resolve React from the app running Jest (cwd), not from this package's nested deps. */
useStore(key, selector) {
const React = require(require.resolve('react', { paths: [process.cwd()] }));
const [, setRev] = React.useState(0);
const setState = React.useCallback(
(action) => {
const prev = { counter: global.__brownieExampleCounter };
const partial = typeof action === 'function' ? action(prev) : action;
if (
partial &&
typeof partial === 'object' &&
partial.counter !== undefined
) {
global.__brownieExampleCounter = partial.counter;
}
setRev((r) => r + 1);
},
[selector]
);
return [
selector({ counter: global.__brownieExampleCounter }),
setState,
];
},
}));
28 changes: 28 additions & 0 deletions apps/brownfield-example-shared-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@callstack/brownfield-example-shared-tests",
"version": "0.0.1",
"private": true,
"scripts": {
"test": "node -e \"process.exit(0)\""
},
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
"./jest/setup": "./jest/setup.js"
},
"peerDependencies": {
"@testing-library/react-native": ">=12",
"react": ">=18",
"react-native": ">=0.73",
"react-test-renderer": ">=18"
},
"devDependencies": {
"@callstack/brownfield-navigation": "workspace:^",
"@callstack/react-native-brownfield": "workspace:^",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^30.0.0",
"@types/react": "^19.1.1",
"typescript": "5.9.3"
}
}
7 changes: 7 additions & 0 deletions apps/brownfield-example-shared-tests/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable no-var */
declare global {
var __brownieExampleCounter: number;
var __resetBrownieExampleStore: () => void;
}

export {};
4 changes: 4 additions & 0 deletions apps/brownfield-example-shared-tests/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { runPostMessageTabSuite } from './suites/postMessageTab.suite';
export { runCounterSuite } from './suites/counter.suite';
export { runExpoRnAppSuite } from './suites/expoRnApp.suite';
export { runHomeScreenSuite } from './suites/homeScreen.suite';
Loading
Loading