Skip to content

Commit f326874

Browse files
committed
test: add comprehensive test coverage for render-text-as-image and related components
- **Add blend module tests** for alpha blending edge cases: - `both_fully_transparent_stays_transparent()` verifies that blending two fully transparent colors yields transparency - `semi_transparent_blend()` ensures semi‑transparent white over black produces mid‑grey - **Expand render‑text‑as‑image crate tests** to validate public API, defaults, and error handling: - Custom foreground/background colour rendering - Zero‑padding layout - `Rgba` default and constant values (`WHITE`, `BLACK`) - `RenderParams` default values match specification - `RenderError` Display implementation and `std::error` conformance - Debug formatting for error variants - **Add UI proxy error‑image mapping tests** that verify each cache‑image error variant produces a valid PNG: - `UrlIsUnreachable`, `UrlIsNotAnImage`, `ImageTooBig`, `UserQuotaMet`, `Unauthenticated` - Ensures `LazyLock` caching produces identical images across calls - **Include mailer filter test** for `do_nothing_filter` that passes JSON strings unchanged These tests follow the project's crate‑level test convention (`src/tests/`) and ensure the recently introduced `render‑text‑as‑image` package and its integration points behave correctly across a range of inputs and edge cases.
1 parent 67e411f commit f326874

6 files changed

Lines changed: 149 additions & 2 deletions

File tree

packages/render-text-as-image/src/blend.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,20 @@ mod tests {
5353
blend_source_over(&mut dst, [200, 200, 200, 255], 0);
5454
assert_eq!(dst, original);
5555
}
56+
57+
#[test]
58+
fn both_fully_transparent_stays_transparent() {
59+
let mut dst = Rgba([0, 0, 0, 0]);
60+
blend_source_over(&mut dst, [255, 255, 255, 0], 0);
61+
assert_eq!(dst, Rgba([0, 0, 0, 0]));
62+
}
63+
64+
#[test]
65+
fn semi_transparent_blend() {
66+
let mut dst = Rgba([0, 0, 0, 255]);
67+
blend_source_over(&mut dst, [255, 255, 255, 255], 128);
68+
// After blending, the pixel should be a mid-grey.
69+
assert!(dst.0[0] > 100 && dst.0[0] < 200);
70+
assert_eq!(dst.0[3], 255);
71+
}
5672
}

packages/render-text-as-image/src/tests/mod.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Crate-level tests for the public API.
22
33
use crate::types::MAX_TEXT_BYTES;
4-
use crate::{render_text_to_png, RenderError, RenderParams};
4+
use crate::{render_text_to_png, RenderError, RenderParams, Rgba};
5+
6+
// --- Rendering tests ---
57

68
#[test]
79
fn empty_text_produces_valid_png() {
@@ -44,3 +46,82 @@ fn deterministic_output() {
4446
let b = render_text_to_png(&params).unwrap();
4547
assert_eq!(a, b);
4648
}
49+
50+
#[test]
51+
fn custom_colours_produce_valid_png() {
52+
let params = RenderParams {
53+
text: "colour",
54+
fg_colour: Rgba::BLACK,
55+
bg_colour: Rgba::WHITE,
56+
..Default::default()
57+
};
58+
let png = render_text_to_png(&params).unwrap();
59+
assert_eq!(&png[..4], &[0x89, b'P', b'N', b'G']);
60+
}
61+
62+
#[test]
63+
fn zero_padding_produces_valid_png() {
64+
let params = RenderParams {
65+
text: "tight",
66+
padding_em: 0.0,
67+
..Default::default()
68+
};
69+
let png = render_text_to_png(&params).unwrap();
70+
assert_eq!(&png[..4], &[0x89, b'P', b'N', b'G']);
71+
}
72+
73+
// --- Type default / constant tests ---
74+
75+
#[test]
76+
fn rgba_default_is_white() {
77+
assert_eq!(Rgba::default(), Rgba::WHITE);
78+
}
79+
80+
#[test]
81+
fn rgba_white_has_expected_components() {
82+
assert_eq!(Rgba::WHITE.0, [255, 255, 255, 255]);
83+
}
84+
85+
#[test]
86+
fn rgba_black_has_expected_components() {
87+
assert_eq!(Rgba::BLACK.0, [0, 0, 0, 255]);
88+
}
89+
90+
#[test]
91+
fn render_params_default_values() {
92+
let p = RenderParams::default();
93+
assert_eq!(p.text, "");
94+
assert!((p.font_size_px - 20.0).abs() < f32::EPSILON);
95+
assert_eq!(p.fg_colour, Rgba::WHITE);
96+
assert_eq!(p.bg_colour, Rgba([0x33, 0x33, 0x33, 0xFF]));
97+
assert!((p.padding_em - 0.4).abs() < f32::EPSILON);
98+
}
99+
100+
// --- RenderError Display / Error tests ---
101+
102+
#[test]
103+
fn render_error_text_too_long_display() {
104+
let err = RenderError::TextTooLong;
105+
let msg = err.to_string();
106+
assert!(msg.contains("256"), "should mention the byte limit: {msg}");
107+
}
108+
109+
#[test]
110+
fn render_error_encoding_failed_display() {
111+
let err = RenderError::EncodingFailed("oops".into());
112+
let msg = err.to_string();
113+
assert!(msg.contains("oops"), "should contain the inner message: {msg}");
114+
}
115+
116+
#[test]
117+
fn render_error_implements_std_error() {
118+
let err = RenderError::TextTooLong;
119+
let _: &dyn std::error::Error = &err;
120+
}
121+
122+
#[test]
123+
fn render_error_debug_format() {
124+
let err = RenderError::TextTooLong;
125+
let dbg = format!("{err:?}");
126+
assert!(dbg.contains("TextTooLong"));
127+
}

src/tests/mailer.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use lettre::Message;
2+
use serde_json::json;
23

3-
use crate::mailer::{build_content, build_letter};
4+
use crate::mailer::{build_content, build_letter, do_nothing_filter};
45

56
#[test]
67
fn it_should_build_a_letter() {
@@ -18,3 +19,11 @@ fn it_should_build_content() {
1819
assert_ne!(plain_body, "");
1920
assert_ne!(html_body, "");
2021
}
22+
23+
#[test]
24+
fn do_nothing_filter_passes_through_string() {
25+
let input = json!("hello world");
26+
let args = std::collections::HashMap::new();
27+
let result = do_nothing_filter(&input, &args).unwrap();
28+
assert_eq!(result, json!("hello world"));
29+
}

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ mod mailer;
55
mod models;
66
mod services;
77
mod tracker;
8+
mod ui;
89
mod utils;

src/tests/ui/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod proxy;

src/tests/ui/proxy.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use crate::cache::image::manager::Error;
2+
use crate::ui::proxy::map_error_to_image;
3+
4+
#[test]
5+
fn map_error_url_is_unreachable() {
6+
let img = map_error_to_image(&Error::UrlIsUnreachable);
7+
assert_eq!(&img[..4], &[0x89, b'P', b'N', b'G']);
8+
}
9+
10+
#[test]
11+
fn map_error_url_is_not_an_image() {
12+
let img = map_error_to_image(&Error::UrlIsNotAnImage);
13+
assert_eq!(&img[..4], &[0x89, b'P', b'N', b'G']);
14+
}
15+
16+
#[test]
17+
fn map_error_image_too_big() {
18+
let img = map_error_to_image(&Error::ImageTooBig);
19+
assert_eq!(&img[..4], &[0x89, b'P', b'N', b'G']);
20+
}
21+
22+
#[test]
23+
fn map_error_user_quota_met() {
24+
let img = map_error_to_image(&Error::UserQuotaMet);
25+
assert_eq!(&img[..4], &[0x89, b'P', b'N', b'G']);
26+
}
27+
28+
#[test]
29+
fn map_error_unauthenticated() {
30+
let img = map_error_to_image(&Error::Unauthenticated);
31+
assert_eq!(&img[..4], &[0x89, b'P', b'N', b'G']);
32+
}
33+
34+
#[test]
35+
fn cached_images_are_consistent() {
36+
let a = map_error_to_image(&Error::UrlIsUnreachable);
37+
let b = map_error_to_image(&Error::UrlIsUnreachable);
38+
assert_eq!(a, b, "LazyLock-cached images should be identical across calls");
39+
}

0 commit comments

Comments
 (0)