1#[derive(Debug, Clone, PartialEq)]
6pub struct RecordingFixture {
7 pub id: &'static str,
9 pub title: &'static str,
11 pub captured_at: &'static str,
13 pub duration: &'static str,
15 pub size: &'static str,
17 pub selected: bool,
19}
20
21#[must_use]
23pub fn sample_recordings() -> Vec<RecordingFixture> {
24 vec![
25 RecordingFixture {
26 id: "rec-01",
27 title: "Demo · auth login",
28 captured_at: "Captured 2026-05-09",
29 duration: "1m 24s",
30 size: "328 MB",
31 selected: true,
32 },
33 RecordingFixture {
34 id: "rec-02",
35 title: "Standup recap",
36 captured_at: "Captured 2026-05-08",
37 duration: "4m 02s",
38 size: "1.1 GB",
39 selected: false,
40 },
41 RecordingFixture {
42 id: "rec-03",
43 title: "Bug repro · payments",
44 captured_at: "Captured 2026-05-07",
45 duration: "0m 38s",
46 size: "112 MB",
47 selected: false,
48 },
49 RecordingFixture {
50 id: "rec-04",
51 title: "Onboarding tour",
52 captured_at: "Captured 2026-05-06",
53 duration: "2m 11s",
54 size: "604 MB",
55 selected: false,
56 },
57 ]
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
62pub struct StorageMeter {
63 pub used_bytes: u64,
65 pub total_bytes: u64,
67}
68
69impl StorageMeter {
70 #[must_use]
72 pub fn fraction(self) -> f32 {
73 if self.total_bytes == 0 {
74 0.0
75 } else {
76 #[allow(
77 clippy::cast_precision_loss,
78 reason = "ratio fits f32 without meaningful loss"
79 )]
80 let used = self.used_bytes as f32;
81 #[allow(
82 clippy::cast_precision_loss,
83 reason = "ratio fits f32 without meaningful loss"
84 )]
85 let total = self.total_bytes as f32;
86 (used / total).clamp(0.0, 1.0)
87 }
88 }
89}
90
91#[must_use]
93pub const fn sample_storage_meter() -> StorageMeter {
94 StorageMeter {
95 used_bytes: 33_285_996_544,
96 total_bytes: 53_687_091_200,
97 }
98}
99
100#[must_use]
103pub fn sample_recording_cards() -> Vec<crate::components::library::RecordingCardView> {
104 use crate::components::library::{
105 RecordingCardState, RecordingCardView, RecordingMetricsView, ThumbnailView,
106 };
107 vec![
108 RecordingCardView {
109 id: "rec-01".into(),
110 title: "Demo · auth login".into(),
111 subtitle: "Captured 2026-05-09 · 1080p".into(),
112 duration_label: "1m 24s".into(),
113 category: Some("Tutorial".into()),
114 metrics: RecordingMetricsView {
115 views: 142,
116 comments: 6,
117 reactions: 12,
118 },
119 thumbnail: ThumbnailView {
120 css_background: "linear-gradient(135deg,#1e3a8a,#7c3aed)".into(),
121 },
122 state: RecordingCardState::Ready,
123 selected: true,
124 },
125 RecordingCardView {
126 id: "rec-02".into(),
127 title: "Standup recap".into(),
128 subtitle: "Captured 2026-05-08 · 4K".into(),
129 duration_label: "4m 02s".into(),
130 category: Some("Demo".into()),
131 metrics: RecordingMetricsView {
132 views: 38,
133 comments: 1,
134 reactions: 4,
135 },
136 thumbnail: ThumbnailView {
137 css_background: "linear-gradient(135deg,#0f766e,#facc15)".into(),
138 },
139 state: RecordingCardState::Processing { percent: 64 },
140 selected: false,
141 },
142 RecordingCardView {
143 id: "rec-03".into(),
144 title: "Bug repro · payments".into(),
145 subtitle: "Captured 2026-05-07".into(),
146 duration_label: "0m 38s".into(),
147 category: Some("Bug".into()),
148 metrics: RecordingMetricsView {
149 views: 7,
150 comments: 3,
151 reactions: 0,
152 },
153 thumbnail: ThumbnailView {
154 css_background: "linear-gradient(135deg,#7f1d1d,#f97316)".into(),
155 },
156 state: RecordingCardState::Ready,
157 selected: false,
158 },
159 RecordingCardView {
160 id: "rec-04".into(),
161 title: "Onboarding tour".into(),
162 subtitle: "Captured 2026-05-06 · 1080p".into(),
163 duration_label: "2m 11s".into(),
164 category: None,
165 metrics: RecordingMetricsView {
166 views: 88,
167 comments: 0,
168 reactions: 9,
169 },
170 thumbnail: ThumbnailView {
171 css_background: String::new(),
172 },
173 state: RecordingCardState::Ready,
174 selected: false,
175 },
176 ]
177}
178
179#[must_use]
181pub fn sample_library_grid() -> crate::components::library::LibraryGridView {
182 use crate::components::library::{
183 LibraryGridView, LibraryLayoutMode, LibraryToolbarView, RecordingFilterView,
184 };
185 LibraryGridView {
186 toolbar: LibraryToolbarView {
187 filters: vec![
188 RecordingFilterView {
189 id: "all",
190 label: "All",
191 active: true,
192 },
193 RecordingFilterView {
194 id: "tutorial",
195 label: "Tutorials",
196 active: false,
197 },
198 RecordingFilterView {
199 id: "demo",
200 label: "Demos",
201 active: false,
202 },
203 RecordingFilterView {
204 id: "bug",
205 label: "Bug repros",
206 active: false,
207 },
208 ],
209 sort_label: "Most recent",
210 layout: LibraryLayoutMode::Grid,
211 },
212 recordings: sample_recording_cards(),
213 empty_message: "No recordings yet — press ⌘⇧2 to start one.",
214 }
215}
216
217#[must_use]
219pub fn sample_library_grid_empty() -> crate::components::library::LibraryGridView {
220 let mut v = sample_library_grid();
221 v.recordings.clear();
222 v
223}
224
225#[must_use]
227pub fn sample_library_grid_list_mode() -> crate::components::library::LibraryGridView {
228 use crate::components::library::LibraryLayoutMode;
229 let mut v = sample_library_grid();
230 v.toolbar.layout = LibraryLayoutMode::List;
231 v
232}
233
234fn library_primary_nav(inbox_unread: u32) -> Vec<crate::components::library::LibraryNavItemView> {
235 use crate::components::library::LibraryNavItemView;
236 vec![
237 LibraryNavItemView {
238 id: "new",
239 label: "New recording",
240 icon: "+",
241 count: None,
242 badge: None,
243 selected: false,
244 disabled: false,
245 },
246 LibraryNavItemView {
247 id: "all",
248 label: "All recordings",
249 icon: "▦",
250 count: Some(42),
251 badge: None,
252 selected: true,
253 disabled: false,
254 },
255 LibraryNavItemView {
256 id: "starred",
257 label: "Starred",
258 icon: "★",
259 count: Some(7),
260 badge: None,
261 selected: false,
262 disabled: false,
263 },
264 LibraryNavItemView {
265 id: "shared",
266 label: "Shared with me",
267 icon: "↗",
268 count: Some(3),
269 badge: None,
270 selected: false,
271 disabled: false,
272 },
273 LibraryNavItemView {
274 id: "inbox",
275 label: "Inbox",
276 icon: "✉",
277 count: None,
278 badge: (inbox_unread > 0).then_some(inbox_unread),
279 selected: false,
280 disabled: false,
281 },
282 ]
283}
284
285fn library_spaces_and_tags() -> Vec<crate::components::library::LibrarySectionView> {
286 use crate::components::library::{LibraryNavItemView, LibrarySectionView};
287 let spaces = LibrarySectionView {
288 heading: "SPACES",
289 items: vec![
290 LibraryNavItemView {
291 id: "space-team",
292 label: "Northwind Studio",
293 icon: "🟢",
294 count: Some(24),
295 badge: None,
296 selected: false,
297 disabled: false,
298 },
299 LibraryNavItemView {
300 id: "space-product",
301 label: "Product reviews",
302 icon: "🟣",
303 count: Some(8),
304 badge: None,
305 selected: false,
306 disabled: false,
307 },
308 ],
309 };
310 let tags = LibrarySectionView {
311 heading: "TAGS",
312 items: vec![
313 LibraryNavItemView {
314 id: "tag-tutorial",
315 label: "Tutorials",
316 icon: "🟦",
317 count: Some(12),
318 badge: None,
319 selected: false,
320 disabled: false,
321 },
322 LibraryNavItemView {
323 id: "tag-bug",
324 label: "Bug repros",
325 icon: "🟥",
326 count: Some(6),
327 badge: None,
328 selected: false,
329 disabled: false,
330 },
331 LibraryNavItemView {
332 id: "tag-demo",
333 label: "Customer demos",
334 icon: "🟨",
335 count: Some(3),
336 badge: None,
337 selected: false,
338 disabled: false,
339 },
340 ],
341 };
342 vec![spaces, tags]
343}
344
345#[must_use]
348pub fn sample_library_sidebar(inbox_unread: u32) -> crate::components::library::LibrarySidebarView {
349 use crate::components::library::{LibrarySidebarView, StorageMeterView};
350 LibrarySidebarView {
351 primary: library_primary_nav(inbox_unread),
352 sections: library_spaces_and_tags(),
353 storage: StorageMeterView {
354 used_bytes_label: "12.4 GB".to_owned(),
355 quota_label: "50 GB".to_owned(),
356 percent_used: sample_storage_meter().fraction(),
357 plan_label: Some("Free"),
358 },
359 }
360}
361
362#[must_use]
365pub fn sample_library_sidebar_inbox_active() -> crate::components::library::LibrarySidebarView {
366 let mut v = sample_library_sidebar(3);
367 for item in &mut v.primary {
368 item.selected = item.id == "inbox";
369 }
370 v
371}
372
373#[must_use]
375pub fn sample_library_sidebar_high_storage() -> crate::components::library::LibrarySidebarView {
376 let mut v = sample_library_sidebar(0);
377 "47.6 GB".clone_into(&mut v.storage.used_bytes_label);
378 v.storage.percent_used = 0.95;
379 v
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn library_sidebar_has_primary_and_sections() {
388 let v = sample_library_sidebar(3);
389 assert!(v.primary.iter().any(|i| i.id == "inbox"));
390 assert_eq!(v.sections.len(), 2);
391 assert_eq!(v.sections[0].heading, "SPACES");
392 assert_eq!(v.sections[1].heading, "TAGS");
393 }
394
395 #[test]
396 fn high_storage_variant_passes_warn_threshold() {
397 let v = sample_library_sidebar_high_storage();
398 assert!(v.storage.percent_used >= 0.85);
399 }
400
401 #[test]
402 fn recordings_non_empty_with_one_selected() {
403 let r = sample_recordings();
404 assert!(!r.is_empty());
405 assert_eq!(r.iter().filter(|x| x.selected).count(), 1);
406 }
407
408 #[test]
409 fn storage_fraction_in_unit_interval() {
410 let f = sample_storage_meter().fraction();
411 assert!((0.0..=1.0).contains(&f));
412 }
413}