Skip to content

Commit

Permalink
Merge pull request #229 from oss-slu/library-tests
Browse files Browse the repository at this point in the history
Library Page Tests
  • Loading branch information
rcAsironman authored Feb 9, 2025
2 parents d8474a5 + b3d0432 commit b8f280b
Show file tree
Hide file tree
Showing 2 changed files with 315 additions and 11 deletions.
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';
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());
});

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 () => {
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 @@ -321,7 +320,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 @@ -330,7 +329,7 @@ const Library = ({ navigation, route }) => {

</View>

<View style={[styles(theme, width).toolContainer, { marginHorizontal: 20 }]}>
<View testID= "Filter" style={[styles(theme, width).toolContainer, { marginHorizontal: 20 }]}>
{
!isSearchVisible && (
<View>
Expand All @@ -350,7 +349,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 @@ -379,14 +378,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 @@ -396,7 +397,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

0 comments on commit b8f280b

Please sign in to comment.