Skip to main content

autumn_plugin_motion/
script.rs

1//! [`motion_script()`]: the `<script>` tags for the vendored Motion build.
2//!
3//! Put it in the page `<head>` (or at the end of `<body>`); the scripts are
4//! `defer`red so they run after parsing, in document order (Motion first,
5//! then the init script). Every tag points at the content-hashed URL from
6//! [`MOTION_ASSETS`] and carries the Subresource Integrity hash the bundle
7//! computes from the embedded bytes.
8
9use autumn_web::{Markup, html};
10
11use crate::assets::{INIT_JS, MOTION_ASSETS, MOTION_CSS, MOTION_JS};
12
13/// Renders the `<script>` tags loading Motion and the plugin init script.
14///
15/// ```rust
16/// use autumn_plugin_motion::motion_script;
17/// use autumn_web::html;
18///
19/// let head = html! {
20///     head {
21///         (motion_script())
22///     }
23/// };
24/// assert!(head.into_string().contains("/static/_plugins/motion/motion.min."));
25/// ```
26#[must_use]
27pub fn motion_script() -> Markup {
28    html! {
29        (MOTION_ASSETS.deferred_script_tag(MOTION_JS))
30        (MOTION_ASSETS.deferred_script_tag(INIT_JS))
31    }
32}
33
34/// Renders the `<link>` tag for the plugin stylesheet.
35///
36/// Only needed when you use [`crate::Motion::scroll_progress`] (or want the
37/// plugin's default styles); the animation behavior itself is class-free.
38///
39/// ```rust
40/// use autumn_plugin_motion::motion_stylesheet;
41///
42/// let html = motion_stylesheet().into_string();
43/// assert!(html.contains(r#"href="/static/_plugins/motion/motion."#), "{html}");
44/// ```
45#[must_use]
46pub fn motion_stylesheet() -> Markup {
47    MOTION_ASSETS.stylesheet_tag(MOTION_CSS)
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    /// The bundle entry for `path` (test helper).
55    fn asset(path: &str) -> &'static autumn_web::assets::PluginAsset {
56        MOTION_ASSETS.get(path).expect("file is bundled")
57    }
58
59    #[test]
60    fn script_tags_carry_sri_and_fingerprinted_urls() {
61        let html = motion_script().into_string();
62        let motion = asset(MOTION_JS);
63        let init = asset(INIT_JS);
64        assert!(
65            html.contains(&format!(r#"src="{}""#, motion.url())),
66            "{html}"
67        );
68        assert!(html.contains(&format!(r#"src="{}""#, init.url())), "{html}");
69        assert!(
70            html.contains(&format!(r#"integrity="{}""#, motion.integrity())),
71            "motion script tag carries the SRI hash: {html}"
72        );
73        assert!(
74            html.contains(&format!(r#"integrity="{}""#, init.integrity())),
75            "init script tag carries the SRI hash: {html}"
76        );
77        assert_eq!(html.matches("defer").count(), 2, "{html}");
78        assert_eq!(
79            html.matches(r#"crossorigin="anonymous""#).count(),
80            2,
81            "{html}"
82        );
83        assert!(!html.contains("not found"), "{html}");
84    }
85
86    #[test]
87    fn motion_loads_before_the_init_script() {
88        let html = motion_script().into_string();
89        let motion_at = html.find(asset(MOTION_JS).url()).expect("motion tag");
90        let init_at = html.find(asset(INIT_JS).url()).expect("init tag");
91        assert!(
92            motion_at < init_at,
93            "deferred scripts run in order; init.js needs window.Motion: {html}"
94        );
95    }
96
97    #[test]
98    fn stylesheet_link_carries_sri_and_fingerprinted_url() {
99        let html = motion_stylesheet().into_string();
100        let css = asset(MOTION_CSS);
101        assert!(html.contains(r#"rel="stylesheet""#), "{html}");
102        assert!(html.contains(&format!(r#"href="{}""#, css.url())), "{html}");
103        assert!(
104            html.contains(&format!(r#"integrity="{}""#, css.integrity())),
105            "{html}"
106        );
107        assert!(html.contains(r#"crossorigin="anonymous""#), "{html}");
108    }
109}