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
2 changes: 1 addition & 1 deletion __mocks__/react-native-gesture-handler.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('react-native-gesture-handler/src/mocks.ts');
module.exports = require('react-native-gesture-handler/src/mocks.js');
2 changes: 1 addition & 1 deletion expo-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/// <reference types="expo/types" />

// NOTE: This file should not be edited and should be in your git ignore
// NOTE: This file should not be edited and should be in your git ignore
2,452 changes: 2,452 additions & 0 deletions junit.xml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/api/novu/inbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createApiEndpoint } from '../common/client';

const getGroupsApi = createApiEndpoint('/Inbox/DeleteMessage');

export const deleteMessage = async (messageId: string) => {
const response = await getGroupsApi.delete({
groupId: encodeURIComponent(messageId),
});
return response.data;
};
5 changes: 5 additions & 0 deletions src/app/(app)/__tests__/contacts.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ jest.mock('@/components/contacts/contact-details-sheet', () => ({
ContactDetailsSheet: () => 'ContactDetailsSheet',
}));

jest.mock('@/components/ui/focus-aware-status-bar', () => ({
FocusAwareStatusBar: () => null,
}));

jest.mock('nativewind', () => ({
styled: (component: any) => component,
cssInterop: jest.fn(),
useColorScheme: () => ({ colorScheme: 'light' }),
}));

// Mock cssInterop globally
Expand Down
285 changes: 285 additions & 0 deletions src/app/(app)/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
import { render, waitFor } from '@testing-library/react-native';
import React from 'react';

import Map from '../index';
import { useAppLifecycle } from '@/hooks/use-app-lifecycle';
import { useLocationStore } from '@/stores/app/location-store';
import { locationService } from '@/services/location';

// Mock the dependencies
jest.mock('@/hooks/use-app-lifecycle');
jest.mock('@/stores/app/location-store');
jest.mock('@/services/location');
jest.mock('@/hooks/use-map-signalr-updates', () => ({
useMapSignalRUpdates: jest.fn(),
}));
jest.mock('@react-navigation/native', () => ({
useIsFocused: jest.fn(() => true),
useNavigation: jest.fn(() => ({
navigate: jest.fn(),
push: jest.fn(),
replace: jest.fn(),
goBack: jest.fn(),
})),
}));
jest.mock('@/api/mapping/mapping', () => ({
getMapDataAndMarkers: jest.fn().mockResolvedValue({
Data: { MapMakerInfos: [] }
}),
}));
jest.mock('@rnmapbox/maps', () => ({
setAccessToken: jest.fn(),
MapView: 'MapView',
Camera: 'Camera',
PointAnnotation: 'PointAnnotation',
StyleURL: {
Street: 'mapbox://styles/mapbox/streets-v11',
},
UserTrackingMode: {
Follow: 'follow',
FollowWithHeading: 'followWithHeading',
},
}));
jest.mock('expo-router', () => ({
Stack: {
Screen: ({ children, ...props }: any) => children,
},
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
}),
}));
jest.mock('@react-navigation/native', () => ({
useIsFocused: () => true,
useNavigation: () => ({
navigate: jest.fn(),
goBack: jest.fn(),
}),
}));
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
jest.mock('nativewind', () => ({
useColorScheme: () => ({
colorScheme: 'light',
}),
}));
jest.mock('@/stores/toast/store', () => ({
useToastStore: () => ({
showToast: jest.fn(),
}),
}));
jest.mock('@/stores/app/core-store', () => ({
useCoreStore: {
getState: () => ({
setActiveCall: jest.fn(),
}),
},
}));
jest.mock('@/components/maps/map-pins', () => ({
__esModule: true,
default: ({ pins, onPinPress }: any) => null,
}));
jest.mock('@/components/maps/pin-detail-modal', () => ({
__esModule: true,
default: ({ pin, isOpen, onClose, onSetAsCurrentCall }: any) => null,
}));



const mockUseAppLifecycle = useAppLifecycle as jest.MockedFunction<typeof useAppLifecycle>;
const mockUseLocationStore = useLocationStore as jest.MockedFunction<typeof useLocationStore>;
const mockLocationService = locationService as jest.Mocked<typeof locationService>;

describe('Map Component - App Lifecycle', () => {
beforeEach(() => {
jest.clearAllMocks();

// Setup default mocks
mockUseLocationStore.mockReturnValue({
latitude: 40.7128,
longitude: -74.0060,
heading: 0,
isMapLocked: false,
});

mockLocationService.startLocationUpdates = jest.fn().mockResolvedValue(undefined);
mockLocationService.stopLocationUpdates = jest.fn();
});

it('should reset user moved state when app becomes active', async () => {
// Start with app inactive
mockUseAppLifecycle.mockReturnValue({
isActive: false,
appState: 'background',
isBackground: true,
lastActiveTimestamp: null,
});

const { rerender } = render(<Map />);

// Simulate app becoming active
mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

rerender(<Map />);

await waitFor(() => {
// Verify that location service was called to start updates
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});
});

it('should handle map centering when location updates and app is active', async () => {
// Mock active app state
mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

render(<Map />);

await waitFor(() => {
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});
});

it('should reset hasUserMovedMap when map gets locked', async () => {
mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

// Start with unlocked map
mockUseLocationStore.mockReturnValue({
latitude: 40.7128,
longitude: -74.0060,
heading: 0,
isMapLocked: false,
});

const { rerender } = render(<Map />);

// Change to locked map
mockUseLocationStore.mockReturnValue({
latitude: 40.7128,
longitude: -74.0060,
heading: 0,
isMapLocked: true,
});

rerender(<Map />);

await waitFor(() => {
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});
});

it('should use navigation mode settings when map is locked', async () => {
mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

// Mock locked map with heading
mockUseLocationStore.mockReturnValue({
latitude: 40.7128,
longitude: -74.0060,
heading: 90, // East
isMapLocked: true,
});

render(<Map />);

await waitFor(() => {
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});

// The component should render with navigation mode settings
// (followZoomLevel: 16, followUserMode: FollowWithHeading, followPitch: 45)
});

it('should use normal mode settings when map is unlocked', async () => {
mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

// Mock unlocked map
mockUseLocationStore.mockReturnValue({
latitude: 40.7128,
longitude: -74.0060,
heading: 90,
isMapLocked: false,
});

render(<Map />);

await waitFor(() => {
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});

// The component should render with normal mode settings
// (followZoomLevel: 12, followUserMode: undefined, no followPitch)
});

it('should reset camera to normal view when exiting locked mode', async () => {
// Create a simplified test that focuses on the behavior without rendering the full component
const mockSetCamera = jest.fn();

// Mock the location service more completely
mockLocationService.startLocationUpdates.mockResolvedValue(undefined);
mockLocationService.stopLocationUpdates.mockResolvedValue(undefined);

mockUseAppLifecycle.mockReturnValue({
isActive: true,
appState: 'active',
isBackground: false,
lastActiveTimestamp: Date.now(),
});

// Test the logic by checking if the effect would be called
// We'll verify this by testing the behavior when the map lock state changes
let lockState = true;
let currentLocation = {
latitude: 40.7128,
longitude: -74.0060,
heading: 90,
isMapLocked: lockState,
};

mockUseLocationStore.mockImplementation(() => currentLocation);

const { rerender } = render(<Map />);

// Simulate unlocking the map
lockState = false;
currentLocation = {
...currentLocation,
isMapLocked: lockState,
};

rerender(<Map />);

// The test passes if the component renders without errors
// The actual camera reset behavior is tested through integration tests
await waitFor(() => {
expect(mockLocationService.startLocationUpdates).toHaveBeenCalled();
});
});
});
45 changes: 45 additions & 0 deletions src/app/(app)/__tests__/protocols.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,51 @@ jest.mock('@/components/protocols/protocol-details-sheet', () => ({
},
}));

jest.mock('@/components/ui/focus-aware-status-bar', () => ({
FocusAwareStatusBar: () => null,
}));

jest.mock('@/components/ui/box', () => ({
Box: ({ children, ...props }: any) => {
const { View } = require('react-native');
return <View {...props}>{children}</View>;
},
}));

jest.mock('@/components/ui/input', () => ({
Input: ({ children, ...props }: any) => {
const { View } = require('react-native');
return <View {...props}>{children}</View>;
},
InputField: (props: any) => {
const { TextInput } = require('react-native');
return <TextInput {...props} />;
},
InputIcon: ({ as: Icon, ...props }: any) => {
const { View } = require('react-native');
return Icon ? <Icon {...props} /> : <View {...props} />;
},
InputSlot: ({ children, onPress, ...props }: any) => {
const { Pressable, View } = require('react-native');
return onPress ? <Pressable onPress={onPress} {...props}>{children}</Pressable> : <View {...props}>{children}</View>;
},
}));

jest.mock('lucide-react-native', () => ({
Search: ({ ...props }: any) => {
const { View } = require('react-native');
return <View {...props} testID="search-icon" />;
},
X: ({ ...props }: any) => {
const { View } = require('react-native');
return <View {...props} testID="x-icon" />;
},
FileText: ({ ...props }: any) => {
const { View } = require('react-native');
return <View {...props} testID="file-text-icon" />;
},
}));

// Mock the protocols store
const mockProtocolsStore = {
protocols: [],
Expand Down
Loading
Loading