tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ main shared/foreign-lottie/tests/no_panic_fork.rs Rust · 129 lines · 5.9 KB History Who touched what raw

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) b575c63f1 · Sergio ·

1//! Tests del fork tawasuyu: los Lotties que en velato 0.9 upstream
2//! **paniqueaban** (`todo!()`/`unimplemented!()` en el importador) ahora se
3//! importan con degradación graciosa, sin panic. Cada caso construye una
4//! `Composition` y exige que vuelva `Ok`.
5
6use foreign_lottie::Composition;
7
8/// Lottie con un layer cuyo transform NO trae campo de rotación (`r`).
9/// Upstream: `None => todo!("split rotation")`. Fork: rotación 0.
10#[test]
11fn transform_sin_rotacion_no_paniquea() {
12    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
13        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,
14        "ks":{"p":{"a":0,"k":[0,0]}},"shapes":[]}]}"#;
15    assert!(Composition::from_slice(json).is_ok());
16}
17
18/// Transform con rotación **splitteada** (`r` = {x,y,z,or}). Upstream:
19/// `SplitRotation => todo!()`. Fork: usa la componente z.
20#[test]
21fn split_rotation_no_paniquea() {
22    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
23        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,
24        "ks":{"p":{"a":0,"k":[0,0]},
25              "r":{"x":{"a":0,"k":0},"y":{"a":0,"k":0},"z":{"a":0,"k":45},
26                   "or":{"a":0,"k":[0,0,0]}}},
27        "shapes":[]}]}"#;
28    assert!(Composition::from_slice(json).is_ok());
29}
30
31/// Transform con posición **splitteada** (`p` = {s:true,x,y}). Upstream del
32/// shape-transform: `SplitPosition => todo!("split position")`. Fork: la arma
33/// desde x/y. (Lo metemos dentro de un group-shape con su transform para
34/// ejercitar `conv_shape_transform`.)
35#[test]
36fn split_position_en_shape_no_paniquea() {
37    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
38        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,
39        "ks":{"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0}},
40        "shapes":[{"ty":"gr","it":[
41            {"ty":"tr",
42             "p":{"s":true,"x":{"a":0,"k":10},"y":{"a":0,"k":20}},
43             "a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},
44             "r":{"a":0,"k":0},"o":{"a":0,"k":100}}
45        ]}]}]}"#;
46    assert!(Composition::from_slice(json).is_ok());
47}
48
49/// Blend mode `Add` (be=10) en un layer. Upstream: `Add => unimplemented!()`.
50/// Fork: compositing aditivo (Compose::Plus).
51#[test]
52fn blend_add_no_paniquea() {
53    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
54        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,"bm":10,
55        "ks":{"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0}},"shapes":[]}]}"#;
56    assert!(Composition::from_slice(json).is_ok());
57}
58
59/// Un Lottie sano sigue parseando (no rompimos el camino feliz).
60#[test]
61fn lottie_sano_sigue_ok() {
62    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
63        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,
64        "ks":{"p":{"a":0,"k":[50,50]},"r":{"a":0,"k":0}},
65        "shapes":[
66          {"ty":"rc","p":{"a":0,"k":[50,50]},"s":{"a":0,"k":[80,80]},"r":{"a":0,"k":0}},
67          {"ty":"fl","c":{"a":0,"k":[0.8,0,0,1]},"o":{"a":0,"k":100}}
68        ]}]}"#;
69    let comp = Composition::from_slice(json).expect("lottie sano parsea");
70    assert_eq!((comp.width, comp.height), (100, 100));
71}
72
73// ── El deserializador: una capa que no entendemos no se lleva el archivo ──
74
75/// Un `ty` fuera de 0–4 (texto=5, cámara, luz, ajuste…) devolvía
76/// `Err("unknown layer type")`, y serde aborta el documento al primer error:
77/// **un solo layer de texto tumbaba el `.json` entero**. En un export de After
78/// Effects una cámara o una luz vienen de fábrica, así que el caso es el
79/// común, no el raro. Ahora la capa se reconoce como `Unsupported` y el resto
80/// del archivo se importa.
81///
82/// El diente del test es la SEGUNDA aserción: que no paniquee ni devuelva
83/// `Err` no alcanza si el documento vuelve sin capas — sería la misma pérdida
84/// con otro nombre.
85#[test]
86fn una_capa_de_tipo_desconocido_no_rechaza_el_documento() {
87    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
88        "layers":[
89          {"ty":5,"ip":0,"op":60,"st":0,"sr":1,
90           "ks":{"p":{"a":0,"k":[50,50]},"r":{"a":0,"k":0}}},
91          {"ty":4,"ip":0,"op":60,"st":0,"sr":1,
92           "ks":{"p":{"a":0,"k":[50,50]},"r":{"a":0,"k":0}},
93           "shapes":[
94             {"ty":"rc","p":{"a":0,"k":[50,50]},"s":{"a":0,"k":[80,80]},"r":{"a":0,"k":0}},
95             {"ty":"fl","c":{"a":0,"k":[0.8,0,0,1]},"o":{"a":0,"k":100}}
96           ]}
97        ]}"#;
98    let comp = Composition::from_slice(json).expect("una capa de texto no puede tumbar el archivo");
99    assert!(
100        !comp.layers.is_empty(),
101        "importó con Ok pero sin capas: la capa de forma se fue con la de texto"
102    );
103}
104
105/// Y el otro lado del mismo agujero: **omitir el campo `s` (escala) de un
106/// transform** es legal en Lottie y su default es 100. El default del
107/// importador era `[1,1,1]`, y la escala se divide por 100 al armar la
108/// `Affine`, así que la capa entraba al **1 %** — sin error, sin panic, sin
109/// nada que mirar salvo el resultado. Verificado a nivel píxel en
110/// `02_ruway/llimphi/llimphi-test/tests/lottie_degrada_pero_pinta.rs`
111/// (4 px sin `s`, 8.100 con `s:[100,100]`); acá se fija el número que lo
112/// causa, que es más barato y no necesita GPU.
113#[test]
114fn un_transform_sin_escala_entra_al_cien_por_ciento() {
115    let json = r#"{"v":"5.5.2","fr":30,"ip":0,"op":60,"w":100,"h":100,
116        "layers":[{"ty":4,"ip":0,"op":60,"st":0,"sr":1,
117        "ks":{"p":{"a":0,"k":[50,50]},"r":{"a":0,"k":0}},"shapes":[]}]}"#;
118    let comp = Composition::from_slice(json).expect("parsea");
119    let capa = comp.layers.first().expect("hay una capa");
120    // `Transform::evaluate` devuelve un `ValueRef<kurbo::Affine>`.
121    let escala = capa.transform.evaluate(0.0).as_ref().as_coeffs();
122    // Los coeficientes 0 y 3 de la Affine son el escalado en x e y. Con el
123    // default viejo daban 0,01.
124    assert!(
125        (escala[0] - 1.0).abs() < 1e-6 && (escala[3] - 1.0).abs() < 1e-6,
126        "un transform sin `s` entró con escala {:?}, no 1,0 (= 100 %)",
127        (escala[0], escala[3])
128    );
129}