Skip to main content

oxihuman_export/
web_animation_api_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Web Animations API JSON export stub.
6
7/// A single keyframe in Web Animations API format.
8pub struct WebAnimKeyframe {
9    pub offset: f32,
10    pub property: String,
11    pub value: String,
12    pub easing: String,
13}
14
15/// Web Animations API animation options.
16pub struct WebAnimOptions {
17    pub duration: u32,
18    pub iterations: f32,
19    pub fill: String,
20    pub easing: String,
21    pub delay: i32,
22}
23
24impl Default for WebAnimOptions {
25    fn default() -> Self {
26        Self {
27            duration: 1000,
28            iterations: 1.0,
29            fill: "none".to_string(),
30            easing: "ease".to_string(),
31            delay: 0,
32        }
33    }
34}
35
36/// A Web Animations API animation export.
37pub struct WebAnimExport {
38    pub keyframes: Vec<WebAnimKeyframe>,
39    pub options: WebAnimOptions,
40    pub target_selector: String,
41}
42
43/// Create a new Web Animations API export.
44pub fn new_web_anim_export(target: &str, options: WebAnimOptions) -> WebAnimExport {
45    WebAnimExport {
46        keyframes: Vec::new(),
47        options,
48        target_selector: target.to_string(),
49    }
50}
51
52/// Add a keyframe.
53pub fn add_web_anim_keyframe(
54    exp: &mut WebAnimExport,
55    offset: f32,
56    property: &str,
57    value: &str,
58    easing: &str,
59) {
60    exp.keyframes.push(WebAnimKeyframe {
61        offset: offset.clamp(0.0, 1.0),
62        property: property.to_string(),
63        value: value.to_string(),
64        easing: easing.to_string(),
65    });
66}
67
68/// Keyframe count.
69pub fn web_anim_keyframe_count(exp: &WebAnimExport) -> usize {
70    exp.keyframes.len()
71}
72
73/// Render a simple JSON representation.
74pub fn render_web_anim_json(exp: &WebAnimExport) -> String {
75    format!(
76        r#"{{"target":"{}","duration":{},"keyframes":{}}}"#,
77        exp.target_selector,
78        exp.options.duration,
79        exp.keyframes.len()
80    )
81}
82
83/// Validate export (target non-empty, duration > 0).
84pub fn validate_web_anim(exp: &WebAnimExport) -> bool {
85    !exp.target_selector.is_empty() && exp.options.duration > 0
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn new_export_empty() {
94        let exp = new_web_anim_export("#el", WebAnimOptions::default());
95        assert_eq!(web_anim_keyframe_count(&exp), 0 /* empty */);
96    }
97
98    #[test]
99    fn add_keyframe_increments() {
100        let mut exp = new_web_anim_export("#el", WebAnimOptions::default());
101        add_web_anim_keyframe(&mut exp, 0.0, "opacity", "0", "ease");
102        assert_eq!(web_anim_keyframe_count(&exp), 1 /* one keyframe */);
103    }
104
105    #[test]
106    fn offset_clamped() {
107        let mut exp = new_web_anim_export("#el", WebAnimOptions::default());
108        add_web_anim_keyframe(&mut exp, 1.5, "x", "10px", "ease");
109        assert!(exp.keyframes[0].offset <= 1.0 /* clamped */);
110    }
111
112    #[test]
113    fn validate_valid() {
114        let exp = new_web_anim_export("#div", WebAnimOptions::default());
115        assert!(validate_web_anim(&exp) /* valid */);
116    }
117
118    #[test]
119    fn validate_empty_target_fails() {
120        let exp = new_web_anim_export("", WebAnimOptions::default());
121        assert!(!validate_web_anim(&exp) /* invalid */);
122    }
123
124    #[test]
125    fn render_contains_target() {
126        let exp = new_web_anim_export("#my-el", WebAnimOptions::default());
127        let json = render_web_anim_json(&exp);
128        assert!(json.contains("my-el") /* target in output */);
129    }
130
131    #[test]
132    fn render_contains_duration() {
133        let opt = WebAnimOptions {
134            duration: 2500,
135            ..Default::default()
136        };
137        let exp = new_web_anim_export("#x", opt);
138        let json = render_web_anim_json(&exp);
139        assert!(json.contains("2500") /* duration */);
140    }
141
142    #[test]
143    fn default_options_reasonable() {
144        let opt = WebAnimOptions::default();
145        assert!(opt.duration > 0 /* positive duration */);
146        assert!(opt.iterations >= 1.0 /* at least once */);
147    }
148
149    #[test]
150    fn multiple_keyframes() {
151        let mut exp = new_web_anim_export("#a", WebAnimOptions::default());
152        add_web_anim_keyframe(&mut exp, 0.0, "opacity", "0", "ease");
153        add_web_anim_keyframe(&mut exp, 0.5, "opacity", "0.5", "linear");
154        add_web_anim_keyframe(&mut exp, 1.0, "opacity", "1", "ease");
155        assert_eq!(web_anim_keyframe_count(&exp), 3 /* three keyframes */);
156    }
157}