- Getting Started
- The Basics
- Concepts
- SuperNative
-
EDGE Components
- Introduction
- Activity Indicator
- Badge
- Bottom Navigation
- Bottom Sheet
- Button
- Button Group
- Canvas
- Carousel
- Checkbox
- Chip
- Column
- Divider
- Gesture Area
- Icon
- Icons
- Image
- Layout & Styling
- List
- Menus
- Modal
- Pressable
- Progress Bar
- Radio Group
- Row
- Scroll View
- Select
- Shapes
- Side Navigation
- Slider
- Spacer
- Stack
- Tab Row
- Text
- Text Input
- Toggle
- Top Bar
- Virtual List
- Web View
- Plugins
- Testing
- Architecture
You're viewing pre-release documentation — version 4.x is in beta
Features, APIs and behaviour may change before the stable release. View the stable version (3.x)
Navigation & Flows
Overview#
Screens navigate: a tap pushes a detail screen, a save replaces the current one, a back gesture pops. The testing suite lets you assert on those intentions, and — when you want to keep going — follow them onto the next screen and walk a whole flow, with each screen's state preserved exactly as it is on device.
Asserting on navigation#
After an interaction, assert on where the screen intends to go:
assertNavigatedTo($uri)— a forward push to$uri.assertReplacedWith($uri)— the current screen was replaced with$uri.assertWentBack()— a back navigation.assertExitedToWeb($uri)— navigation left the native stack for a web URL.assertNoNavigation()— the interaction stayed on this screen.
it('navigates to a demo when a featured card is tapped', function () { Native::visit('/') ->tap('Scanner') ->assertNavigatedTo('/media/scanner');});
Following a flow#
follow() (alias followNavigation()) resolves the pending navigation through the route registry and returns a
new harness mounted on the destination, carrying the intent's data, params, and layout.
It mirrors the router precisely. On a forward navigate the current screen stays alive underneath — goBack() returns
to it with its state intact and onResume() fired. On a replace it unmounts and drops out of the stack.
use App\NativeComponents\GeolocationDemo; it('follows navigation from home onto the geolocation demo', function () { Native::visit('/') ->tap('Location') ->assertNavigatedTo('/system/geolocation') ->follow() ->assertScreen(GeolocationDemo::class) ->assertSee('Geolocation');});
assertScreen($componentClass) confirms which component the harness is currently driving — useful after a follow()
or goBack() to pin down exactly where you are.
Going back with state preserved#
goBack() pops the current screen and returns the harness for the one below it — the live component, resumed exactly
as the router resumes it. State the previous screen held before you navigated away is still there:
use App\NativeComponents\GeolocationDemo;use App\NativeComponents\Home; it('returns to home with state preserved after visiting a demo', function () { $home = Native::visit('/') ->call('doubleTapped') // mutate state before navigating away ->tap('Location'); $home->follow() ->assertScreen(GeolocationDemo::class) ->assertNavTitle('Geolocation') ->goBack() ->assertScreen(Home::class) ->assertSet('gesture', 'Double-tapped!') // survived the push/pop ->assertSee('Double-tapped!');});
Chrome assertions#
When a screen returns view() with a layout, the suite renders that layout's chrome — the navigation bar and tab bar
— just as it appears on device. Assert on it directly.
assertNavTitle($title)— the navigation bar shows this title.assertHasTabBar()— the screen renders native tab chrome.assertTabBarVisible()/assertTabBarHidden()— the tab bar's visibility on this screen.assertHasTab($label)— a tab with this label exists.assertTabActive($label)— the tab with this label is the active one.
visit() resolves the route's layout automatically, so chrome is populated exactly as navigation would produce it:
it('renders the hub inside tab chrome with the right tab active', function () { Native::visit('/media') ->assertHasTabBar() ->assertNavTitle('Media') ->assertHasTab('Home') ->assertHasTab('Media') ->assertHasTab('System') ->assertTabActive('Media') ->assertTabBarVisible();});
Detail screens using stack chrome carry a title but no tab bar:
it('renders detail screens in stack chrome without a tab bar', function () { Native::visit('/system/haptics') ->assertNavTitle('Haptics') ->assertMissingElement('native_root_tabs');});
Walking a full flow#
Put it together — follow forward through the stack, assert the chrome tracks it, and pop back:
use App\NativeComponents\MediaHub;use App\NativeComponents\ScannerDemo; it('walks hub → demo → back with chrome tracking the stack', function () { $hub = Native::visit('/media')->assertTabActive('Media'); $scanner = $hub->tap('Scanner') ->assertNavigatedTo('/media/scanner') ->follow() ->assertScreen(ScannerDemo::class) ->assertNavTitle('Scanner'); $scanner->goBack() ->assertScreen(MediaHub::class) ->assertTabActive('Media');});
in no time