diff --git a/apps/src/tests/single-feature-tests/tabs/index.ts b/apps/src/tests/single-feature-tests/tabs/index.ts index 39584ef9dc..4988d0e420 100644 --- a/apps/src/tests/single-feature-tests/tabs/index.ts +++ b/apps/src/tests/single-feature-tests/tabs/index.ts @@ -18,6 +18,7 @@ import TestTabsSpecialEffectsScrollToTop from './test-tabs-special-effects-scrol import TestTabsTabBarExperimentalUserInterfaceStyle from './test-tabs-tab-bar-experimental-user-interface-style-ios'; import TestTabsLifecycleEvents from './test-tabs-lifecycle-events'; import TestTabsItemTitle from './test-tabs-item-title-ios'; +import TestTabsItemIcon from './test-tabs-item-icon-ios'; const scenarios = { TestTabBottomAccessory, @@ -38,6 +39,7 @@ const scenarios = { TestTabsTabBarExperimentalUserInterfaceStyle, TestTabsLifecycleEvents, TestTabsItemTitle, + TestTabsItemIcon, }; const TabsScenarioGroup: ScenarioGroup = { diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/index.tsx b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/index.tsx new file mode 100644 index 0000000000..5e6d4e7dfe --- /dev/null +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/index.tsx @@ -0,0 +1,205 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { scenarioDescription } from './scenario-description'; +import { createScenario } from '@apps/tests/shared/helpers'; +import { + TabsContainerWithHostConfigContext, + type TabRouteConfig, + DEFAULT_TAB_ROUTE_OPTIONS, +} from '@apps/shared/gamma/containers/tabs'; +import { Colors } from '@apps/shared/styling'; + +function TintTab() { + return ( + + Template Source (Host Tint) + + Host `tabBarTintColor`:{' '} + GreenDark100{'\n'} + `icon`: templateSource icon.png{'\n'} + `selectedIcon`: templateSource icon_fill.png{'\n'} + `tabBarItemIconColor` is NOT set.{'\n'} + {'\n'} + Selected: filled template image, tinted{' '} + GREEN.{'\n'} + Unselected: Titles and icons render in the system theme color. For the last tab, the icon retains the black color from its source image. + + ); +} + +function OverrideTab() { + return ( + + SF Symbol (Tint Color Override) + + Host `tabBarTintColor`:{' '} + GreenDark100{'\n'} + `selected.tabBarItemIconColor`:{' '} + RedLight100{'\n'} + `icon`: SF Symbol "star"{'\n'} + `selectedIcon`: SF Symbol "star.fill"{'\n'} + {'\n'} + Selected: filled star, tinted{' '} + RED{'\n'} title on iOS18: GREEN on iOS26: RED{'\n'} + Unselected: Titles and icons render in the system theme color. For the last tab, the icon retains the black color from its source image. + + ); +} + +function XcassetTab() { + return ( + + Xcasset (Host Tint) + + Host `tabBarTintColor`:{' '} + GreenDark100{'\n'} + `icon`: Xcasset custom-icon-fill{'\n'} + `tabBarItemIconColor` is NOT set.{'\n'} + {'\n'} + Selected: filled template image, tinted{' '} + GREEN.{'\n'} + Unselected: Titles and icons render in the system theme color. For the last tab, the icon retains the black color from its source image. + + ); +} + +function ImageTab() { + return ( + + Image Source (Non-Tintable) + + Host `tabBarTintColor`:{' '} + GreenDark100{'\n'} + `normal.tabBarItemIconColor`:{' '} + BlueDark100{'\n'} + `icon`: imageSource icon.png{'\n'} + `selectedIcon`: imageSource icon_fill.png{'\n'} + {'\n'} + `imageSource` icons render in their original colors and are NOT + affected by `tabBarTintColor` or `tabBarItemIconColor`.{'\n'} + {'\n'} + Selected: filled image in its black color{'\n'}(the host{' '} + green tint is + ignored).{'\n'} + Unselected iOS18: outline icons in BLUE color.{'\n'} + Unselected iOS26: icons in system theme color. + + + ); +} + +const ROUTE_CONFIGS: TabRouteConfig[] = [ + { + name: 'Tint', + Component: TintTab, + options: { + ...DEFAULT_TAB_ROUTE_OPTIONS, + title: 'Tint', + ios: { + icon: { + type: 'templateSource', + templateSource: require('@assets/variableIcons/icon.png'), + }, + selectedIcon: { + type: 'templateSource', + templateSource: require('@assets/variableIcons/icon_fill.png'), + }, + }, + }, + }, + { + name: 'Override', + Component: OverrideTab, + options: { + ...DEFAULT_TAB_ROUTE_OPTIONS, + title: 'Override', + ios: { + icon: { + type: 'sfSymbol', + name: 'star', + }, + selectedIcon: { + type: 'sfSymbol', + name: 'star.fill', + }, + standardAppearance: { + stacked: { + selected: { + tabBarItemIconColor: Colors.RedLight100, + }, + }, + }, + }, + }, + }, + { + name: 'XcassetIcon', + Component: XcassetTab, + options: { + title: 'Xcasset', + ios: { + icon: { + type: 'xcasset', + name: 'custom-icon-fill', + }, + }, + }, + }, + { + name: 'Image', + Component: ImageTab, + options: { + ...DEFAULT_TAB_ROUTE_OPTIONS, + title: 'Image', + ios: { + icon: { + type: 'imageSource', + imageSource: require('@assets/variableIcons/icon.png'), + }, + selectedIcon: { + type: 'imageSource', + imageSource: require('@assets/variableIcons/icon_fill.png'), + }, + standardAppearance: { + stacked: { + normal: { + tabBarItemIconColor: Colors.BlueDark100, + }, + }, + }, + }, + }, + }, +]; + +export function App() { + return ( + + ); +} + +const styles = StyleSheet.create({ + screen: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 24, + gap: 12, + }, + label: { + fontSize: 17, + fontWeight: '600', + textAlign: 'center', + }, + hint: { + fontSize: 13, + color: Colors.LightOffNavy, + textAlign: 'center', + lineHeight: 20, + }, +}); + +export default createScenario(App, scenarioDescription); diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario-description.ts b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario-description.ts new file mode 100644 index 0000000000..6f1e2889ea --- /dev/null +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario-description.ts @@ -0,0 +1,12 @@ +import type { ScenarioDescription } from '@apps/tests/shared/helpers'; + +export const scenarioDescription: ScenarioDescription = { + name: 'Tab Bar Item Icon', + key: 'test-tabs-item-icon-ios', + details: + 'Exercises iOS tab bar item icon props: icon, selectedIcon, host' + + ' tabBarTintColor, and per-tab tabBarItemIconColor override.', + platforms: ['ios'], + e2eCoverage: 'incomplete', + smokeTest: false, +}; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario.md b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario.md new file mode 100644 index 0000000000..52a3492b8f --- /dev/null +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-item-icon-ios/scenario.md @@ -0,0 +1,119 @@ +# Test Scenario: Tab Bar Item Icon (iOS) + +## Details + +**Description:** Validates iOS tab bar item icon props: `icon` and +`selectedIcon` (different images for selected vs. unselected states), the +host-level `tabBarTintColor`, and the per-tab +`standardAppearance.stacked.selected.tabBarItemIconColor` override that +takes precedence over the host tint. Covers four `PlatformIconIOS` types: +`templateSource` (tintable), `sfSymbol` (tintable), `xcasset` (tintable), and `imageSource` +(non-tintable). + +**OS test creation version:** iOS 18.6 and iOS 26.5 + +## E2E test + +Incomplete: Not automated. All observable outcomes are purely visual (icon color, selected vs. +unselected glyph). Detox does not expose tint color or rendered image +attributes of native tab bar items, so automated assertion is not feasible. + +## Prerequisites + +- iOS device or simulator running iOS 18 or later. +- The iPhone in portrait orientation is the primary verification surface (stacked layout). + +## Note + +- **Normal (unselected) state ([iOS26 KI](https://github.com/software-mansion/react-native-screens-labs/discussions/395)):** + On iOS 18 and lower, any per-tab + `normal.tabBarItemIconColor` apply to unselected tab icons. On iOS 26, + only the selected tab is tinted by `tabBarItemIconColor`; + unselected tabs adopt the system theme appearance. +- `tabBarTintColor` is applied only to selected tab bar item icon and title. +- **`imageSource` icons are non-tintable:** they render in their original + colors regardless of `tabBarTintColor` or `tabBarItemIconColor`. + `templateSource`, `xcasset` and `sfSymbol` icons are tintable. + +## Steps + +### Host `tabBarTintColor` applies to a tintable selected icon + +1. Launch the app and navigate to the **Tab Bar Item Icon** screen. + +- [ ] Expected: Four tabs are visible in the tab bar: **Tint**, + **Override**, **Xcasset**, and **Image**. The **Tint** tab is + selected by default. Its icon is the filled template image + tinted **green** by the host + `tabBarTintColor`. The unselected **Override** and **Xcasset** + tabs render their icons and titles in the system theme color. The + unselected **Image** tab title renders in the system theme color, + but its icon keeps its original source colors. + +--- + +### `icon` vs `selectedIcon` swap + +2. Tap the **Override** tab. + +- [ ] Expected: The **Override** tab's icon swaps from the outline + star to the filled star. The + previously selected **Tint** tab swaps from the filled template + image back to the outline template image. + +--- + +### `tabBarItemIconColor` overrides `tabBarTintColor` + +3. With **Override** still selected, observe the selected icon color. + +- [ ] Expected: The filled star is **red**, NOT green. + On iOS 18 the selected title is + green (host tint); on iOS 26 the selected title is red (override - it's native + bug KI linked in Notes section). + +4. Tap the **Tint** tab, then tap **Override** again. + +- [ ] Expected: On re-selection the red filled star reappears + immediately with no visual glitch. The **Tint** tab shows the system-theme + outline template + image. + +--- + +### `xcasset` icon uses host tint, no `selectedIcon` + +5. Tap the **Xcasset** tab. + +- [ ] Expected: The **Xcasset** tab's icon shows the `custom-icon-fill` + xcasset image tinted **green**. Because no `selectedIcon` is configured for this + tab, the same icon asset is used in both selected and unselected + states. The previously selected **Override** tab reverts to the + outline star in system theme color. + +--- + +### `imageSource` icons are non-tintable + +6. Tap the **Image** tab. + +- [ ] Expected: The icon swaps from the outline image to + the filled image. Both renders use the original PNG + colors - the host `tabBarTintColor` (green) have NO effect on the + selected icon. On iOS 18, the unselected icon renders in + **blue**; on iOS 26+ it renders in the system theme + color. + +--- + +### Stability check + +7. Cycle through all four tabs in order + (Tint -> Override -> Xcasset -> Image), then in reverse. + +- [ ] Expected: Each tab swaps between its `icon` and `selectedIcon` + (where configured) consistently on selection. The correct tint + behavior is applied each time: green host tint for **Tint** and + **Xcasset**, red override for **Override**'s selected state, and + no tint effect for **Image**. No crash, layout freeze, or visual + artifact occurs during rapid cycling.