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

feat: group pagination #92

Merged
merged 5 commits into from
Nov 27, 2024
Merged

feat: group pagination #92

merged 5 commits into from
Nov 27, 2024

Conversation

chalabi2
Copy link
Collaborator

@chalabi2 chalabi2 commented Nov 26, 2024

partially fixes #15

Summary by CodeRabbit

  • New Features
    • Introduced pagination for the "Your Groups" component, allowing users to view groups in manageable sections.
    • Added navigation controls for easy page access, including previous, next, and numbered buttons.
    • Added a new useIsMobile hook to determine if the user is on a mobile device, enhancing responsive design.
  • Improvements
    • Adjusted loading skeleton to display the correct number of rows based on the new pagination settings.
  • Tests
    • Implemented tests for pagination functionality, ensuring correct navigation and rendering of groups per page.

Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Walkthrough

The changes implement pagination in the YourGroups component of myGroups.tsx. A new state variable currentPage is introduced to track the current page, with a dynamic pageSize set to 8 groups per page. The filtered groups are displayed using a sliced array paginatedGroups, and a pagination control section is added for navigation. The loading skeleton is adjusted to match the new page size, ensuring the component maintains its structure while enhancing user interaction with group listings.

Changes

File Path Change Summary
components/groups/components/myGroups.tsx Added pagination functionality, including currentPage, pageSize, and totalPages. Updated rendering logic to use paginatedGroups and adjusted loading skeleton rows from 10 to 8. Added pagination controls for navigation.
hooks/index.tsx Added export for the useIsMobile hook from ./useIsMobile.
hooks/useIsMobile.ts Introduced useIsMobile hook to determine if the viewport is mobile based on a breakpoint.
components/groups/components/tests/myGroups.test.tsx Added tests for pagination functionality, including rendering controls, navigation, and item counts.

Assessment against linked issues

Objective Addressed Explanation
Implement pagination for groups

🐰 "In the garden where groups now play,
Pagination hops in, brightening the day!
With buttons to skip and pages to see,
Each click brings joy, oh what glee!
Eight friends at a time, in a row they’ll stay,
A hop, a skip, let’s jump to the next display!" 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@chalabi2 chalabi2 mentioned this pull request Nov 26, 2024
Copy link

codecov bot commented Nov 26, 2024

Codecov Report

Attention: Patch coverage is 67.77778% with 29 lines in your changes missing coverage. Please review.

Project coverage is 59.42%. Comparing base (ef0a56a) to head (bc023bb).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
hooks/useIsMobile.ts 11.53% 23 Missing ⚠️
components/groups/components/myGroups.tsx 90.47% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #92      +/-   ##
==========================================
+ Coverage   59.37%   59.42%   +0.04%     
==========================================
  Files         145      146       +1     
  Lines       14100    14187      +87     
==========================================
+ Hits         8372     8430      +58     
- Misses       5728     5757      +29     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
components/groups/components/myGroups.tsx (1)

Line range hint 34-58: Consider performance and flexibility improvements

The pagination implementation is correct, but consider these enhancements:

  1. Memoize totalPages and paginatedGroups calculations to prevent unnecessary recalculations on re-renders
  2. Make pageSize configurable via props to improve component reusability
+ const pageSize = props.pageSize ?? 8;
+ const totalPages = useMemo(() => 
+   Math.ceil(filteredGroups.length / pageSize),
+   [filteredGroups.length, pageSize]
+ );
+ const paginatedGroups = useMemo(() =>
+   filteredGroups.slice((currentPage - 1) * pageSize, currentPage * pageSize),
+   [filteredGroups, currentPage, pageSize]
+ );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 263a5e1 and 89ead32.

📒 Files selected for processing (1)
  • components/groups/components/myGroups.tsx (6 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
components/groups/components/myGroups.tsx

[warning] 234-235: components/groups/components/myGroups.tsx#L234-L235
Added lines #L234 - L235 were not covered by tests


[warning] 244-266: components/groups/components/myGroups.tsx#L244-L266
Added lines #L244 - L266 were not covered by tests


[warning] 271-272: components/groups/components/myGroups.tsx#L271-L272
Added lines #L271 - L272 were not covered by tests

🔇 Additional comments (3)
components/groups/components/myGroups.tsx (3)

Line range hint 174-214: LGTM! Loading skeleton matches page size

The skeleton rows count has been correctly updated to match the pagination size of 8, maintaining consistent UI during loading states.


Line range hint 214-227: LGTM! Pagination correctly applied to group rendering

The switch to paginatedGroups is implemented correctly, maintaining all existing functionality while adding pagination support.


34-35: Handle pagination edge cases

Consider adding logic to handle edge cases when the total number of items changes:

  1. Reset to page 1 when search term changes
  2. Adjust current page when the last page becomes empty due to filtering

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
hooks/useIsMobile.ts (2)

12-14: Consider debouncing the resize handler for better performance.

The current implementation might trigger frequent rerenders during resize events. Consider adding debouncing to optimize performance.

 import { useState, useEffect } from 'react';
+import debounce from 'lodash/debounce';
 
 const useIsMobile = (breakpoint: number = 1024) => {
   const [isMobile, setIsMobile] = useState(false);
 
   useEffect(() => {
     if (typeof window === 'undefined') {
       return;
     }
 
-    const checkIsMobile = () => {
+    const checkIsMobile = debounce(() => {
       setIsMobile(window.innerWidth <= breakpoint);
-    };
+    }, 150);

26-29: LGTM! Consider extending for broader device detection.

The hook effectively serves its current purpose. For future scalability, consider extending it to support multiple breakpoints or device types (e.g., tablet, desktop) if needed by other components.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 89ead32 and 741413b.

📒 Files selected for processing (3)
  • components/groups/components/myGroups.tsx (7 hunks)
  • hooks/index.tsx (1 hunks)
  • hooks/useIsMobile.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
components/groups/components/myGroups.tsx

[warning] 238-239: components/groups/components/myGroups.tsx#L238-L239
Added lines #L238 - L239 were not covered by tests


[warning] 248-270: components/groups/components/myGroups.tsx#L248-L270
Added lines #L248 - L270 were not covered by tests


[warning] 275-276: components/groups/components/myGroups.tsx#L275-L276
Added lines #L275 - L276 were not covered by tests

hooks/useIsMobile.ts

[warning] 9-9: hooks/useIsMobile.ts#L9
Added line #L9 was not covered by tests

🔇 Additional comments (9)
hooks/index.tsx (2)

8-8: LGTM! Export syntax is correct.

The named export with default import syntax is the appropriate pattern for re-exporting the default export from useIsMobile.


8-8: Consider extracting the breakpoint value to a shared constant.

The useIsMobile hook uses a hardcoded breakpoint of 1024px. To maintain consistency across the application, consider extracting this value to a shared constants file.

Let's check if there are other breakpoint values in use:

✅ Verification successful

Let me analyze the search results to verify if there are other breakpoint values in use.


Let me check if there are any other files using this hook or defining breakpoint values.


Let me check the contents of the constants file and theme-related files to see if there are any existing breakpoint definitions.


Consider extracting the breakpoint value to a shared constant in the Tailwind configuration.

The useIsMobile hook uses a default breakpoint of 1024px, which aligns with Tailwind's default lg breakpoint. Since the project already uses Tailwind CSS with a custom configuration (tailwind.config.js) that defines breakpoints under the screens property, it would be more maintainable to:

  1. Add the mobile breakpoint to the Tailwind screens configuration
  2. Import and use this value in the useIsMobile hook

This ensures consistency with the project's responsive design system defined in Tailwind.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for pixel-based breakpoints in the codebase
# Expected: Find other breakpoint definitions to ensure consistency

# Look for pixel values that might be breakpoints
rg -g '*.{ts,tsx,css,scss}' '\d+px' 

# Look for potential breakpoint-related constants
rg -g '*.{ts,tsx}' '(breakpoint|BREAKPOINT|screen|SCREEN|viewport|VIEWPORT)'

Length of output: 87085


Script:

# Let's check the useIsMobile hook implementation
cat hooks/useIsMobile.ts

Length of output: 665


Script:

# Search for useIsMobile hook usage
rg "useIsMobile"

# Search for any breakpoint-related constants or configuration files
fd -e ts -e tsx -e js -e jsx "breakpoint|config|constant|theme"

Length of output: 532


Script:

# Check contents of potential files containing breakpoint definitions
cat utils/constants.ts
cat tailwind.config.js
cat contexts/theme.tsx

Length of output: 9124

hooks/useIsMobile.ts (3)

1-3: LGTM! Clean imports and well-structured hook signature.

The hook follows React naming conventions and provides a sensible default breakpoint value.


16-24: LGTM! Proper event listener management.

The implementation follows React best practices with:

  • Proper event listener cleanup
  • Correct dependencies array
  • Initial state setup

4-11: Add test coverage for SSR compatibility check.

While the SSR check is correctly implemented, it lacks test coverage. Consider adding tests for both server and client-side scenarios.

Here's a script to verify other SSR checks in the codebase:

Would you like me to help generate test cases for this hook? The tests should cover:

  1. Server-side rendering scenario
  2. Client-side initialization
  3. Breakpoint changes
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 9-9: hooks/useIsMobile.ts#L9
Added line #L9 was not covered by tests

components/groups/components/myGroups.tsx (4)

Line range hint 23-39: LGTM! Clean implementation of pagination state management

The pagination state setup is well-structured with proper mobile responsiveness consideration.


Line range hint 178-218: LGTM! Consistent loading state with page size

The loading skeleton count now matches the pagination size, providing a consistent user experience during loading.


345-346: LGTM! Clean code improvement


57-61: Verify pagination with filtered results

The pagination implementation looks correct, but let's verify its integration with the search functionality.

✅ Verification successful

Pagination and filtering implementation verified successfully

The implementation correctly handles both filtering and pagination:

  • The filtering is applied first using filteredGroups array, which filters based on group title
  • Pagination is then correctly applied to the filtered results using slice on the filtered array
  • The test file confirms proper functionality with search and group selection tests
  • The pagination controls are properly implemented with accessibility considerations
  • The component maintains the correct order of operations: filter first, then paginate
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the component handles both filtering and pagination correctly
# Look for any direct manipulation of filteredGroups array that might affect pagination

# Search for filteredGroups usage
rg "filteredGroups" --type tsx -C 3

# Check for any existing tests related to filtering
rg -t test "filteredGroups|searchTerm|setSearchTerm" 

Length of output: 147


Script:

#!/bin/bash
# Let's try with more generic file extensions and patterns

# Search for filteredGroups usage in all possible React/TypeScript files
rg "filteredGroups" -t ts -t tsx -t jsx -t js -C 3

# Look for search/filter related logic
rg "setSearchTerm|searchTerm|filterGroups" -t ts -t tsx -t jsx -t js -C 3

# Find test files related to groups
fd ".*\.(test|spec)\.(ts|tsx|js|jsx)$" | grep -i "group"

Length of output: 1277


Script:

#!/bin/bash
# Let's examine the myGroups test file and component implementation

# Check myGroups component implementation
cat components/groups/components/myGroups.tsx

# Check myGroups test file
cat components/groups/components/__tests__/myGroups.test.tsx

Length of output: 21878

Comment on lines 232 to 285
{totalPages > 1 && (
<div
className="flex items-center justify-center gap-2"
onClick={e => e.stopPropagation()}
>
<button
onClick={e => {
e.stopPropagation();
setCurrentPage(prev => Math.max(1, prev - 1));
}}
disabled={currentPage === 1 || isLoading}
className="p-2 hover:bg-[#FFFFFF1A] rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>

{[...Array(totalPages)].map((_, index) => {
const pageNum = index + 1;
if (
pageNum === 1 ||
pageNum === totalPages ||
(pageNum >= currentPage - 1 && pageNum <= currentPage + 1)
) {
return (
<button
key={pageNum}
onClick={e => {
e.stopPropagation();
setCurrentPage(pageNum);
}}
className={`w-8 h-8 flex items-center justify-center rounded-lg transition-colors
${currentPage === pageNum ? 'bg-[#FFFFFF1A] text-white' : 'hover:bg-[#FFFFFF1A]'}`}
>
{pageNum}
</button>
);
} else if (pageNum === currentPage - 2 || pageNum === currentPage + 2) {
return <span key={pageNum}>...</span>;
}
return null;
})}

<button
onClick={e => {
e.stopPropagation();
setCurrentPage(prev => Math.min(totalPages, prev + 1));
}}
disabled={currentPage === totalPages || isLoading}
className="p-2 hover:bg-[#FFFFFF1A] rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add test coverage for pagination logic

Please add test coverage for:

  • Page navigation (prev/next buttons)
  • Page number selection
  • Ellipsis rendering logic
  • Disabled states handling

Would you like me to help generate comprehensive test cases for the pagination functionality?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 238-239: components/groups/components/myGroups.tsx#L238-L239
Added lines #L238 - L239 were not covered by tests


[warning] 248-270: components/groups/components/myGroups.tsx#L248-L270
Added lines #L248 - L270 were not covered by tests


[warning] 275-276: components/groups/components/myGroups.tsx#L275-L276
Added lines #L275 - L276 were not covered by tests


🛠️ Refactor suggestion

Enhance accessibility of pagination controls

While the pagination implementation is solid, it needs accessibility improvements:

 <div
   className="flex items-center justify-center gap-2"
+  role="navigation"
+  aria-label="Group list pagination"
   onClick={e => e.stopPropagation()}
 >
   <button
     onClick={e => {
       e.stopPropagation();
       setCurrentPage(prev => Math.max(1, prev - 1));
     }}
     disabled={currentPage === 1 || isLoading}
     className="p-2 hover:bg-[#FFFFFF1A] rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+    aria-label="Go to previous page"
   >
     ‹
   </button>
   {/* ... */}
   <button
     onClick={e => {
       e.stopPropagation();
       setCurrentPage(prev => Math.min(totalPages, prev + 1));
     }}
     disabled={currentPage === totalPages || isLoading}
     className="p-2 hover:bg-[#FFFFFF1A] rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+    aria-label="Go to next page"
   >
     ›
   </button>

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 238-239: components/groups/components/myGroups.tsx#L238-L239
Added lines #L238 - L239 were not covered by tests


[warning] 248-270: components/groups/components/myGroups.tsx#L248-L270
Added lines #L248 - L270 were not covered by tests


[warning] 275-276: components/groups/components/myGroups.tsx#L275-L276
Added lines #L275 - L276 were not covered by tests

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (4)
components/groups/components/__tests__/myGroups.test.tsx (2)

51-66: Consider improving mock data setup for better maintainability.

The mock data setup could be enhanced in the following ways:

-const mockPropsWithManyGroups = {
+// Generate test data for pagination (8 per page + 4 overflow to test multiple pages)
+const GROUPS_PER_PAGE = 8;
+const TOTAL_TEST_GROUPS = GROUPS_PER_PAGE + 4;
+const mockPropsWithManyGroups = {
   groups: {
-    groups: Array(12)
-      .fill(null)
-      .map((_, index) => ({
+    groups: Array.from({ length: TOTAL_TEST_GROUPS }, (_, index) => ({
         id: `${index + 1}`,
         ipfsMetadata: { title: `Group ${index + 1}` },
         policies: [{ address: `policy${index + 1}`, decision_policy: { threshold: '1' } }],
         admin: `admin${index + 1}`,
         members: [{ member: { address: `member${index + 1}` } }],
         total_weight: '1',
       })),
   },
-  proposals: {},
+  proposals: Object.fromEntries(
+    Array.from({ length: TOTAL_TEST_GROUPS }, (_, i) => [`policy${i + 1}`, []])
+  ),
   isLoading: false,
 };

142-142: Replace magic number with a constant.

The page size (8) should be defined as a constant to maintain consistency with the component implementation.

-const totalPages = Math.ceil(mockPropsWithManyGroups.groups.groups.length / 8);
+const DESKTOP_PAGE_SIZE = 8;
+const totalPages = Math.ceil(mockPropsWithManyGroups.groups.groups.length / DESKTOP_PAGE_SIZE);
components/groups/components/myGroups.tsx (2)

38-38: Consider defining pageSize as a constant

Move the pageSize definition outside the component to improve maintainability and reusability.

+const DESKTOP_PAGE_SIZE = 8;
+const MOBILE_PAGE_SIZE = 6;

export function YourGroups({
  // ...
}) {
-  const pageSize = isMobile ? 6 : 8;
+  const pageSize = isMobile ? MOBILE_PAGE_SIZE : DESKTOP_PAGE_SIZE;

239-293: Add keyboard navigation support for pagination

Enhance user experience by adding keyboard navigation support for the pagination controls.

 <div
   className="flex items-center justify-center gap-2"
   onClick={e => e.stopPropagation()}
   role="navigation"
   aria-label="Pagination"
+  onKeyDown={e => {
+    if (e.key === 'ArrowLeft' && currentPage > 1) {
+      setCurrentPage(prev => Math.max(1, prev - 1));
+    } else if (e.key === 'ArrowRight' && currentPage < totalPages) {
+      setCurrentPage(prev => Math.min(totalPages, prev + 1));
+    }
+  }}
+  tabIndex={0}
 >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 741413b and 12a798b.

📒 Files selected for processing (2)
  • components/groups/components/__tests__/myGroups.test.tsx (2 hunks)
  • components/groups/components/myGroups.tsx (7 hunks)
🔇 Additional comments (1)
components/groups/components/myGroups.tsx (1)

Line range hint 1-356: Implementation looks good with suggested improvements

The pagination implementation is well-structured and includes proper accessibility attributes. Consider implementing the suggested improvements for better maintainability and user experience.

Comment on lines +107 to +166
describe('Pagination', () => {
test('renders pagination controls when there are more items than page size', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

expect(screen.getByRole('navigation', { name: /pagination/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /next page/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /previous page/i })).toBeInTheDocument();
});

test('pagination controls navigate between pages correctly', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

// Should start with page 1
expect(screen.getByRole('button', { name: 'Page 1', current: 'page' })).toBeInTheDocument();

// Click next page
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
expect(screen.getByRole('button', { name: 'Page 2', current: 'page' })).toBeInTheDocument();

// Click previous page
fireEvent.click(screen.getByRole('button', { name: /previous page/i }));
expect(screen.getByRole('button', { name: 'Page 1', current: 'page' })).toBeInTheDocument();
});

test('previous button is disabled on first page', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

const prevButton = screen.getByRole('button', { name: /previous page/i });
expect(prevButton).toBeDisabled();
});

test('next button is disabled on last page', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

// Navigate to last page
const totalPages = Math.ceil(mockPropsWithManyGroups.groups.groups.length / 8);
for (let i = 1; i < totalPages; i++) {
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
}

const nextButton = screen.getByRole('button', { name: /next page/i });
expect(nextButton).toBeDisabled();
});

test('direct page selection works correctly', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

// Click page 2 button
fireEvent.click(screen.getByRole('button', { name: 'Page 2' }));
expect(screen.getByRole('button', { name: 'Page 2', current: 'page' })).toBeInTheDocument();
});

test('shows correct number of items per page', () => {
renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);

// On desktop (non-mobile), should show 8 items per page
const groupRows = screen.getAllByRole('button', { name: /Select Group \d+ group/i });
expect(groupRows).toHaveLength(8);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test coverage for important edge cases.

While the current test suite covers basic pagination functionality well, consider adding the following test cases:

  1. Test filtering with pagination:
test('maintains correct pagination state when filtering results', () => {
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Navigate to second page
  fireEvent.click(screen.getByRole('button', { name: /next page/i }));
  
  // Apply filter that matches items on second page
  const searchInput = screen.getByPlaceholderText('Search for a group...');
  fireEvent.change(searchInput, { target: { value: 'Group 9' } });
  
  // Verify we see the filtered results
  expect(screen.getAllByRole('button', { name: /Select Group 9 group/i })).toHaveLength(1);
});
  1. Test empty state after filtering:
test('handles empty pages gracefully when filtering', () => {
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Apply filter that matches no items
  const searchInput = screen.getByPlaceholderText('Search for a group...');
  fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
  
  // Verify pagination is hidden and empty state is shown
  expect(screen.queryByRole('navigation', { name: /pagination/i })).not.toBeInTheDocument();
  expect(screen.getByText(/No groups found/i)).toBeInTheDocument();
});
  1. Test mobile view pagination:
test('adjusts items per page for mobile view', () => {
  // Mock useIsMobile to return true
  mock.module('@/hooks', () => ({
    useIsMobile: () => true
  }));
  
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Verify we see 6 items instead of 8 on mobile
  const groupRows = screen.getAllByRole('button', { name: /Select Group \d+ group/i });
  expect(groupRows).toHaveLength(6);
});

@@ -163,7 +175,7 @@
</thead>
<tbody className="space-y-4" role="rowgroup">
{isLoading
? Array(10)
? Array(8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use pageSize for skeleton row count

The number of skeleton rows should match the pageSize variable to maintain consistency between loading and loaded states.

-    ? Array(8)
+    ? Array(pageSize)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
? Array(8)
? Array(pageSize)

@@ -31,6 +32,11 @@
isLoading: boolean;
}) {
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Reset currentPage when search term changes

The currentPage should be reset to 1 when the search term changes to avoid showing empty pages.

Add a useEffect hook to handle this:

+useEffect(() => {
+  setCurrentPage(1);
+}, [searchTerm]);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +57 to +61
const totalPages = Math.ceil(filteredGroups.length / pageSize);
const paginatedGroups = filteredGroups.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize pagination calculations with useMemo

The paginatedGroups calculation should be memoized to prevent unnecessary recalculations on every render.

-  const totalPages = Math.ceil(filteredGroups.length / pageSize);
-  const paginatedGroups = filteredGroups.slice(
-    (currentPage - 1) * pageSize,
-    currentPage * pageSize
-  );
+  const { totalPages, paginatedGroups } = useMemo(() => {
+    const total = Math.max(1, Math.ceil(filteredGroups.length / pageSize));
+    const paginated = filteredGroups.slice(
+      (currentPage - 1) * pageSize,
+      currentPage * pageSize
+    );
+    return { totalPages: total, paginatedGroups: paginated };
+  }, [filteredGroups, currentPage, pageSize]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const totalPages = Math.ceil(filteredGroups.length / pageSize);
const paginatedGroups = filteredGroups.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
const { totalPages, paginatedGroups } = useMemo(() => {
const total = Math.max(1, Math.ceil(filteredGroups.length / pageSize));
const paginated = filteredGroups.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
return { totalPages: total, paginatedGroups: paginated };
}, [filteredGroups, currentPage, pageSize]);

@fmorency fmorency merged commit 54274d7 into main Nov 27, 2024
8 checks passed
@fmorency fmorency deleted the chalabi/group_pagination branch November 27, 2024 15:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
components/groups/components/__tests__/myGroups.test.tsx (1)

56-71: Enhance mock data to include proposals.

The mock data is well-structured for pagination testing, but the empty proposals object might miss testing scenarios where proposals affect the group display or filtering.

Consider enhancing the mock data:

const mockPropsWithManyGroups = {
  groups: {
    groups: Array(12).fill(null).map((_, index) => ({
      id: `${index + 1}`,
      ipfsMetadata: { title: `Group ${index + 1}` },
      policies: [{ address: `policy${index + 1}`, decision_policy: { threshold: '1' } }],
      admin: `admin${index + 1}`,
      members: [{ member: { address: `member${index + 1}` } }],
      total_weight: '1',
    })),
  },
  proposals: {
    // Add some proposals for testing proposal-related scenarios
    policy1: [{ id: '1', title: 'Proposal 1' }],
    policy2: [{ id: '2', title: 'Proposal 2' }],
  },
  isLoading: false,
};
components/groups/components/myGroups.tsx (2)

194-196: Simplify the proposals null check

The nested conditions can be simplified using optional chaining.

-group.policies &&
-group.policies.length > 0 &&
-proposals[group.policies[0].address]
+proposals[group.policies?.[0]?.address]

328-329: Remove redundant comment

The comment "Add pagination controls" can be removed as the pagination controls are already implemented above.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 12a798b and bc023bb.

📒 Files selected for processing (2)
  • components/groups/components/__tests__/myGroups.test.tsx (3 hunks)
  • components/groups/components/myGroups.tsx (7 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
components/groups/components/myGroups.tsx

[warning] 246-248: components/groups/components/myGroups.tsx#L246-L248
Added lines #L246 - L248 were not covered by tests


[warning] 250-250: components/groups/components/myGroups.tsx#L250
Added line #L250 was not covered by tests


[warning] 252-253: components/groups/components/myGroups.tsx#L252-L253
Added lines #L252 - L253 were not covered by tests

🔇 Additional comments (6)
components/groups/components/__tests__/myGroups.test.tsx (2)

34-37: 🛠️ Refactor suggestion

Add test coverage for mobile view pagination.

While the mobile detection mock is set up correctly, there's no test case that verifies the mobile view behavior. Consider adding a test case that checks the mobile-specific pagination behavior.

Add a test case for mobile view:

test('adjusts items per page for mobile view', () => {
  const useIsMobile = require('@/hooks/useIsMobile').default;
  useIsMobile.mockReturnValue(true);
  
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Verify mobile-specific behavior
  const groupRows = screen.getAllByRole('button', { name: /Select Group \d+ group/i });
  expect(groupRows).toHaveLength(6); // Assuming mobile shows 6 items per page
});

112-171: 🛠️ Refactor suggestion

Add test coverage for pagination edge cases.

While the current test suite covers basic pagination functionality well, it's missing important edge cases around filtering and empty states.

Add these test cases:

test('maintains correct pagination state when filtering results', () => {
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Navigate to second page
  fireEvent.click(screen.getByRole('button', { name: /next page/i }));
  
  // Apply filter that matches items on second page
  const searchInput = screen.getByPlaceholderText('Search for a group...');
  fireEvent.change(searchInput, { target: { value: 'Group 9' } });
  
  // Verify we see the filtered results
  expect(screen.getAllByRole('button', { name: /Select Group 9 group/i })).toHaveLength(1);
});

test('handles empty pages gracefully when filtering', () => {
  renderWithChainProvider(<YourGroups {...mockPropsWithManyGroups} />);
  
  // Apply filter that matches no items
  const searchInput = screen.getByPlaceholderText('Search for a group...');
  fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
  
  // Verify pagination is hidden and empty state is shown
  expect(screen.queryByRole('navigation', { name: /pagination/i })).not.toBeInTheDocument();
  expect(screen.getByText(/No groups found/i)).toBeInTheDocument();
});
components/groups/components/myGroups.tsx (4)

35-38: Reset currentPage when searchTerm changes

The currentPage should be reset to 1 when the search term changes to avoid showing empty pages.


53-57: Optimize pagination calculations with useMemo

The paginatedGroups calculation should be memoized to prevent unnecessary recalculations on every render.


149-149: Use pageSize for skeleton row count

The number of skeleton rows should match the pageSize variable to maintain consistency between loading and loaded states.


205-268: Add test coverage for pagination edge cases

Please add test coverage for:

  • Ellipsis rendering logic (when currentPage is near start/end)
  • Page number visibility conditions
  • Edge cases when totalPages is small
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 246-248: components/groups/components/myGroups.tsx#L246-L248
Added lines #L246 - L248 were not covered by tests


[warning] 250-250: components/groups/components/myGroups.tsx#L250
Added line #L250 was not covered by tests


[warning] 252-253: components/groups/components/myGroups.tsx#L252-L253
Added lines #L252 - L253 were not covered by tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pagination?
2 participants