diff --git a/apps/fs-experiment/App.tsx b/apps/fs-experiment/App.tsx index 50c6ae2..2c972b7 100644 --- a/apps/fs-experiment/App.tsx +++ b/apps/fs-experiment/App.tsx @@ -40,6 +40,7 @@ const SANDBOXED_SUBSTITUTIONS: Record = { function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark' const [useSubstitution, setUseSubstitution] = useState(false) + const [sandboxReady, setSandboxReady] = useState(false) const theme = { bg: isDarkMode ? '#000' : '#fff', @@ -98,7 +99,10 @@ function App(): React.JSX.Element { useSubstitution ? 'substitution-on' : 'substitution-off' } style={styles.switchRow} - onPress={() => setUseSubstitution(v => !v)}> + onPress={() => { + setSandboxReady(false) + setUseSubstitution(v => !v) + }}> Module substitution{' '} @@ -107,12 +111,18 @@ function App(): React.JSX.Element { { + setSandboxReady(false) + setUseSubstitution(v) + }} trackColor={{false: theme.border, true: theme.green}} /> + {sandboxReady && ( + + )} console.log('Host received from sandbox:', msg)} + onMessage={msg => { + const payload = typeof msg === 'string' ? JSON.parse(msg) : msg + if (payload?.cmd === 'ready') setSandboxReady(true) + console.log('Host received from sandbox:', msg) + }} onError={err => console.log('Host received error from sandbox:', err) } @@ -137,6 +151,10 @@ const styles = StyleSheet.create({ root: { flex: 1, }, + readyMarker: { + width: 0, + height: 0, + }, section: { borderBottomWidth: StyleSheet.hairlineWidth, }, diff --git a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx index b39c19d..d2b3cc9 100644 --- a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx +++ b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx @@ -8,6 +8,7 @@ import { expect, it, render, + waitFor, } from 'react-native-harness' import App from '../App' @@ -15,6 +16,7 @@ import App from '../App' const isIOS = Platform.OS === 'ios' const TEST_TIMEOUT = 60_000 const RENDER_TIMEOUT = 10_000 +const SANDBOX_READY_TIMEOUT = 90_000 async function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) @@ -26,7 +28,16 @@ async function setSubstitution(enabled: boolean) { await userEvent.press(await screen.findByTestId('substitution-switch')) await screen.findByAccessibilityLabel(wantLabel) - await sleep(3000) + // Toggling substitution restarts the sandbox — wait for it to be ready again + // instead of a fixed sleep + await waitFor( + () => { + if (!screen.queryByTestId('sandbox-ready')) { + throw new Error('Sandbox not ready after substitution change') + } + }, + {timeout: SANDBOX_READY_TIMEOUT} + ) } async function writeAndRead( @@ -85,8 +96,15 @@ describe('Substitution OFF', () => { beforeAll(async () => { blockCleanup = true await render(, {timeout: RENDER_TIMEOUT}) - await sleep(4000) - }, 30_000) + await waitFor( + () => { + if (!screen.queryByTestId('sandbox-ready')) { + throw new Error('Sandbox not ready') + } + }, + {timeout: SANDBOX_READY_TIMEOUT} + ) + }, SANDBOX_READY_TIMEOUT + RENDER_TIMEOUT) if (isIOS) { it( diff --git a/apps/fs-experiment/rn-harness.config.mjs b/apps/fs-experiment/rn-harness.config.mjs index 240080c..5aab348 100644 --- a/apps/fs-experiment/rn-harness.config.mjs +++ b/apps/fs-experiment/rn-harness.config.mjs @@ -33,6 +33,7 @@ const config = { forwardClientLogs: true, resetEnvironmentBetweenTestFiles: true, disableViewFlattening: true, + bridgeTimeout: 120_000, } export default config diff --git a/apps/fs-experiment/sandbox.js b/apps/fs-experiment/sandbox.js index e0bc3a3..b116f39 100644 --- a/apps/fs-experiment/sandbox.js +++ b/apps/fs-experiment/sandbox.js @@ -58,6 +58,9 @@ function installTestCommandHandler() { function SandboxApp(props) { useEffect(() => { installTestCommandHandler() + if (typeof globalThis.postMessage === 'function') { + globalThis.postMessage({cmd: 'ready'}) + } }, []) return