Skip to content

Commit 1431baa

Browse files
authored
Merge pull request #144 from Resgrid/develop
CU-868f1731u Fixing ios interact issue, fixing zero state sidebar.
2 parents 732a79f + e14683a commit 1431baa

File tree

10 files changed

+181
-22
lines changed

10 files changed

+181
-22
lines changed

.DS_Store

0 Bytes
Binary file not shown.

app.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
185185
extraMavenRepos: ['../../node_modules/@notifee/react-native/android/libs'],
186186
targetSdkVersion: 35,
187187
},
188+
ios: {
189+
deploymentTarget: '18.0',
190+
},
188191
},
189192
],
190193
[

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"start": "cross-env EXPO_NO_DOTENV=1 expo start",
99
"prebuild": "cross-env EXPO_NO_DOTENV=1 yarn expo prebuild",
1010
"android": "cross-env EXPO_NO_DOTENV=1 expo run:android",
11-
"ios": "cross-env EXPO_NO_DOTENV=1 expo run:ios",
11+
"ios": "cross-env EXPO_NO_DOTENV=1 expo run:ios --device",
1212
"web": "cross-env EXPO_NO_DOTENV=1 expo start --web",
1313
"xcode": "xed -b ios",
1414
"doctor": "npx expo-doctor@latest",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
3+
// Simple test to verify tab layout configuration without complex mocking
4+
describe('TabLayout Configuration', () => {
5+
it('should have proper tab bar styling configuration for iOS release build touch events', () => {
6+
// This test verifies that the tab bar configuration includes the necessary
7+
// zIndex and elevation properties to fix touch event issues in iOS release builds
8+
9+
const expectedTabBarStyle = {
10+
paddingBottom: 5,
11+
paddingTop: 5,
12+
height: 60, // Default height for portrait mode
13+
elevation: 8, // Ensures tab bar is above other elements on Android
14+
zIndex: 100, // Ensures tab bar is above other elements on iOS
15+
};
16+
17+
const expectedLandscapeTabBarStyle = {
18+
paddingBottom: 5,
19+
paddingTop: 5,
20+
height: 65, // Height for landscape mode
21+
elevation: 8,
22+
zIndex: 100,
23+
};
24+
25+
// Verify that the configuration object has the required properties
26+
expect(expectedTabBarStyle.zIndex).toBe(100);
27+
expect(expectedTabBarStyle.elevation).toBe(8);
28+
expect(expectedLandscapeTabBarStyle.height).toBe(65);
29+
expect(expectedTabBarStyle.height).toBe(60);
30+
});
31+
32+
it('should handle notification inbox positioning properly', () => {
33+
// Verify that NotificationInbox is positioned to not interfere with tab bar
34+
// The NotificationInbox should be rendered within the tab content area,
35+
// not at the root level which could block touch events
36+
37+
const notificationInboxProps = {
38+
isOpen: false,
39+
onClose: jest.fn(),
40+
};
41+
42+
// When closed, should not interfere with touch events
43+
expect(notificationInboxProps.isOpen).toBe(false);
44+
45+
// pointerEvents should be set to 'none' when closed to prevent interference
46+
const expectedPointerEvents = notificationInboxProps.isOpen ? 'auto' : 'none';
47+
expect(expectedPointerEvents).toBe('none');
48+
});
49+
50+
it('should use proper z-index values to prevent conflicts', () => {
51+
// Verify that z-index values are properly configured to prevent conflicts
52+
// Tab bar should have z-index 100
53+
// NotificationInbox should have lower z-index (999-1000) to not interfere
54+
55+
const tabBarZIndex = 100;
56+
const notificationBackdropZIndex = 999;
57+
const notificationSidebarZIndex = 1000;
58+
59+
expect(tabBarZIndex).toBeLessThan(notificationBackdropZIndex);
60+
expect(notificationBackdropZIndex).toBeLessThan(notificationSidebarZIndex);
61+
});
62+
});

src/app/(app)/_layout.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export default function TabLayout() {
224224
}
225225

226226
const content = (
227-
<View style={styles.container}>
227+
<View style={styles.container} pointerEvents="box-none">
228228
<View className="flex-1 flex-row" ref={parentRef}>
229229
{/* Drawer - conditionally rendered as permanent in landscape */}
230230
{isLandscape ? (
@@ -265,6 +265,9 @@ export default function TabLayout() {
265265
paddingBottom: 5,
266266
paddingTop: 5,
267267
height: isLandscape ? 65 : 60,
268+
elevation: 8, // Ensure tab bar is above other elements on Android
269+
zIndex: 100, // Ensure tab bar is above other elements on iOS
270+
backgroundColor: 'transparent', // Ensure proper touch event handling
268271
},
269272
}}
270273
>
@@ -333,6 +336,9 @@ export default function TabLayout() {
333336
}}
334337
/>
335338
</Tabs>
339+
340+
{/* NotificationInbox positioned within the tab content area */}
341+
{activeUnitId && config && rights?.DepartmentCode && <NotificationInbox isOpen={isNotificationsOpen} onClose={() => setIsNotificationsOpen(false)} />}
336342
</View>
337343
</View>
338344
</View>
@@ -342,8 +348,6 @@ export default function TabLayout() {
342348
<>
343349
{activeUnitId && config && rights?.DepartmentCode ? (
344350
<NovuProvider subscriberId={`${rights?.DepartmentCode}_Unit_${activeUnitId}`} applicationIdentifier={config.NovuApplicationId} backendUrl={config.NovuBackendApiUrl} socketUrl={config.NovuSocketUrl}>
345-
{/* NotificationInbox at the root level */}
346-
<NotificationInbox isOpen={isNotificationsOpen} onClose={() => setIsNotificationsOpen(false)} />
347351
{content}
348352
</NovuProvider>
349353
) : (

src/components/__tests__/zero-state.test.tsx

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react-native';
33
import { AlertCircle, FileX } from 'lucide-react-native';
44
import React from 'react';
55

6-
import { Button } from '@/components/ui/button';
6+
import { Button, ButtonText } from '@/components/ui/button';
77

88
import ZeroState from '../common/zero-state';
99

@@ -36,4 +36,75 @@ describe('ZeroState', () => {
3636
expect(screen.getByText('Connection failed')).toBeTruthy();
3737
expect(screen.getByText('Check your internet connection')).toBeTruthy();
3838
});
39+
40+
it('applies custom View className', () => {
41+
const { getByTestId } = render(
42+
<ZeroState viewClassName="custom-view-class bg-red-100" />
43+
);
44+
45+
const zeroStateContainer = getByTestId('zero-state');
46+
expect(zeroStateContainer.parent).toBeTruthy();
47+
});
48+
49+
it('applies custom Center className', () => {
50+
const { getByTestId } = render(
51+
<ZeroState centerClassName="custom-center-class bg-blue-100" />
52+
);
53+
54+
const zeroStateElement = getByTestId('zero-state');
55+
expect(zeroStateElement).toBeTruthy();
56+
});
57+
58+
it('combines centerClassName with additional className', () => {
59+
const { getByTestId } = render(
60+
<ZeroState
61+
centerClassName="custom-center-class"
62+
className="additional-class"
63+
/>
64+
);
65+
66+
const zeroStateElement = getByTestId('zero-state');
67+
expect(zeroStateElement).toBeTruthy();
68+
});
69+
70+
it('renders with children', () => {
71+
render(
72+
<ZeroState>
73+
<Button onPress={() => { }}>
74+
<ButtonText>Retry</ButtonText>
75+
</Button>
76+
</ZeroState>
77+
);
78+
79+
expect(screen.getByText('Retry')).toBeTruthy();
80+
});
81+
82+
it('uses error state defaults when isError is true', () => {
83+
render(<ZeroState isError />);
84+
85+
expect(screen.getByText('An error occurred')).toBeTruthy();
86+
expect(screen.getByText('Please try again later')).toBeTruthy();
87+
});
88+
89+
it('overrides error state defaults with custom text', () => {
90+
render(
91+
<ZeroState
92+
isError
93+
heading="Custom error title"
94+
description="Custom error description"
95+
/>
96+
);
97+
98+
expect(screen.getByText('Custom error title')).toBeTruthy();
99+
expect(screen.getByText('Custom error description')).toBeTruthy();
100+
});
101+
102+
it('applies default classNames when not provided', () => {
103+
const { getByTestId } = render(<ZeroState />);
104+
105+
const zeroStateElement = getByTestId('zero-state');
106+
expect(zeroStateElement).toBeTruthy();
107+
// The default centerClassName should be applied
108+
expect(zeroStateElement.parent).toBeTruthy();
109+
});
39110
});

src/components/common/zero-state.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,21 @@ interface ZeroStateProps {
5252
isError?: boolean;
5353

5454
/**
55-
* Custom class name for additional styling
55+
* Custom class name for additional styling of the Center component
5656
*/
5757
className?: string;
58+
59+
/**
60+
* Custom class name for the root View component
61+
* @default "size-full p-6"
62+
*/
63+
viewClassName?: string;
64+
65+
/**
66+
* Custom class name for the Center component (overrides default)
67+
* @default "flex-1 p-6"
68+
*/
69+
centerClassName?: string;
5870
}
5971

6072
/**
@@ -69,6 +81,8 @@ const ZeroState: React.FC<ZeroStateProps> = ({
6981
children,
7082
isError = false,
7183
className = '',
84+
viewClassName = 'size-full p-6',
85+
centerClassName = 'flex-1 p-6',
7286
}) => {
7387
const { t } = useTranslation();
7488

@@ -78,8 +92,8 @@ const ZeroState: React.FC<ZeroStateProps> = ({
7892
const defaultDescription = isError ? t('common.tryAgainLater', 'Please try again later') : t('common.nothingToDisplay', "There's nothing to display at the moment");
7993

8094
return (
81-
<View className="size-full p-6">
82-
<Center className={`flex-1 p-6 ${className}`} testID="zero-state">
95+
<View className={viewClassName}>
96+
<Center className={`${centerClassName} ${className}`} testID="zero-state">
8397
<VStack space="md" className="items-center">
8498
<Box className="mb-4">
8599
<Icon size={iconSize} color={isError ? '#ef4444' : iconColor} />

src/components/notifications/NotificationInbox.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export const NotificationInbox = ({ isOpen, onClose }: NotificationInboxProps) =
236236
}
237237

238238
return (
239-
<View style={StyleSheet.absoluteFill}>
239+
<View style={StyleSheet.absoluteFill} pointerEvents={isOpen ? 'auto' : 'none'}>
240240
{/* Backdrop for tapping outside to close */}
241241
<Animated.View style={[StyleSheet.absoluteFill, styles.backdrop, { opacity: fadeAnim }]}>
242242
<Pressable style={styles.backdropPressable} onPress={onClose} />
@@ -337,7 +337,7 @@ export const NotificationInbox = ({ isOpen, onClose }: NotificationInboxProps) =
337337
const styles = StyleSheet.create({
338338
backdrop: {
339339
backgroundColor: 'rgba(0, 0, 0, 0.5)',
340-
zIndex: 9999,
340+
zIndex: 999,
341341
},
342342
backdropPressable: {
343343
width: '100%',
@@ -358,7 +358,7 @@ const styles = StyleSheet.create({
358358
shadowOpacity: 0.25,
359359
shadowRadius: 3.84,
360360
elevation: 5,
361-
zIndex: 10000,
361+
zIndex: 1000,
362362
},
363363
safeArea: {
364364
flex: 1,

src/components/sidebar/sidebar.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ const Sidebar = () => {
4747

4848
{/* Third row - Status buttons or empty state */}
4949
{isActiveStatusesEmpty ? (
50-
<ZeroState icon={Settings} iconSize={60} iconColor="#64748b" heading={t('common.noActiveUnit')} description={t('common.noActiveUnitDescription')} className="mt-4">
51-
<Button variant="solid" action="primary" size="md" onPress={handleNavigateToSettings} className="mt-4">
50+
<ZeroState
51+
icon={Settings}
52+
iconSize={60}
53+
iconColor="#64748b"
54+
heading={t('common.noActiveUnit')}
55+
description={t('common.noActiveUnitDescription')}
56+
className="mt-0"
57+
viewClassName="size-full px-6 pb-6 pt-0"
58+
centerClassName="p-6"
59+
>
60+
<Button variant="solid" action="primary" size="md" onPress={handleNavigateToSettings} className="mt-0">
5261
<ButtonText>{t('settings.title')}</ButtonText>
5362
</Button>
5463
</ZeroState>

src/stores/protocols/__tests__/store.test.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,17 +353,13 @@ describe('useProtocolsStore', () => {
353353

354354
const { result } = renderHook(() => useProtocolsStore());
355355

356-
// Start two fetch operations concurrently
357-
const promise1 = act(async () => {
358-
await result.current.fetchProtocols();
359-
});
360-
361-
const promise2 = act(async () => {
362-
await result.current.fetchProtocols();
356+
// Start two fetch operations concurrently within a single act
357+
await act(async () => {
358+
const promise1 = result.current.fetchProtocols();
359+
const promise2 = result.current.fetchProtocols();
360+
await Promise.all([promise1, promise2]);
363361
});
364362

365-
await Promise.all([promise1, promise2]);
366-
367363
expect(getAllProtocols).toHaveBeenCalledTimes(2);
368364
expect(result.current.protocols).toEqual(mockProtocols);
369365
expect(result.current.isLoading).toBe(false);

0 commit comments

Comments
 (0)