Skip to main content

yggdrasil/components/post/
post_footer.rs

1//! 文章页脚组件:正文后的发丝线与上一篇 / 下一篇。
2
3use leptos::prelude::*;
4
5use crate::components::post::post_nav_links::PostNavLinks;
6use crate::models::post::PostFooterData;
7
8/// 文章页脚组件。
9///
10/// Props:
11/// - `post`:页脚展示所需的最小字段集(P4.2:不携带完整正文,调用方用
12///   `PostFooterData::from(&post)` 构造)
13/// - `sample`:图鉴展示非跳转的样例链接;正式文章默认 false。
14///
15/// 标签已移到页眉;没有相邻文章时不渲染。
16#[component]
17pub fn PostFooter(post: PostFooterData, #[prop(optional)] sample: bool) -> impl IntoView {
18    if post.prev_post.is_none() && post.next_post.is_none() {
19        return ().into_any();
20    }
21    view! {
22        <footer class="post-footer">
23            <PostNavLinks prev=post.prev_post next=post.next_post sample/>
24        </footer>
25    }
26    .into_any()
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::models::post::PostNav;
33    use leptos::reactive::owner::Owner;
34
35    #[test]
36    fn renders_footer_only_with_neighbours() {
37        let empty = Owner::new().with(|| {
38            view! { <PostFooter post=PostFooterData { prev_post: None, next_post: None }/> }
39                .to_html()
40        });
41        assert!(!empty.contains("post-footer"), "{empty}");
42        let filled = Owner::new().with(|| {
43            view! {
44                <PostFooter post=PostFooterData {
45                    prev_post: Some(PostNav { title: "旧文".into(), slug: "old".into() }),
46                    next_post: None,
47                }/>
48            }
49            .to_html()
50        });
51        assert!(filled.contains("post-footer"), "{filled}");
52        assert!(filled.contains("旧文"), "{filled}");
53    }
54}