11use std:: str:: FromStr ;
22
3+ use jotup:: r#async:: AsyncRenderOutputExt ;
34use rostra_core:: id:: RostraId ;
45
6+ use super :: RostraRenderExt ;
57use crate :: UiState ;
68
79#[ test]
@@ -13,3 +15,227 @@ fn extract_rostra_id_link() {
1315 Some ( RostraId :: from_str( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap( ) )
1416 ) ;
1517}
18+
19+ /// Valid base32 test event ID (16 bytes = 26 base32 characters)
20+ const TEST_EVENT_ID : & str = "AAAAAAAAAAAAAAAAAAAAAAAAAA" ;
21+
22+ #[ test]
23+ fn extract_rostra_media_link ( ) {
24+ assert_eq ! (
25+ UiState :: extra_rostra_media_link( & format!( "rostra-media:{TEST_EVENT_ID}" ) ) ,
26+ Some ( rostra_core:: ShortEventId :: from_str( TEST_EVENT_ID ) . unwrap( ) )
27+ ) ;
28+ assert_eq ! ( UiState :: extra_rostra_media_link( "not-a-media-link" ) , None ) ;
29+ }
30+
31+ /// Helper to render djot content with image filter only
32+ async fn render_with_images ( content : & str , author_id : RostraId ) -> String {
33+ let renderer = jotup:: html:: tokio:: Renderer :: default ( ) . rostra_images ( author_id) ;
34+
35+ let out = renderer
36+ . render_into_document ( content)
37+ . await
38+ . expect ( "Rendering failed" ) ;
39+
40+ String :: from_utf8 ( out. into_inner ( ) ) . expect ( "valid utf8" )
41+ }
42+
43+ /// Helper to render djot content with code block filter only
44+ async fn render_with_prism ( content : & str ) -> String {
45+ let renderer = jotup:: html:: tokio:: Renderer :: default ( ) . prism_code_blocks ( ) ;
46+
47+ let out = renderer
48+ . render_into_document ( content)
49+ . await
50+ . expect ( "Rendering failed" ) ;
51+
52+ String :: from_utf8 ( out. into_inner ( ) ) . expect ( "valid utf8" )
53+ }
54+
55+ #[ tokio:: test]
56+ async fn rostra_media_renders_with_download_fallback ( ) {
57+ let author_id =
58+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
59+ let content = format ! ( "" ) ;
60+
61+ let html = render_with_images ( & content, author_id) . await ;
62+
63+ // Should contain the wrapper span
64+ assert ! ( html. contains( "m-rostraMedia" ) , "Missing wrapper class" ) ;
65+
66+ // Should contain the image with correct URL
67+ assert ! (
68+ html. contains( & format!(
69+ "/ui/media/rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy/{TEST_EVENT_ID}"
70+ ) ) ,
71+ "Missing media URL"
72+ ) ;
73+
74+ // Should contain onerror handler for download fallback
75+ assert ! ( html. contains( "onerror=" ) , "Missing onerror handler" ) ;
76+ assert ! (
77+ html. contains( "m-rostraMedia__download" ) ,
78+ "Missing download class in fallback"
79+ ) ;
80+ assert ! (
81+ html. contains( "m-rostraMedia__downloadIcon" ) ,
82+ "Missing download icon class"
83+ ) ;
84+ }
85+
86+ #[ tokio:: test]
87+ async fn rostra_media_uses_alt_text_in_fallback ( ) {
88+ let author_id =
89+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
90+ let content = format ! ( "" ) ;
91+
92+ let html = render_with_images ( & content, author_id) . await ;
93+
94+ // Should use alt text in the download link
95+ assert ! (
96+ html. contains( "my cool file" ) ,
97+ "Missing alt text in fallback"
98+ ) ;
99+
100+ // Should sanitize filename (spaces become dashes)
101+ assert ! (
102+ html. contains( "my-cool-file" ) ,
103+ "Filename not sanitized correctly"
104+ ) ;
105+ }
106+
107+ #[ tokio:: test]
108+ async fn rostra_media_empty_alt_uses_default ( ) {
109+ let author_id =
110+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
111+ let content = format ! ( "" ) ;
112+
113+ let html = render_with_images ( & content, author_id) . await ;
114+
115+ // Should use "media" as default display name
116+ assert ! (
117+ html. contains( ">media</a>" ) ,
118+ "Missing default 'media' text in fallback"
119+ ) ;
120+ }
121+
122+ #[ tokio:: test]
123+ async fn external_image_gets_lazy_loading ( ) {
124+ let author_id =
125+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
126+ let content = "" ;
127+
128+ let html = render_with_images ( content, author_id) . await ;
129+
130+ // Should have lazyload wrapper
131+ assert ! (
132+ html. contains( "lazyload-wrapper" ) ,
133+ "Missing lazyload wrapper"
134+ ) ;
135+
136+ // Should have lazyload message
137+ assert ! (
138+ html. contains( "lazyload-message" ) ,
139+ "Missing lazyload message"
140+ ) ;
141+
142+ // Should use data-src instead of src for lazy loading
143+ assert ! (
144+ html. contains( "data-src=\" https://example.com/image.png\" " ) ,
145+ "Missing data-src attribute"
146+ ) ;
147+
148+ // Should include the alt text in the load message
149+ assert ! (
150+ html. contains( "alt text" ) ,
151+ "Missing alt text in load message"
152+ ) ;
153+ }
154+
155+ #[ tokio:: test]
156+ async fn youtube_embed_gets_lazy_loading ( ) {
157+ let author_id =
158+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
159+ let content = "" ;
160+
161+ let html = render_with_images ( content, author_id) . await ;
162+
163+ // Should have lazyload wrapper
164+ assert ! (
165+ html. contains( "lazyload-wrapper" ) ,
166+ "Missing lazyload wrapper"
167+ ) ;
168+
169+ // Should have video-specific lazyload message
170+ assert ! (
171+ html. contains( "lazyload-message -video" ) ,
172+ "Missing video lazyload message"
173+ ) ;
174+
175+ // Should create an iframe with youtube embed URL
176+ assert ! (
177+ html. contains( "youtube.com/embed/dQw4w9WgXcQ" ) ,
178+ "Missing youtube embed URL"
179+ ) ;
180+
181+ // Should use data-src for lazy loading
182+ assert ! (
183+ html. contains( "data-src=\" https://www.youtube.com/embed/" ) ,
184+ "Missing data-src on iframe"
185+ ) ;
186+ }
187+
188+ #[ tokio:: test]
189+ async fn youtu_be_short_url_works ( ) {
190+ let author_id =
191+ RostraId :: from_str ( "rse1okfyp4yj75i6riwbz86mpmbgna3f7qr66aj1njceqoigjabegy" ) . unwrap ( ) ;
192+ let content = "" ;
193+
194+ let html = render_with_images ( content, author_id) . await ;
195+
196+ // Should create an iframe with youtube embed URL
197+ assert ! (
198+ html. contains( "youtube.com/embed/dQw4w9WgXcQ" ) ,
199+ "Missing youtube embed URL for short URL"
200+ ) ;
201+ }
202+
203+ #[ tokio:: test]
204+ async fn code_block_gets_prism_classes ( ) {
205+ let content = "```rust\n fn main() {}\n ```" ;
206+
207+ let html = render_with_prism ( content) . await ;
208+
209+ // Should have language class on code element
210+ assert ! (
211+ html. contains( "language-rust" ) ,
212+ "Missing language-rust class"
213+ ) ;
214+ }
215+
216+ #[ tokio:: test]
217+ async fn code_block_unknown_language ( ) {
218+ let content = "```\n plain code\n ```" ;
219+
220+ let html = render_with_prism ( content) . await ;
221+
222+ // Should still render as code block
223+ assert ! ( html. contains( "<code" ) , "Missing code element" ) ;
224+ }
225+
226+ #[ tokio:: test]
227+ async fn inline_code_not_affected_by_prism ( ) {
228+ let content = "Some `inline code` here" ;
229+
230+ let html = render_with_prism ( content) . await ;
231+
232+ // Inline code should not get language class
233+ assert ! (
234+ !html. contains( "language-" ) ,
235+ "Inline code should not have language class"
236+ ) ;
237+ assert ! (
238+ html. contains( "<code>inline code</code>" ) ,
239+ "Missing inline code"
240+ ) ;
241+ }
0 commit comments