Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/expo-go/ios/Exponent/Kernel/Core/EXKernel.m
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ - (instancetype)init

[DevMenuManager.shared setDelegate:self];
[DevMenuManager shared].configuration.onboardingAppName = @"Expo Go";
[[DevMenuManager shared] setShowFloatingActionButton:YES];

// Register keyboard commands (e.g., Cmd+D) for simulator
[[EXKernelDevKeyCommands sharedInstance] registerDevCommands];
Expand Down
5 changes: 5 additions & 0 deletions apps/router-e2e/__e2e__/helmet/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Tabs } from 'expo-router';

export default function Layout() {
return <Tabs screenOptions={{ headerShown: true }} />;
}
36 changes: 36 additions & 0 deletions apps/router-e2e/__e2e__/helmet/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Link, router } from 'expo-router';
import Head from 'expo-router/head';
import { StyleSheet, View, Text, Pressable } from 'react-native';

export default function IndexScreen() {
return (
<View style={styles.container}>
<Head>
<title>Index</title>
</Head>
<Text testID="index-text">Index Page</Text>
<Link href="/page1" testID="index-link-page1">
Go to Page 1
</Link>
<Link href="/page2" testID="index-link-page2">
Go to Page 2
</Link>
<Pressable testID="index-button-page1" onPress={() => router.push('/page1')}>
<Text>Push Page 1</Text>
</Pressable>
<Pressable testID="index-button-page2" onPress={() => router.push('/page2')}>
<Text>Push Page 2</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
gap: 15,
},
});
36 changes: 36 additions & 0 deletions apps/router-e2e/__e2e__/helmet/app/page1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Link, router } from 'expo-router';
import Head from 'expo-router/head';
import { StyleSheet, View, Text, Pressable } from 'react-native';

export default function Page1Screen() {
return (
<View style={styles.container}>
<Head>
<title>Page 1</title>
</Head>
<Text testID="page1-text">Page 1</Text>
<Link href="/" testID="page1-link-index">
Go to Index
</Link>
<Link href="/page2" testID="page1-link-page2">
Go to Page 2
</Link>
<Pressable testID="page1-button-index" onPress={() => router.push('/')}>
<Text>Push Index</Text>
</Pressable>
<Pressable testID="page1-button-page2" onPress={() => router.push('/page2')}>
<Text>Push Page 2</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
gap: 15,
},
});
36 changes: 36 additions & 0 deletions apps/router-e2e/__e2e__/helmet/app/page2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Link, router } from 'expo-router';
import Head from 'expo-router/head';
import { StyleSheet, View, Text, Pressable } from 'react-native';

export default function Page2Screen() {
return (
<View style={styles.container}>
<Head>
<title>Page 2</title>
</Head>
<Text testID="page2-text">Page 2</Text>
<Link href="/" testID="page2-link-index">
Go to Index
</Link>
<Link href="/page1" testID="page2-link-page1">
Go to Page 1
</Link>
<Pressable testID="page2-button-index" onPress={() => router.push('/')}>
<Text>Push Index</Text>
</Pressable>
<Pressable testID="page2-button-page1" onPress={() => router.push('/page1')}>
<Text>Push Page 1</Text>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
gap: 15,
},
});
2 changes: 2 additions & 0 deletions apps/router-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"start:stack": "E2E_ROUTER_SRC=stack expo start",
"ios:stack": "E2E_ROUTER_SRC=stack expo run:ios",
"android:stack": "E2E_ROUTER_SRC=stack expo run:android",
"start:helmet": "E2E_ROUTER_SRC=helmet expo start",
"export:helmet": "E2E_ROUTER_SRC=helmet expo export -p web",
"lint": "expo lint __e2e__",
"android": "expo run:android",
"ios": "expo run:ios",
Expand Down
1 change: 1 addition & 0 deletions packages/@expo/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

- Fix RSC support in development ([#42617](https://github.com/expo/expo/pull/42617) by [@hassankhan](https://github.com/hassankhan))
- Fix loader URL resolution for nested `/index` paths ([#42629](https://github.com/expo/expo/pull/42629) by [@hassankhan](https://github.com/hassankhan))
- [web] Ensure `<Head>` component re-renders when focus changes ([#42681](https://github.com/expo/expo/pull/42681) by [@hassankhan](https://github.com/hassankhan))

### 💡 Others

Expand Down
108 changes: 108 additions & 0 deletions packages/@expo/cli/e2e/playwright/prod/helmet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { test, expect } from '@playwright/test';

import { clearEnv, restoreEnv } from '../../__tests__/export/export-side-effects';
import { getRouterE2ERoot } from '../../__tests__/utils';
import { createExpoServe, executeExpoAsync } from '../../utils/expo';
import { pageCollectErrors } from '../page';

test.beforeAll(() => clearEnv());
test.afterAll(() => restoreEnv());

const projectRoot = getRouterE2ERoot();
const outputDir = 'dist-helmet-playwright';

test.describe('Head component document title', () => {
const expoServe = createExpoServe({
cwd: projectRoot,
env: {
NODE_ENV: 'production',
},
});

test.beforeAll(async () => {
console.time('expo export');
await executeExpoAsync(projectRoot, ['export', '-p', 'web', '--output-dir', outputDir], {
env: {
NODE_ENV: 'production',
EXPO_USE_STATIC: 'static',
E2E_ROUTER_SRC: 'helmet',
},
});
console.timeEnd('expo export');

console.time('expo serve');
await expoServe.startAsync([outputDir]);
console.timeEnd('expo serve');
});

test.afterAll(async () => {
await expoServe.stopAsync();
});

test('updates document title when navigating between tabs', async ({ page }) => {
const pageErrors = pageCollectErrors(page);

await page.goto(expoServe.url.href);
await page.waitForSelector('[data-testid="index-text"]');

await expect(page).toHaveTitle('Index');

await page.getByRole('tab', { name: 'page1' }).click();
await page.waitForSelector('[data-testid="page1-text"]');
await expect(page).toHaveTitle('Page 1');

await page.getByRole('tab', { name: 'page2' }).click();
await page.waitForSelector('[data-testid="page2-text"]');
await expect(page).toHaveTitle('Page 2');

await page.getByRole('tab', { name: 'index' }).click();
await page.waitForSelector('[data-testid="index-text"]');
await expect(page).toHaveTitle('Index');

expect(pageErrors.all).toEqual([]);
});

test('updates document title when navigating via links', async ({ page }) => {
const pageErrors = pageCollectErrors(page);

await page.goto(expoServe.url.href);
await page.waitForSelector('[data-testid="index-text"]');
await expect(page).toHaveTitle('Index');

await page.getByTestId('index-link-page1').click();
await page.waitForSelector('[data-testid="page1-text"]');
await expect(page).toHaveTitle('Page 1');

await page.getByTestId('page1-link-page2').click();
await page.waitForSelector('[data-testid="page2-text"]');
await expect(page).toHaveTitle('Page 2');

await page.getByTestId('page2-link-index').click();
await page.waitForSelector('[data-testid="index-text"]');
await expect(page).toHaveTitle('Index');

expect(pageErrors.all).toEqual([]);
});

test('updates document title when navigating via router.push', async ({ page }) => {
const pageErrors = pageCollectErrors(page);

await page.goto(expoServe.url.href);
await page.waitForSelector('[data-testid="index-text"]');
await expect(page).toHaveTitle('Index');

await page.getByTestId('index-button-page1').click();
await page.waitForSelector('[data-testid="page1-text"]');
await expect(page).toHaveTitle('Page 1');

await page.getByTestId('page1-button-page2').click();
await page.waitForSelector('[data-testid="page2-text"]');
await expect(page).toHaveTitle('Page 2');

await page.getByTestId('page2-button-index').click();
await page.waitForSelector('[data-testid="index-text"]');
await expect(page).toHaveTitle('Index');

expect(pageErrors.all).toEqual([]);
});
});
1 change: 1 addition & 0 deletions packages/expo-dev-launcher/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### 🐛 Bug fixes

- [Android] Fix sometimes two dev menus would open. ([#42567](https://github.com/expo/expo/pull/42567) by [@lukmccall](https://github.com/lukmccall))
- [iOS] Fix color of placeholder text in URL input. ([#42677](https://github.com/expo/expo/pull/42677) by [@alanjhughes](https://github.com/alanjhughes))

### 💡 Others

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fun ServerUrlInput(
TextInput(
placeholder = {
NewText(
text = "http://localhost:8081",
text = "exp://",
style = NewAppTheme.font.md,
color = NewAppTheme.colors.text.secondary
)
Expand Down
9 changes: 5 additions & 4 deletions packages/expo-dev-launcher/ios/SwiftUI/DevServersView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,14 @@ struct DevServersView: View {
}

if showingURLInput {
TextField("http://10.0.0.25:8081", text: $urlText)
TextField("exp://", text: $urlText)
#if !os(macOS)
.autocapitalization(.none)
#endif
.disableAutocorrection(true)
.padding(.horizontal, 16)
.padding(.vertical, 12)
.foregroundColor(.primary)
.onSubmit(connectToURL)
#if !os(tvOS)
.overlay(
RoundedRectangle(cornerRadius: 5)
Expand Down Expand Up @@ -143,7 +142,9 @@ struct DevServersView: View {
}

private var connectButton: some View {
Button(action: connectToURL) {
Button {
connectToURL()
} label: {
Text("Connect")
.font(.headline)
.foregroundColor(.white)
Expand All @@ -153,7 +154,7 @@ struct DevServersView: View {
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.disabled(urlText.isEmpty)
.buttonStyle(PlainButtonStyle())
.buttonStyle(.plain)
}
}

Expand Down
5 changes: 5 additions & 0 deletions packages/expo-dev-menu/ios/DevMenuManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ open class DevMenuManager: NSObject {
return DevMenuPreferences.touchGestureEnabled
}

@objc
public func setShowFloatingActionButton(_ enabled: Bool) {
DevMenuPreferences.showFloatingActionButton = enabled
}

@objc
public func updateCurrentBridge(_ bridge: RCTBridge?) {
currentBridge = bridge
Expand Down
4 changes: 2 additions & 2 deletions packages/expo-dev-menu/ios/Modules/DevMenuPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class DevMenuPreferences: Module {
keyCommandsEnabledKey: true,
showsAtLaunchKey: false,
isOnboardingFinishedKey: true,
showFloatingActionButtonKey: true
showFloatingActionButtonKey: false
])
#else
UserDefaults.standard.register(defaults: [
Expand All @@ -42,7 +42,7 @@ public class DevMenuPreferences: Module {
keyCommandsEnabledKey: true,
showsAtLaunchKey: false,
isOnboardingFinishedKey: false,
showFloatingActionButtonKey: true
showFloatingActionButtonKey: false
])
#endif

Expand Down
2 changes: 2 additions & 0 deletions packages/expo-router/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
- fix toolbar placement updates ([#42585](https://github.com/expo/expo/pull/42585) by [@Ubax](https://github.com/Ubax))
- [ios] activate native zoom preventDismissal only when hook is used ([#42533](https://github.com/expo/expo/pull/42533) by [@Ubax](https://github.com/Ubax))
- Fix loader URL resolution for nested `/index` paths ([#42629](https://github.com/expo/expo/pull/42629) by [@hassankhan](https://github.com/hassankhan))
- [web] Ensure `<Head>` component re-renders when focus changes ([#42681](https://github.com/expo/expo/pull/42681) by [@hassankhan](https://github.com/hassankhan))

### 💡 Others

- Deprecate undocumented `expo-router/rsc/headers` RSC API in favor of `expo-server`'s `requestHeaders` runtime API ([#42678](https://github.com/expo/expo/pull/42678) by [@hassankhan](https://github.com/hassankhan))
- Refactor `useLoaderData` hook for better testability ([#42368](https://github.com/expo/expo/pull/42368) by [@hassankhan](https://github.com/hassankhan))

## 55.0.0-beta.4 — 2026-01-27

Expand Down
2 changes: 1 addition & 1 deletion packages/expo-router/build/head/ExpoHead.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion packages/expo-router/build/head/ExpoHead.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-router/build/head/ExpoHead.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-router/build/hooks.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading