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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type StackHeaderToolbarMenuItemOptionsAndroid,
type StackHeaderToolbarMenuItemShowAsActionAndroid,
} from 'react-native-screens/experimental';
import type { PlatformIconAndroid } from 'react-native-screens';
import { scenarioDescription } from './scenario-descriptions';

type IdOption = 'item-1' | 'item-2' | 'item-3';
Expand All @@ -22,9 +23,13 @@ type ShowAsActionOption =
| 'alwaysWithText'
| 'ifRoom'
| 'ifRoomWithText';
type IconOption = 'undefined' | 'searchIcon';
type CmdIconOption = 'no change' | IconOption;
type CmdShowAsActionOption = 'no change' | ShowAsActionOption;

const ID_OPTIONS: IdOption[] = ['item-1', 'item-2', 'item-3'];
const ICON_OPTIONS: IconOption[] = ['undefined', 'searchIcon'];
const CMD_ICON_OPTIONS: CmdIconOption[] = ['no change', ...ICON_OPTIONS];
const SHOW_AS_ACTION_OPTIONS: ShowAsActionOption[] = [
'undefined',
'never',
Expand All @@ -41,17 +46,30 @@ const CMD_SHOW_AS_ACTION_OPTIONS: CmdShowAsActionOption[] = [
interface SlotConfig {
include: boolean;
id: IdOption;
icon: IconOption;
showAsAction: ShowAsActionOption;
}

type Slots = [SlotConfig, SlotConfig, SlotConfig];

const DEFAULT_SLOTS: Slots = [
{ include: true, id: 'item-1', showAsAction: 'undefined' },
{ include: true, id: 'item-2', showAsAction: 'undefined' },
{ include: true, id: 'item-3', showAsAction: 'undefined' },
{ include: true, id: 'item-1', icon: 'undefined', showAsAction: 'undefined' },
{ include: true, id: 'item-2', icon: 'undefined', showAsAction: 'undefined' },
{ include: true, id: 'item-3', icon: 'undefined', showAsAction: 'undefined' },
];

function resolveIcon(option: IconOption): PlatformIconAndroid | undefined {
switch (option) {
case 'searchIcon':
return {
type: 'imageSource',
imageSource: require('@assets/search_black.png'),
};
default:
return undefined;
}
}

function resolveShowAsAction(
v: ShowAsActionOption,
): StackHeaderToolbarMenuItemShowAsActionAndroid | undefined {
Expand All @@ -67,9 +85,10 @@ const ITEM_TITLES: Record<IdOption, string> = {
function buildItems(slots: Slots) {
return slots
.filter(s => s.include)
.map(({ id, showAsAction }) => ({
.map(({ id, icon, showAsAction }) => ({
id,
title: ITEM_TITLES[id],
icon: resolveIcon(icon),
showAsAction: resolveShowAsAction(showAsAction),
}));
}
Expand Down Expand Up @@ -108,6 +127,7 @@ function MainScreen() {
const [lastClicked, setLastClicked] = useState<string | null>(null);

const [cmdTargetId, setCmdTargetId] = useState<IdOption>('item-1');
const [cmdIcon, setCmdIcon] = useState<CmdIconOption>('no change');
const [cmdShowAsAction, setCmdShowAsAction] =
useState<CmdShowAsActionOption>('no change');

Expand Down Expand Up @@ -147,6 +167,9 @@ function MainScreen() {

const sendCommand = useCallback(() => {
const options: StackHeaderToolbarMenuItemOptionsAndroid = {
...(cmdIcon !== 'no change' && {
icon: resolveIcon(cmdIcon),
}),
...(cmdShowAsAction !== 'no change' && {
showAsAction: resolveShowAsAction(cmdShowAsAction),
}),
Expand All @@ -155,7 +178,7 @@ function MainScreen() {
cmdTargetId,
options,
);
}, [cmdTargetId, cmdShowAsAction]);
}, [cmdTargetId, cmdIcon, cmdShowAsAction]);

return (
<ScrollView style={styles.scroll} contentContainerStyle={styles.content}>
Expand All @@ -166,6 +189,12 @@ function MainScreen() {
items={ID_OPTIONS}
onValueChange={setCmdTargetId}
/>
<SettingsPicker<CmdIconOption>
label="icon"
value={cmdIcon}
items={CMD_ICON_OPTIONS}
onValueChange={setCmdIcon}
/>
<SettingsPicker<CmdShowAsActionOption>
label="showAsAction"
value={cmdShowAsAction}
Expand Down Expand Up @@ -204,6 +233,12 @@ function SlotControls({ slots, updateSlot }: SlotControlsProps) {
value={slot.include}
onValueChange={v => updateSlot(i, { include: v })}
/>
<SettingsPicker<IconOption>
label="icon"
value={slot.icon}
items={ICON_OPTIONS}
onValueChange={v => updateSlot(i, { icon: v })}
/>
<SettingsPicker<ShowAsActionOption>
label="showAsAction"
value={slot.showAsAction}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

## Details

**Description:** This test covers the `showAsAction` prop on Android toolbar
menu items. It verifies that items placed in the action bar vs overflow menu
behave correctly for all five values: `never`, `always`, `alwaysWithText`,
`ifRoom`, `ifRoomWithText`. It also verifies that the default (omitted prop)
is equivalent to `never`, and that runtime updates via
`setToolbarMenuItemOptions` — including resetting to the default with
`undefined` — take effect immediately.
**Description:** This test covers the `showAsAction` prop on Android
toolbar menu items. It verifies that items placed in the action bar
vs overflow menu behave correctly for all five values: `never`,
`always`, `alwaysWithText`, `ifRoom`, `ifRoomWithText`. It also
verifies that the default (omitted prop) is equivalent to `never`,
and that runtime updates via `setToolbarMenuItemOptions` — including
resetting to the default with `undefined` — take effect immediately.
The test exercises the interaction between `showAsAction` and the
`icon` prop, since the `WITH_TEXT` modifier only has a visible
effect when an icon is present.

**OS test creation version:** Android: API Level 36

Expand All @@ -22,27 +25,37 @@ Other — automation is not implemented yet.

## Note

The WITH_TEXT modifier forces text labels alongside icons. Without icons (not
yet exposed), these values are indistinguishable from `always` / `ifRoom` — the
item still appears in the toolbar, showing its title as a text action.
The `WITH_TEXT` modifier (`alwaysWithText`, `ifRoomWithText`) requests
that the item's text title be displayed alongside its icon in the
toolbar. On Android this text-alongside-icon layout only takes effect
in **landscape** orientation. In portrait the toolbar shows the icon
without text — visually identical to `always` / `ifRoom`.

The number of `ifRoom` / `ifRoomWithText` items that fit in the toolbar is
determined on first layout and is not updated on subsequent orientation changes.
If the app starts in landscape the toolbar is wider, so more items may be
promoted but rotating to portrait will not hide the items (the title bar may
shrink instead); if it starts in portrait, rotating to landscape will not
promote additional items.
Without an icon, `always` vs `alwaysWithText` and `ifRoom` vs
`ifRoomWithText` are indistinguishable: the item shows its title as
a text action in both cases.

Icons are never shown in the overflow menu. Regardless of the `icon`
prop, overflow items display only their text title.

The number of `ifRoom` / `ifRoomWithText` items that fit in the
toolbar is determined on first layout and is not updated on subsequent
orientation changes. If the app starts in landscape the toolbar is
wider, so more items may be promoted but rotating to portrait will not
hide the items (the title bar may shrink instead); if it starts in
portrait, rotating to landscape will not promote additional items.

## Steps

### Baseline — default is equivalent to `never`

1. Launch the app and navigate to **Stack Toolbar Menu Show As Action**.
1. Launch the app and navigate to **Stack Toolbar Menu Show As
Action**.

- [ ] Header title reads "Show As Action Test". The toolbar
shows only the overflow (⋮) icon — no items appear as direct
action buttons. Open the overflow menu: three items are listed
("I1", "Item 2", "Item Number Three").
- [ ] Header title reads "Show As Action Test". The toolbar shows
only the overflow (⋮) icon — no items appear as direct action
buttons. Open the overflow menu: three items are listed ("I1",
"Item 2", "Item Number Three").

2. Open the overflow menu and tap "I1".

Expand All @@ -64,88 +77,125 @@ promote additional items.

---

### Props — icon in overflow

5. Set Slot 1 `icon` to `searchIcon` (leave `showAsAction` at
`never`). Open the overflow menu.

- [ ] "I1" appears as text only — no icon is shown. Icons are not
displayed in the overflow menu.

---

### Props — `always`

5. Change Slot 1 `showAsAction` to `always`.
6. Change Slot 1 `showAsAction` to `always`.

- [ ] "I1" appears directly in the toolbar as a text
action button. The overflow menu now contains only "Item 2" and
"Item Number Three".
- [ ] The search icon appears directly in the toolbar as an action
button. The text "I1" is not shown — the icon replaces it when
an icon is set. The overflow menu now contains only "Item 2"
and "Item Number Three".

6. Tap the "I1" action button in the toolbar.
7. Tap the search icon action button in the toolbar.

- [ ] "Last clicked" updates to `item-1`.

---

### Props — `alwaysWithText`

> TODO: The difference between `always` and `alwaysWithText` (forcing a
> text label alongside an icon) cannot be verified until icon support is
> added. This step only confirms the item appears in the toolbar.
8. Change Slot 1 `showAsAction` to `alwaysWithText`. Ensure the
device is in **portrait** orientation.

- [ ] "I1" appears in the toolbar as a search icon action button —
no text is visible next to the icon. Visually identical to
`always` in portrait.

7. Change Slot 1 `showAsAction` to `alwaysWithText`.
9. Rotate the device to **landscape**.

- [ ] "I1" still appears directly in the toolbar. Visually
indistinguishable from `always` without an icon.
- [ ] "I1" now shows the search icon with the text "I1" beside it.
This text-alongside-icon layout is the distinguishing effect
of the `WITH_TEXT` modifier; it only appears in landscape.

10. Rotate back to **portrait**.

- [ ] "I1" returns to icon-only display.

---

### Props — `ifRoom`

8. Change all three slots to `ifRoom` (Slot 1, Slot 2, Slot 3).
11. Reset Slot 1 `icon` to `undefined`. Change all three slots to
`ifRoom` (Slot 1, Slot 2, Slot 3).

- [ ] Items that fit within the available toolbar space appear
as action buttons; the rest fall back to the overflow menu. Exact
count depends on screen width.
- [ ] Items that fit within the available toolbar space appear as
action buttons; the rest fall back to the overflow menu.
Exact count depends on screen width.

---

### Props — `ifRoomWithText`

> TODO: Same icon limitation as `alwaysWithText`. This step only
> confirms `ifRoomWithText` behaves like `ifRoom` without icons.
12. Set all three slots: `icon` = `searchIcon`,
`showAsAction` = `ifRoomWithText`. Ensure the device is in
**portrait** orientation.

9. Change all three slots to `ifRoomWithText`.
- [ ] Items that fit in the toolbar show as icon-only action
buttons (no text beside the icons). Items that overflow
appear in the menu as text only (no icons).
- [ ] Rotate to landscape: promoted items now show their icon
with text title beside it.

- [ ] Behaviour matches `ifRoom` — items appear in the toolbar
when there is room, otherwise in overflow.
13. Rotate back to portrait. Reset all three slots: `icon` =
`undefined`, `showAsAction` = `undefined`.

- [ ] All items return to the overflow menu. The toolbar shows
only the overflow (⋮) icon.

---

### Runtime command — `never` → `always`

10. Reset all slots to `showAsAction = undefined` (all items in
overflow). Verify the overflow menu shows "I1", "Item 2",
14. Verify the overflow menu shows "I1", "Item 2",
"Item Number Three" and no action buttons are visible.

11. In **Send Command**, set target id = `item-1`,
15. In **Send Command**, set target id = `item-1`,
showAsAction = `always`. Tap **Send Command**.

- [ ] "I1" immediately moves from the overflow menu to the
toolbar as an action button without any prop change.
toolbar as a text action button without any prop change.

16. In **Send Command**, set target id = `item-1`,
icon = `searchIcon`, showAsAction = `no change`. Tap
**Send Command**.

- [ ] "I1" in the toolbar changes from a text action button to a
search icon action button. The `showAsAction = always`
override from step 15 is preserved.

---

### Runtime command — `always` → `never`

12. Set target id = `item-1`, showAsAction = `never`. Tap
**Send Command**.
17. Set target id = `item-1`, showAsAction = `never`,
icon = `no change`. Tap **Send Command**.

- [ ] "I1" moves back to the overflow menu.
- [ ] "I1" moves back to the overflow menu. Open the overflow
menu: "I1" appears as text only — the icon set in step 16
is not visible in the overflow menu.

---

### Runtime command — reset to default via `undefined`

13. Set target id = `item-2`, showAsAction = `always`. Tap
18. Set target id = `item-2`, showAsAction = `always`. Tap
**Send Command**.

- [ ] "Item 2" appears as a toolbar action button.

14. Set target id = `item-2`, showAsAction = `undefined`. Tap
19. Set target id = `item-2`, showAsAction = `undefined`. Tap
**Send Command**.

- [ ] "Item 2" returns to the overflow menu. The
`showAsAction` override is cleared and falls back to the regular
default (`never`).
- [ ] "Item 2" returns to the overflow menu. The `showAsAction`
override is cleared and falls back to the regular default
(`never`).
Loading