-
-
Notifications
You must be signed in to change notification settings - Fork 615
CI/CD: Refresh Frontend Test Suites #1056
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
1bdf7d0
793bdba
b6c84d3
dbecc0e
38b1f33
96bede0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| import { render, screen } from '@/test-utils'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { Routes, Route, useLocation } from 'react-router'; | ||
| import { AppSidebar } from '../Navigation/Sidebar/AppSidebar'; | ||
| import { SidebarProvider } from '@/components/ui/sidebar'; | ||
| import { ROUTES } from '@/constants/routes'; | ||
|
|
||
| // Display current routes | ||
| const LocationDisplay = () => { | ||
| const location = useLocation(); | ||
| return <div data-testid="location-display">{location.pathname}</div>; | ||
| }; | ||
|
|
||
| // Sidebar + routes display | ||
| const SidebarWithRoutes = () => ( | ||
| <SidebarProvider> | ||
| <AppSidebar /> | ||
| <main> | ||
| <LocationDisplay /> | ||
| <Routes> | ||
| <Route path="/" element={<div>Home Page</div>} /> | ||
| <Route path={ROUTES.HOME} element={<div>Home Page</div>} /> | ||
| <Route path={ROUTES.SETTINGS} element={<div>Settings Page</div>} /> | ||
| <Route path={ROUTES.AI} element={<div>AI Tagging Page</div>} /> | ||
| <Route path={ROUTES.FAVOURITES} element={<div>Favourites Page</div>} /> | ||
| <Route path={ROUTES.VIDEOS} element={<div>Videos Page</div>} /> | ||
| <Route path={ROUTES.ALBUMS} element={<div>Albums Page</div>} /> | ||
| <Route path={ROUTES.MEMORIES} element={<div>Memories Page</div>} /> | ||
| </Routes> | ||
| </main> | ||
| </SidebarProvider> | ||
| ); | ||
|
|
||
| describe('Sidebar', () => { | ||
| describe('Structure Tests', () => { | ||
| test('renders all main navigation links', () => { | ||
| render( | ||
| <SidebarProvider> | ||
| <AppSidebar /> | ||
| </SidebarProvider>, | ||
| ); | ||
|
|
||
| // Verify key navigation items exist | ||
| expect(screen.getByText('Home')).toBeInTheDocument(); | ||
| expect(screen.getByText('AI Tagging')).toBeInTheDocument(); | ||
| expect(screen.getByText('Favourites')).toBeInTheDocument(); | ||
| expect(screen.getByText('Videos')).toBeInTheDocument(); | ||
| expect(screen.getByText('Albums')).toBeInTheDocument(); | ||
| expect(screen.getByText('Memories')).toBeInTheDocument(); | ||
| expect(screen.getByText('Settings')).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Navigation Interaction Tests', () => { | ||
| test('clicking Settings link navigates to /settings', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| expect(screen.getByTestId('location-display')).toHaveTextContent('/'); // start location | ||
|
|
||
| await user.click(screen.getByText('Settings')); // click settings | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/settings', | ||
| ); | ||
| expect(screen.getByText('Settings Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking Home link navigates to /home', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/settings'] }); | ||
|
|
||
| // start location | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/settings', | ||
| ); | ||
|
|
||
| await user.click(screen.getByText('Home')); // click home | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent('/home'); | ||
| expect(screen.getByText('Home Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking AI Tagging link navigates to /ai-tagging', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| await user.click(screen.getByText('AI Tagging')); // click ai-tagging | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/ai-tagging', | ||
| ); | ||
| expect(screen.getByText('AI Tagging Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking Favourites link navigates to /favourites', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| await user.click(screen.getByText('Favourites')); // click favourites | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/favourites', | ||
| ); | ||
| expect(screen.getByText('Favourites Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking Videos link navigates to /videos', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| await user.click(screen.getByText('Videos')); // click videos | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/videos', | ||
| ); | ||
| expect(screen.getByText('Videos Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking Albums link navigates to /albums', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| await user.click(screen.getByText('Albums')); // click albums | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/albums', | ||
| ); | ||
| expect(screen.getByText('Albums Page')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('clicking Memories link navigates to /memories', async () => { | ||
| const user = userEvent.setup(); | ||
| render(<SidebarWithRoutes />, { initialRoutes: ['/'] }); | ||
|
|
||
| await user.click(screen.getByText('Memories')); // click memories | ||
|
|
||
| // verify | ||
| expect(screen.getByTestId('location-display')).toHaveTextContent( | ||
| '/memories', | ||
| ); | ||
| expect(screen.getByText('Memories Page')).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { render, screen } from '@/test-utils'; | ||
| import { Home } from '../Home/Home'; | ||
| import Settings from '../SettingsPage/Settings'; | ||
|
|
||
| describe('Page Sanity Tests', () => { | ||
| describe('Home Page', () => { | ||
| test('renders home page structure', async () => { | ||
| render(<Home />); | ||
| expect( | ||
| await screen.findByText( | ||
| /Image Gallery|No Images to Display|Loading images/i, | ||
| ), | ||
| ).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Settings Page', () => { | ||
| test('renders settings page sections', () => { | ||
| render(<Settings />); | ||
|
|
||
| expect(screen.getByText('Folder Management')).toBeInTheDocument(); | ||
| expect(screen.getByText('User Preferences')).toBeInTheDocument(); | ||
| expect(screen.getByText('Application Controls')).toBeInTheDocument(); | ||
|
|
||
| expect( | ||
| screen.getByRole('button', { name: /Check for Updates/i }), | ||
| ).toBeInTheDocument(); | ||
| expect(screen.getByText('GPU Acceleration')).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import React, { ReactElement } from 'react'; | ||
| import { render, RenderOptions } from '@testing-library/react'; | ||
| import { Provider } from 'react-redux'; | ||
| import { ThemeProvider } from '@/contexts/ThemeContext'; | ||
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
| import { MemoryRouter } from 'react-router'; | ||
| import { configureStore } from '@reduxjs/toolkit'; | ||
| import { rootReducer, RootState } from '@/app/store'; | ||
|
|
||
| interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> { | ||
| preloadedState?: Partial<RootState>; | ||
| store?: ReturnType<typeof configureStore>; | ||
| initialRoutes?: string[]; | ||
|
Comment on lines
+10
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, check the structure and read the test-utils.tsx file
git ls-files frontend/src/ | head -20Repository: AOSSIE-Org/PictoPy Length of output: 926 🏁 Script executed: # Check file sizes before reading
wc -l frontend/src/test-utils.tsx
wc -l frontend/src/app/store.tsRepository: AOSSIE-Org/PictoPy Length of output: 121 🏁 Script executed: # Read the test-utils.tsx file
cat -n frontend/src/test-utils.tsxRepository: AOSSIE-Org/PictoPy Length of output: 2923 🏁 Script executed: # Check for RootState export in store.ts
rg -n "export.*RootState" frontend/src/app/store.tsRepository: AOSSIE-Org/PictoPy Length of output: 123 🏁 Script executed: # Check for ReturnType<typeof configureStore> usage in test-utils.tsx
rg -n "ReturnType<typeof configureStore>" frontend/src/test-utils.tsxRepository: AOSSIE-Org/PictoPy Length of output: 156 🏁 Script executed: # Search for 'as any' casts in test files
rg -n "as any" frontend/src/components/__tests__/ --type ts --type tsxRepository: AOSSIE-Org/PictoPy Length of output: 89 🏁 Script executed: # Check if EnhancedStore is available from `@reduxjs/toolkit`
rg -n "EnhancedStore" frontend/src/ --type ts --type tsxRepository: AOSSIE-Org/PictoPy Length of output: 89 🏁 Script executed: # Try searching for 'as any' without the type filter
rg -n "as any" frontend/src/components/__tests__/Repository: AOSSIE-Org/PictoPy Length of output: 176 🏁 Script executed: # Check if InfoDialog.test.tsx exists and examine it
cat -n frontend/src/components/__tests__/InfoDialog.test.tsxRepository: AOSSIE-Org/PictoPy Length of output: 2946 🏁 Script executed: # Check store.ts to understand the full context
cat -n frontend/src/app/store.tsRepository: AOSSIE-Org/PictoPy Length of output: 1294 🏁 Script executed: # Search for 'as any' more broadly in all test files
rg -n "as any" frontend/src/ -g "*.test.*"Repository: AOSSIE-Org/PictoPy Length of output: 176 🏁 Script executed: # Check `@reduxjs/toolkit` exports to verify if EnhancedStore is available
# by looking at imports in the codebase
rg -n "from '@reduxjs/toolkit'" frontend/src/ | head -20Repository: AOSSIE-Org/PictoPy Length of output: 1152 🏁 Script executed: # Check if EnhancedStore or similar types are mentioned anywhere in the codebase
rg -n "EnhancedStore|UnknownAction" frontend/src/Repository: AOSSIE-Org/PictoPy Length of output: 44 🏁 Script executed: # Check package.json to see `@reduxjs/toolkit` version
cat -n package.json | grep -A 5 -B 5 "reduxjs"Repository: AOSSIE-Org/PictoPy Length of output: 44 🏁 Script executed: # Search for Redux Toolkit type documentation patterns in the codebase
rg -n "ReturnType.*store\." frontend/src/Repository: AOSSIE-Org/PictoPy Length of output: 149 🌐 Web query:
💡 Result:
Citations:
Tighten test store typing to Using Replace with explicit 🔧 Suggested typing refinement import { configureStore } from '@reduxjs/toolkit';
+import type { EnhancedStore, UnknownAction } from '@reduxjs/toolkit';
import { rootReducer, RootState } from '@/app/store';
+type TestStore = EnhancedStore<RootState, UnknownAction>;
+
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
preloadedState?: Partial<RootState>;
- store?: ReturnType<typeof configureStore>;
+ store?: TestStore;
initialRoutes?: string[];
}
@@
children: React.ReactNode;
- store: ReturnType<typeof configureStore>;
+ store: TestStore;
queryClient: QueryClient;
initialRoutes?: string[];
}) => {
@@
- const testStore =
+ const testStore: TestStore =
store ??
configureStore({
reducer: rootReducer,
preloadedState,
});Applies to lines 12, 23, 48, and the return statement (lines 80-84). Aligns with coding guideline: "Avoid 'any', use explicit types." 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| const AllTheProviders = ({ | ||
| children, | ||
| store, | ||
| initialRoutes = ['/'], | ||
| }: { | ||
| children: React.ReactNode; | ||
| store: ReturnType<typeof configureStore>; | ||
| initialRoutes?: string[]; | ||
| }) => { | ||
| const queryClient = new QueryClient({ | ||
| defaultOptions: { | ||
| queries: { | ||
| retry: false, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| return ( | ||
| <Provider store={store}> | ||
| <ThemeProvider> | ||
| <QueryClientProvider client={queryClient}> | ||
| <MemoryRouter initialEntries={initialRoutes}>{children}</MemoryRouter> | ||
| </QueryClientProvider> | ||
| </ThemeProvider> | ||
| </Provider> | ||
| ); | ||
| }; | ||
|
|
||
| const customRender = (ui: ReactElement, options?: CustomRenderOptions) => { | ||
| const { store, initialRoutes, preloadedState, ...renderOptions } = | ||
| options || {}; | ||
|
|
||
| const testStore = | ||
| store ?? | ||
| configureStore({ | ||
| reducer: rootReducer, | ||
| preloadedState, | ||
| }); | ||
|
|
||
| return render(ui, { | ||
| wrapper: (props) => ( | ||
| <AllTheProviders | ||
| {...props} | ||
| store={testStore} | ||
| initialRoutes={initialRoutes} | ||
| /> | ||
| ), | ||
| ...renderOptions, | ||
| }); | ||
| }; | ||
|
|
||
| // Re-export everything | ||
| export * from '@testing-library/react'; | ||
| export { customRender as render }; | ||
Uh oh!
There was an error while loading. Please reload this page.