Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library Page Tests #229

Merged
merged 4 commits into from
Feb 9, 2025
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
303 changes: 303 additions & 0 deletions __tests__/Library.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import React from 'react';
ademDurakovic marked this conversation as resolved.
Show resolved Hide resolved
ademDurakovic marked this conversation as resolved.
Show resolved Hide resolved
import { render, fireEvent, waitFor, act } from '@testing-library/react-native';
import Library from '../lib/screens/Library';
import { useTheme } from '../lib/components/ThemeProvider';
import ApiService from '../lib/utils/api_calls';
import ToastMessage from 'react-native-toast-message';
import { onAuthStateChanged } from 'firebase/auth';
import configureStore from 'redux-mock-store';
import { AddNoteProvider } from '../lib/context/AddNoteContext';
import moxios from 'moxios'
import { User } from '../lib/models/user_class';


jest.mock('firebase/database', () => ({
getDatabase: jest.fn(),
}));

jest.mock('firebase/auth', () => ({
getAuth: jest.fn(),
initializeAuth: jest.fn(),
getReactNativePersistence: jest.fn(),
onAuthStateChanged: jest.fn(),
}));

jest.mock('expo-font', () => ({
loadAsync: jest.fn(() => Promise.resolve()),
isLoaded: jest.fn(() => true),
}));

jest.mock('react-native/Libraries/Image/Image', () => ({
...jest.requireActual('react-native/Libraries/Image/Image'),
resolveAssetSource: jest.fn(() => ({ uri: 'mocked-asset-uri' })),
}));

jest.mock('react-native/Libraries/Settings/NativeSettingsManager', () => ({
settings: {},
setValues: jest.fn(),
getConstants: jest.fn(() => ({
settings: {},
})),
}));

//mock carousel
jest.mock('react-native-reanimated-carousel', () => {
const React = require('react');
const { View } = require('react-native');
return (props) => <View>{props.children}</View>;
});

const mockStore = configureStore([]);
const store = mockStore({
navigation: {
navState: 'more',
},
theme: {
darkMode: false,
},
});

const mockToggleDarkmode = jest.fn();

jest.mock('../lib/components/ThemeProvider', () => ({
useTheme: jest.fn(() => ({
theme: {
primaryColor: '#ffffff',
text: '#000000',
secondaryColor: '#f0f0f0',
logout: '#ff0000',
logoutText: '#ffffff',
},
isDarkmode: false,
toggleDarkmode: mockToggleDarkmode,
})),
}));

// Mock expo-location module with TypeScript type support
jest.mock('expo-location', () => ({
getForegroundPermissionsAsync: jest.fn(),
requestForegroundPermissionsAsync: jest.fn(),
getCurrentPositionAsync: jest.fn(),
}));


// Silence console warnings during the test
beforeEach(() => {
jest.clearAllMocks();

jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
jest.spyOn(console, 'warn').mockImplementation(() => {});
moxios.install()
});

afterEach(() => {
moxios.uninstall()
});

describe('Library Component', () => {
it('renders without crashing', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} }; // Mock route prop
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);
await waitFor(() => expect(getByTestId('Library')).toBeTruthy());
ademDurakovic marked this conversation as resolved.
Show resolved Hide resolved
});

it('renders search bar', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} }; // Mock route prop
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);
await waitFor(() => expect(getByTestId('SearchBar')).toBeTruthy());
});

it('renders filter bar', async () => {
ademDurakovic marked this conversation as resolved.
Show resolved Hide resolved
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} }; // Mock route prop
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);
await waitFor(() => expect(getByTestId('Filter')).toBeTruthy());
});

it('renders the account icon (top left) and navigates to AccountPage when clicked', async () => {
// Provide a navigation object with a navigate function
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

// Render the Library component with the mocked navigation
const { getByTestId } = render(
<Library navigation={navigationMock} route={routeMock} />
);

// Wait for the account component to appear (using the testID we added)
const accountComponent = await waitFor(() => getByTestId('account-page'));
expect(accountComponent).toBeTruthy();

// Simulate a press event on the account component
fireEvent.press(accountComponent);

// Assert that navigation.navigate was called with "AccountPage"
expect(navigationMock.navigate).toHaveBeenCalledWith("AccountPage");

});

it('Toggles Search Bar and clicks it', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

// Optionally, mock the ApiService to avoid network calls
jest.spyOn(ApiService, 'fetchMessages').mockResolvedValue([]);

// Render the Library component
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);

// Query the search icon button using its testID and simulate a press event.
const searchButton = await waitFor(() => getByTestId('search-button'));
fireEvent.press(searchButton);
expect(searchButton).toBeTruthy();
});

it('renders the user name "Adem" regardless of the greeting', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

// Mock the asynchronous getName method to always return "Adem"
const userInstance = User.getInstance();
jest.spyOn(userInstance, 'getName').mockResolvedValue('Adem');

const { getByText } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);

// Wait for the asynchronous update and check if "Adem" appears
await waitFor(() => {
expect(getByText(/Adem/)).toBeTruthy();
});
});

it('shows the close button when the search bar is opened', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

// Optionally, mock the ApiService.fetchMessages to avoid network calls.
jest.spyOn(ApiService, 'fetchMessages').mockResolvedValue([]);

// Render the Library component.
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);

// Query the search button using its testID.
const searchButton = await waitFor(() => getByTestId('search-button'));
expect(searchButton).toBeTruthy();

// Simulate a press event on the search button.
fireEvent.press(searchButton);

// Now, wait for the close button (the X button) to appear.
const closeButton = await waitFor(() => getByTestId('close-button'));
expect(closeButton).toBeTruthy();
});

it('renders the "Library" title at the top', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

const { getByText } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);

await waitFor(() => {
expect(getByText("Library")).toBeTruthy();
});
});

it('renders the notes list', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} }; // Mock route prop
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);
await waitFor(() => expect(getByTestId('notes-list')).toBeTruthy());
});

it('wishes the user', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} }; // Mock route prop
const { getByTestId } = render(
<Library navigation={navigationMock as any} route={routeMock as any} />
);
await waitFor(() => expect(getByTestId('greeting-component')).toBeTruthy());
});

it('renders the LottieView empty state when no notes are loaded', async () => {
const navigationMock = {
navigate: jest.fn(),
goBack: jest.fn(),
push: jest.fn(),
};
const routeMock = { params: {} };

// Simulate an API call that returns an empty array (no notes)
jest.spyOn(ApiService, 'fetchMessages').mockResolvedValue([]);

// Render the Library component
const { getByText } = render(
<Library navigation={navigationMock} route={routeMock} />
);

// Wait for the asynchronous update and verify that the empty state text is displayed
await waitFor(() => {
expect(getByText('No Results Found')).toBeTruthy();
});
});

});


23 changes: 12 additions & 11 deletions lib/screens/Library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const Library = ({ navigation, route }) => {
const [isModalVisible, setModalVisible] = useState(false);
const [userName, setUserName] = useState('')
const { theme, isDarkmode } = useTheme();
const { setNavigateToAddNote } = useAddNoteContext();
const [isSearchVisible, setIsSearchVisible] = useState(false);
const [isSortOpened, setIsSortOpened] = useState(false);
const [selectedSortOption, setSelectedSortOption] = useState(1);
Expand Down Expand Up @@ -289,7 +288,7 @@ const Library = ({ navigation, route }) => {
}

return (
<View style={{ flex: 1, backgroundColor: isDarkmode ? 'black' : '#e4e4e4' }}>
<View testID = "Library" style={{ flex: 1, backgroundColor: isDarkmode ? 'black' : '#e4e4e4' }}>
<StatusBar translucent backgroundColor="transparent" />
<View style={styles(theme, width).container}>
<View style={styles(theme, width).topView}>
Expand All @@ -304,7 +303,7 @@ const Library = ({ navigation, route }) => {
}}
>
<View style={styles(theme, width).userAccountAndPageTitle}>
<TouchableOpacity
<TouchableOpacity testID="account-page"
style={[
styles(theme, width).userPhoto,
{ backgroundColor: theme.black },
Expand All @@ -318,7 +317,7 @@ const Library = ({ navigation, route }) => {
<Text style={styles(theme, width).pageTitle}>Library</Text>
</View>

<View style={styles(theme, width).userWishContainer}>
<View testID="greeting-component" style={styles(theme, width).userWishContainer}>
<Greeting />
<Text style={styles(theme, width).userName}>{userName}</Text>
</View>
Expand All @@ -327,7 +326,7 @@ const Library = ({ navigation, route }) => {

</View>

<View style={[styles(theme, width).toolContainer, { marginHorizontal: 20 }]}>
<View testID= "Filter" style={[styles(theme, width).toolContainer, { marginHorizontal: 20 }]}>
ademDurakovic marked this conversation as resolved.
Show resolved Hide resolved
{
!isSearchVisible && (
<View>
Expand All @@ -347,7 +346,7 @@ const Library = ({ navigation, route }) => {
</View>
)
}
<View style={[styles(theme, width).searchParentContainer, { width: isSearchVisible ? '95%' : 40 }]}>
<View testID="SearchBar" style={[styles(theme, width).searchParentContainer, { width: isSearchVisible ? '95%' : 40 }]}>

{/* Search Container */}
{isSearchVisible && (
Expand Down Expand Up @@ -376,14 +375,16 @@ const Library = ({ navigation, route }) => {
{
isSearchVisible ? (
<View style={[styles(theme, width).seachIcon, { marginTop: -25 }]}>
<TouchableOpacity onPress={toggleSearchBar}>
<Ionicons name='close' size={25} />
{/* Add testID to the close button */}
<TouchableOpacity onPress={toggleSearchBar} testID="close-button">
<Ionicons name="close" size={25} />
</TouchableOpacity>
</View>
) : (
<View style={styles(theme, width).seachIcon}>
<TouchableOpacity onPress={toggleSearchBar}>
<Ionicons name='search' size={25} />
{/* Add testID to the search button */}
<TouchableOpacity onPress={toggleSearchBar} testID="search-button">
<Ionicons name="search" size={25} />
</TouchableOpacity>
</View>
)
Expand All @@ -393,7 +394,7 @@ const Library = ({ navigation, route }) => {
</View>

</View>
<View style={styles(theme, width).scrollerBackgroundColor}>
<View testID = "notes-list" style={styles(theme, width).scrollerBackgroundColor}>
{rendering ? <NoteSkeleton /> : renderList(notes)}
</View>
{isSortOpened && isSearchVisible == false && <View style={{
Expand Down