use crate::harness::TestHarness; #[tokio::test] async fn directory_pagination_shows_limited_communities() { let mut h = TestHarness::new().await; // Create 30 communities (page size is 25) for i in 0..30 { h.create_community(&format!("Community {:02}", i), &format!("comm-{:02}", i)) .await; } // Page 1 should show 25 let resp = h.client.get("/").await; assert!(resp.status.is_success()); assert!( resp.text.contains("Page 1 of 2"), "Should show pagination info: {}", &resp.text[..200.min(resp.text.len())] ); assert!( resp.text.contains("Next"), "Should show Next link on page 1" ); assert!( !resp.text.contains("Previous"), "Should not show Previous link on page 1" ); // Page 2 should show the remaining 5 let resp = h.client.get("/?page=2").await; assert!(resp.status.is_success()); assert!( resp.text.contains("Previous"), "Should show Previous link on page 2" ); } #[tokio::test] async fn empty_category_shows_no_threads() { let mut h = TestHarness::new().await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; let resp = h.client.get("/p/test/general").await; assert!(resp.status.is_success()); assert!( resp.text.contains("No threads yet"), "expected empty state message" ); // Should not render pagination assert!( !resp.text.contains("pagination"), "pagination should not appear with 0 threads" ); } #[tokio::test] async fn page_beyond_max_shows_last_page() { let mut h = TestHarness::new().await; let user_id = h.login_as("pager").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(user_id, comm_id, "member").await; // Create one thread h.create_thread_with_post(cat_id, user_id, "Thread One", "body") .await; // Request page=999 let resp = h.client.get("/p/test/general?page=999").await; assert!(resp.status.is_success()); // Should still show the thread (clamped to page 1) assert!( resp.text.contains("Thread One"), "thread should be visible on clamped page" ); } #[tokio::test] async fn page_zero_treated_as_page_one() { let mut h = TestHarness::new().await; let user_id = h.login_as("pager").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(user_id, comm_id, "member").await; h.create_thread_with_post(cat_id, user_id, "First Thread", "body") .await; let resp = h.client.get("/p/test/general?page=0").await; assert!(resp.status.is_success()); assert!( resp.text.contains("First Thread"), "thread should be visible with page=0" ); } #[tokio::test] async fn sort_by_replies_works() { let mut h = TestHarness::new().await; let user_id = h.login_as("sorter").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(user_id, comm_id, "member").await; // Create two threads let _t1 = h .create_thread_with_post(cat_id, user_id, "Few Replies", "body") .await; let t2 = h .create_thread_with_post(cat_id, user_id, "Many Replies", "body") .await; // Add extra replies to t2 for i in 0..3 { mt_db::mutations::create_post( &h.db, t2, user_id, &format!("reply {i}"), &format!("

reply {i}

"), true, ) .await .unwrap(); } // Sort by replies desc — "Many Replies" should come first let resp = h .client .get("/p/test/general?sort=replies&order=desc") .await; assert!(resp.status.is_success()); let many_pos = resp.text.find("Many Replies").expect("Many Replies not found"); let few_pos = resp.text.find("Few Replies").expect("Few Replies not found"); assert!( many_pos < few_pos, "Many Replies should appear before Few Replies when sorted by replies desc" ); // Sort by replies asc — "Few Replies" should come first (after pinned) let resp = h .client .get("/p/test/general?sort=replies&order=asc") .await; assert!(resp.status.is_success()); let many_pos = resp.text.find("Many Replies").expect("Many Replies not found"); let few_pos = resp.text.find("Few Replies").expect("Few Replies not found"); assert!( few_pos < many_pos, "Few Replies should appear before Many Replies when sorted by replies asc" ); } #[tokio::test] async fn sort_headers_present_in_category() { let mut h = TestHarness::new().await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; let user_id = h.login_as("viewer").await; h.add_membership(user_id, comm_id, "member").await; h.create_thread_with_post(_cat_id, user_id, "A Thread", "body") .await; let resp = h.client.get("/p/test/general").await; assert!(resp.status.is_success()); // Default sort is activity desc, so activity header should have the arrow assert!( resp.text.contains("sort-active"), "active sort indicator should be present" ); assert!( resp.text.contains("?sort=replies"), "replies sort link should be present" ); assert!( resp.text.contains("?sort=activity"), "activity sort link should be present" ); } #[tokio::test] async fn thread_pagination_page_zero() { let mut h = TestHarness::new().await; let user_id = h.login_as("poster").await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(user_id, comm_id, "member").await; let thread_id = h .create_thread_with_post(cat_id, user_id, "My Thread", "first post") .await; let resp = h .client .get(&format!("/p/test/general/{thread_id}?page=0")) .await; assert!(resp.status.is_success()); assert!( resp.text.contains("first post"), "post should be visible with page=0" ); } #[tokio::test] async fn meta_description_present_on_category() { let mut h = TestHarness::new().await; let comm_id = h.create_community("TestProject", "testproject").await; let _cat_id = h .create_category(comm_id, "Bugs", "bugs") .await; let resp = h.client.get("/p/testproject/bugs").await; assert!(resp.status.is_success()); assert!( resp.text.contains(r#""#), "404 page should have noindex meta" ); } #[tokio::test] async fn member_list_shows_members_with_roles() { let mut h = TestHarness::new().await; let owner_id = h.login_as("theowner").await; let comm_id = h.create_community("Test", "test").await; h.add_membership(owner_id, comm_id, "owner").await; let mod_id = h.login_as("themod").await; h.add_membership(mod_id, comm_id, "moderator").await; let member_id = h.login_as("regular").await; h.add_membership(member_id, comm_id, "member").await; let resp = h.client.get("/p/test/members").await; assert!(resp.status.is_success()); assert!(resp.text.contains("theowner"), "owner should be listed"); assert!(resp.text.contains("themod"), "mod should be listed"); assert!(resp.text.contains("regular"), "member should be listed"); // Owner should appear before member in the table (sorted by role) // Search within the data-table to avoid matching the header nav Profile link let table_start = resp.text.find("data-table").unwrap(); let table_html = &resp.text[table_start..]; let owner_pos = table_html.find("/u/theowner").unwrap(); let member_pos = table_html.find("/u/regular").unwrap(); assert!( owner_pos < member_pos, "owner should appear before regular member" ); } #[tokio::test] async fn member_list_nonexistent_community_404() { let mut h = TestHarness::new().await; let resp = h.client.get("/p/nonexistent/members").await; assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND); } #[tokio::test] async fn community_page_has_members_link() { let mut h = TestHarness::new().await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; let resp = h.client.get("/p/test").await; assert!(resp.status.is_success()); assert!( resp.text.contains("/p/test/members"), "community page should have members link" ); }