//! 内容库状态机的守门测试(M109;复审 P1-2 的补课)。 //! //! 从 `state.rs` 拆出(那个文件贴 400 行硬上限)。钉的是**翻页判据**: //! `has_more` 必须读「最近一页是否装满」,不能读累计条数——否则第 2 页之后恒真。 use crate::dto::{ AppEvent, ContentHit, ContentKind, ContentQuery, ContentResults, ContentSource, CoreEvent, }; use super::AppState; fn hit(index: usize) -> ContentHit { ContentHit { id: format!("p{index}"), slug: format!("p{index}"), title: format!("Project {index}"), summary: "s".to_string(), author: "a".to_string(), downloads: 0, updated: "2026-01-01".to_string(), source: ContentSource::Modrinth, game_versions: vec!["1.20.1".to_string()], loaders: vec!["fabric".to_string()], icon: None, } } /// 造一页结果:`offset` 是游标,`count` 是这一页实际回来几条。 fn page(offset: u32, count: u32) -> ContentResults { let mut query = ContentQuery::first(ContentKind::Mod); query.offset = offset; ContentResults { query, hits: (0..count) .map(|index| hit(offset as usize + index as usize)) .collect(), total: 999, curseforge: false, } } fn apply(state: &mut AppState, results: ContentResults) { state.apply(AppEvent::Core(CoreEvent::ContentResults(Box::new(results)))); } /// 复审 P1-2 的第一条:装满一页 ⇒ 还有更多;下一(末)页不满 ⇒ 没有了。 #[test] fn a_short_last_page_clears_has_more() { let mut state = AppState::new(); let limit = ContentQuery::first(ContentKind::Mod).limit; apply(&mut state, page(0, limit)); assert!(state.content.last_page_filled, "首页装满 ⇒ 还有下一页"); assert_eq!( state.content.results.as_ref().map_or(0, |r| r.hits.len()), limit as usize ); // 末页只回来 3 条:累计 23 条「够一页了」,但**这一页**没装满 ⇒ 没有更多 apply(&mut state, page(limit, 3)); assert!( !state.content.last_page_filled, "末页不满 ⇒ 不许说还有更多(判据不能读累计条数)" ); assert_eq!( state.content.results.as_ref().map_or(0, |r| r.hits.len()), limit as usize + 3, "命中项仍然要累加" ); } /// 复审 P1-2 的第二条:再点一次,回来的空页仍然是「没有更多」。 #[test] fn an_empty_last_page_keeps_has_more_false() { let mut state = AppState::new(); let limit = ContentQuery::first(ContentKind::Mod).limit; apply(&mut state, page(0, limit)); apply(&mut state, page(limit, 0)); assert!(!state.content.last_page_filled, "空页 ⇒ 没有更多"); // 用户又点了一次「加载更多」:空页不会把状态翻回真 apply(&mut state, page(limit + limit, 0)); assert!( !state.content.last_page_filled, "再点一次空页 ⇒ 仍然没有更多" ); assert_eq!( state.content.results.as_ref().map_or(0, |r| r.hits.len()), limit as usize, "空页不改变列表内容" ); } /// 换条件(offset 回到 0)时把「最近一页装满」清掉:不然新条件的第一帧就会画「没有更多了」。 #[test] fn changing_the_query_resets_the_page_flag() { let mut state = AppState::new(); let limit = ContentQuery::first(ContentKind::Mod).limit; apply(&mut state, page(0, limit)); assert!(state.content.last_page_filled); let mut query = ContentQuery::first(ContentKind::Mod); query.text = "sodium".to_string(); state.set_content_query(query); assert!(!state.content.last_page_filled, "换条件 ⇒ 清掉上一批的判据"); assert!(state.content.pending); assert!( state.content.results.is_none(), "换条件要清列表(不然加载态画旧命中项)" ); } /// 迟到的一批不许改写判据(用户已经换了条件)。 #[test] fn a_stale_batch_does_not_touch_the_page_flag() { let mut state = AppState::new(); let limit = ContentQuery::first(ContentKind::Mod).limit; apply(&mut state, page(0, limit)); let mut query = ContentQuery::first(ContentKind::Mod); query.text = "sodium".to_string(); state.set_content_query(query); // 旧条件的一页这时才回来:既不改结果,也不改「还有更多」 apply(&mut state, page(limit, 5)); assert!(!state.content.last_page_filled); assert!(state.content.results.is_none(), "迟到的一批被丢掉"); }