Skip to content

Commit

Permalink
Merge pull request #85 from COS301-SE-2024/feat/mobile-integration/bo…
Browse files Browse the repository at this point in the history
…oking

Feat/mobile integration/booking
  • Loading branch information
KamogeloMoeketse authored Jun 24, 2024
2 parents 5be1b44 + b6c2fc1 commit 34c7ce0
Show file tree
Hide file tree
Showing 36 changed files with 2,514 additions and 1,023 deletions.
58 changes: 58 additions & 0 deletions frontend/occupi-mobile3/__tests__/Login_Test/Onboarding3.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Onboarding3 from '../../screens/Login/Onboarding3';
import { router } from 'expo-router';

jest.mock('expo-router', () => ({
useRouter: () => ({
replace: jest.fn(),
push: jest.fn(),
}),
router: {
push: jest.fn(),
},
}));

jest.mock('expo-linear-gradient', () => ({
LinearGradient: ({ children }) => children,
}));

jest.mock('@gluestack-style/react', () => ({
StyledProvider: ({ children }) => children,
useStyled: () => ({}),
StyledText: 'Text',
StyledView: 'View',
StyledImage: 'Image',
StyledButton: 'Button',
// Mock other components if necessary
}));

jest.mock('@gluestack-ui/themed', () => ({
Box: 'View',
Image: 'Image',
Center: 'View',
Text: 'Text',
Heading: 'Text',
Button: 'Button',
}));

describe('Onboarding3', () => {
it('renders the image correctly', () => {
const { getByRole } = render(<Onboarding3 />);
const image = getByRole('image', { name: 'logo' });
expect(image).toBeTruthy();
});

it('renders the heading and text correctly', () => {
const { getByText } = render(<Onboarding3 />);
expect(getByText('Real time updates')).toBeTruthy();
expect(getByText('Provides real time updates for occupancy and capacity')).toBeTruthy();
});

it('navigates to the welcome screen when the button is pressed', () => {
const { getByText } = render(<Onboarding3 />);
const button = getByText('Next');
fireEvent.press(button);
expect(router.push).toHaveBeenCalledWith('/welcome');
});
});
117 changes: 117 additions & 0 deletions frontend/occupi-mobile3/__tests__/Login_Test/OtpVerification.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React from 'react';
import { render, fireEvent, act } from '@testing-library/react-native';
import OTPVerification from '../../screens/Login/OTPVerification';
import { router } from 'expo-router';

jest.mock('expo-router', () => ({
useRouter: () => ({
replace: jest.fn(),
push: jest.fn(),
}),
router: {
push: jest.fn(),
},
}));

jest.mock('expo-linear-gradient', () => ({
LinearGradient: ({ children }) => children,
}));

jest.mock('@gluestack-ui/themed', () => ({
VStack: 'View',
Box: 'View',
HStack: 'View',
Text: 'Text',
Button: 'Button',
Image: 'Image',
Center: 'View',
FormControl: 'View',
Input: 'TextInput',
LinkText: 'Text',
FormControlHelperText: 'Text',
InputField: 'TextInput',
ButtonText: 'Text',
FormControlError: 'View',
FormControlErrorIcon: 'View',
FormControlErrorText: 'Text',
Toast: 'View',
ToastTitle: 'Text',
useToast: () => ({
show: jest.fn(),
}),
Heading: 'Text',
}));

jest.mock('expo-random', () => ({
getRandomBytesAsync: jest.fn().mockResolvedValue(new Uint8Array(3)),
}));

jest.mock('expo-secure-store', () => ({
setItemAsync: jest.fn(),
}));

describe('OTPVerification', () => {
it('renders the main text correctly', () => {
const { getByText } = render(<OTPVerification />);
expect(getByText('We sent you an email code')).toBeTruthy();
expect(getByText('We have sent the OTP code to [email protected]')).toBeTruthy();
});

it('renders the image correctly', () => {
const { getByRole } = render(<OTPVerification />);
const image = getByRole('image', { name: 'occupi' });
expect(image).toBeTruthy();
});

it('renders the countdown timer correctly', () => {
const { getByText } = render(<OTPVerification />);
expect(getByText('60 seconds remaining')).toBeTruthy();
});

it('updates the countdown timer every second', () => {
jest.useFakeTimers();
const { getByText } = render(<OTPVerification />);

act(() => {
jest.advanceTimersByTime(1000);
});

expect(getByText('59 seconds remaining')).toBeTruthy();

jest.useRealTimers();
});

it('validates OTP input correctly', async () => {
const { getByText, getByPlaceholderText, findByText } = render(<OTPVerification />);

const input = getByPlaceholderText('');

fireEvent.changeText(input, '1');
fireEvent.changeText(input, '12');
fireEvent.changeText(input, '123');
fireEvent.changeText(input, '1234');
fireEvent.changeText(input, '12345');
fireEvent.changeText(input, '123456');

const button = getByText('Verify');
fireEvent.press(button);

expect(await findByText('OTP must be at least 6 characters in length')).toBeTruthy();
});

it('navigates to the home screen when OTP is correct', async () => {
const { getByText, getAllByPlaceholderText } = render(<OTPVerification />);

const inputs = getAllByPlaceholderText('');
inputs.forEach((input, index) => {
fireEvent.changeText(input, (index + 1).toString());
});

const button = getByText('Verify');
fireEvent.press(button);

await act(async () => {
expect(router.push).toHaveBeenCalledWith('/home');
});
});
});
88 changes: 88 additions & 0 deletions frontend/occupi-mobile3/__tests__/Profile_Test/Profile.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import Profile from '../../screens/Profile/Profile';
import { router } from 'expo-router';
import DateTimePickerModal from 'react-native-modal-datetime-picker';

jest.mock('expo-router', () => ({
router: {
push: jest.fn(),
},
}));

jest.mock('react-native-modal-datetime-picker', () => {
return {
__esModule: true,
default: jest.fn().mockImplementation(({ onConfirm, onCancel }) => null),
};
});

describe('Profile Component', () => {
it('renders correctly', () => {
const { getByText, getByPlaceholderText } = render(<Profile />);

expect(getByText('My account')).toBeTruthy();
expect(getByPlaceholderText('Sabrina Carpenter')).toBeTruthy();
expect(getByText('Save')).toBeTruthy();
});

it('navigates to settings screen on back arrow press', () => {
const { getByTestId } = render(<Profile />);

const backArrow = getByTestId('back-arrow');
fireEvent.press(backArrow);

expect(router.push).toHaveBeenCalledWith('/settings');
});

it('shows date picker on date of birth press', () => {
const { getByText } = render(<Profile />);

const dateOfBirth = getByText(new Date(2000, 6, 7).toLocaleDateString());
fireEvent.press(dateOfBirth);

expect(DateTimePickerModal).toHaveBeenCalledWith(
expect.objectContaining({
isVisible: true,
}),
{}
);
});

it('saves profile with updated details', async () => {
const alertMock = jest.spyOn(global, 'alert').mockImplementation(() => {});

const { getByText, getByPlaceholderText } = render(<Profile />);

fireEvent.changeText(getByPlaceholderText('Sabrina Carpenter'), 'John Doe');
fireEvent.changeText(getByPlaceholderText('[email protected]'), '[email protected]');
fireEvent.changeText(getByPlaceholderText('21546551'), '12345678');
fireEvent.changeText(getByPlaceholderText('011 101 1111'), '987 654 3210');
fireEvent.changeText(getByPlaceholderText('she/her'), 'he/him');

const saveButton = getByText('Save');
fireEvent.press(saveButton);

await waitFor(() => {
expect(alertMock).toHaveBeenCalledWith(
'Profile Saved',
expect.stringContaining('Name: John Doe')
);
});

alertMock.mockRestore();
});

it('updates gender selection correctly', () => {
const { getByText } = render(<Profile />);

fireEvent.press(getByText('Male'));
expect(getByText('Male')).toBeTruthy();

fireEvent.press(getByText('Female'));
expect(getByText('Female')).toBeTruthy();

fireEvent.press(getByText('Other'));
expect(getByText('Other')).toBeTruthy();
});
});
103 changes: 103 additions & 0 deletions frontend/occupi-mobile3/__tests__/Profile_Test/Settings.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Settings from '../../screens/Profile/Settings';
import { useNavigation, NavigationContainer } from '@react-navigation/native';

jest.mock('@react-native-async-storage/async-storage', () => ({
clear: jest.fn(),
}));

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
navigate: jest.fn(),
reset: jest.fn(),
}),
};
});

jest.mock('@gluestack-ui/themed', () => ({
useTheme: jest.fn(() => ({ theme: 'light', toggleTheme: jest.fn() })),
Switch: jest.fn(({ checked, onValueChange }) => (
<switch onValueChange={onValueChange} value={checked} />
)),
Icon: jest.fn(({ name, fill }) => (
<icon name={name} fill={fill} />
)),
Divider: jest.fn(({ style }) => (
<divider style={style} />
)),
Box: jest.fn(({ data, renderItem, ItemSeparatorComponent }) => (
<box>
{data.map((item, index) => (
<React.Fragment key={index}>
{renderItem({ item })}
{index < data.length - 1 && <ItemSeparatorComponent />}
</React.Fragment>
))}
</box>
)),
}));

describe('Settings Component', () => {
const renderSettings = () => (
<NavigationContainer>
<Settings />
</NavigationContainer>
);

it('renders correctly', () => {
const { getByText, getByAltText } = render(renderSettings());

expect(getByAltText('logo')).toBeTruthy();
expect(getByText('Sabrina Carpenter')).toBeTruthy();
expect(getByText('Chief Executive Officer')).toBeTruthy();
expect(getByText('Version 0.1.0')).toBeTruthy();
});

it('navigates to correct screen when list item is pressed', () => {
const { getByText } = render(renderSettings());
const navigation = useNavigation();

fireEvent.press(getByText('My account'));
expect(navigation.navigate).toHaveBeenCalledWith('AccountScreen');

fireEvent.press(getByText('Privacy Policy'));
expect(navigation.navigate).toHaveBeenCalledWith('PrivacyPolicyScreen');
});

it('toggles notifications correctly', () => {
const { getByText, getByValue } = render(renderSettings());

const notificationsToggle = getByValue(true); // assuming true is the initial value
fireEvent(notificationsToggle, 'valueChange', false);

expect(notificationsToggle.props.value).toBe(false);
});

it('toggles dark mode correctly', () => {
const { getByText, getByValue } = render(renderSettings());
const themeToggle = getByValue(false); // assuming false is the initial value

fireEvent(themeToggle, 'valueChange', true);

expect(themeToggle.props.value).toBe(true);
});

it('logs out correctly', async () => {
const { getByText, getByAltText } = render(renderSettings());
const navigation = useNavigation();

fireEvent.press(getByText('Log out'));
fireEvent.press(getByText('OK'));

expect(AsyncStorage.clear).toHaveBeenCalled();
expect(navigation.reset).toHaveBeenCalledWith({
index: 0,
routes: [{ name: 'login' }],
});
});
});
6 changes: 5 additions & 1 deletion frontend/occupi-mobile4/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
"supportsTablet": true,
"infoPlist": {
"NSFaceIDUsageDescription": "This app uses Face ID or Touch ID to confirm bookings."
}
},
"android": {
"permissions": ["USE_BIOMETRIC", "USE_FINGERPRINT"],
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
Expand Down
Loading

0 comments on commit 34c7ce0

Please sign in to comment.