feat: add insta snapshot testing for TUI components

- Add insta dev-dependency for visual regression testing
- Create lib.rs to expose modules for integration tests
- Add snapshot tests for progress_bar, topbar, library, help modal
- Add unit tests for LibraryState navigation
- Move all tests to tests/ directory (proper Rust convention)
- Make build.rs skip proto compilation when protoc unavailable
- Add docs/testing-possible-solutions.md with testing strategies
This commit is contained in:
Alexander
2026-05-09 11:35:10 +02:00
parent f7660436c2
commit 5bee7092d3
25 changed files with 799 additions and 115 deletions
@@ -0,0 +1,34 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
" "
" ┌─ Keybindings · evil-mode (Doom Emacs) ───────────────────────────────────────────────────────┐ "
" │Motion · char/line Search & jumps SPC leader (Doom) │ "
" │h j k l left / down / up / pat filter library SPC SPC M-x command │ "
" │w / W next word / WORD ? pat search backward SPC b +buffer (tabs) │ "
" │b / B prev word / WORD n / N next / prev match SPC f +file / library │ "
" │e / E end of word / WOR* / # search word fwd/baSPC s +search │ "
" │ge / gE back to end of (WC-o / C-i jumplist back / fwSPC w +window / pane │ "
" │0 / ^ line start (focusm{a-z} set mark SPC t +toggle / theme │ "
" │$ line end (focus r'{a-z} jump to mark line SPC n +notifications │ "
" │{N}<motion> repeat motion N t`{a-z} jump to mark exactSPC a +actions / artist│ "
" │ '' jump to last positSPC q +quit │ "
" │Motion · file/page SPC h +help │ "
" │g g first line Center · z_ SPC l/w/h/c → tab quick │ "
" │G last line z z / z . center cursor │ "
" │{N} G go to line N z t cursor → top Modes & ex commands │ "
" │g t / g T next / prev tab z b / z - cursor → bottom :w / :sync save library │ "
" │C-d / C-u ½ page down/up :q quit │ "
" │C-f / C-b page down/up :theme dark | light │ "
" │C-e / C-y scroll line down/ a · t · s · r add·toggle·search│ "
" │H / M / L viewport top / mi 1‥6 switch tab │ "
" │{ / } paragraph back/fw Enter / Esc open / back │ "
" │[[ / ]] section back/fwd ? this help │ "
" │[c / ]c prev / next chang │ "
" │ │ "
" │ │ "
" │ │ "
" │harmony · v0.4.2 · canonical evil-mode bindings (Vim/Doom Emacs) │ "
" └──────────────────────────────────────────────────────────────────────────────────────────────┘ "
" "
@@ -0,0 +1,24 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
" "
" ┌─ Keybindings · evil-mode (Doom Emacs) ───────────────┐ "
" │Motion · char/lineSearch & jumps SPC leader (Doom) │ "
" │h j k l left/ pat filtSPC SPC M-x │ "
" │w / W next? pat searSPC b +buf│ "
" │b / B prevn / N nextSPC f +fil│ "
" │e / E end * / # searSPC s +sea│ "
" │ge / gE backC-o / C-i jumpSPC w +win│ "
" │0 / ^ linem{a-z} set SPC t +tog│ "
" │$ line'{a-z} jumpSPC n +not│ "
" │{N}<motion> repe`{a-z} jumpSPC a +act│ "
" │ '' jumpSPC q +qui│ "
" │Motion · file/page SPC h +hel│ "
" │g g firsCenter · z_ SPC l/w/h/c → ta│ "
" │G lastz z / z . cent │ "
" │{N} G go tz t cursModes & ex command│ "
" │g t / g T nextz b / z - curs:w / :sync save│ "
" │harmony · v0.4.2 · canonical evil-mode bindings (Vim/D│ "
" └──────────────────────────────────────────────────────┘ "
" "
@@ -0,0 +1,34 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"┌─[ Artists · 2 ]──────────────┐┌─[ Detail · UK · Alternative ]────────────────────────────────────┐"
"│◐ Radiohead 17/22 ││Radiohead │"
"│! Pink Floyd 0/10 ││ │"
"│ ││status ● Monitored path /music/Radiohead │"
"│ ││quality FLAC size 2.5 GB │"
"│ ││albums 2 tracks 17 / 22 │"
"│ ││ │"
"│ ││─ albums ─ 2 releases │"
"│ ││● OK Computer [Album] 1997 ▰▰▰▰▰▰▰▰▰▰ 12/12 FLAC │"
"│ ││◐ Kid A [Album] 2000 ▰▰▰▰▰▱▱▱▱▱ 5/10 FLAC │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││─ tracks · OK Computer ─ 12/12 │"
"│ ││(no album selected) │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"└──────────────────────────────┘└──────────────────────────────────────────────────────────────────┘"
@@ -0,0 +1,34 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"┌─[ Artists · 0 ]──────────────┐┌─[ Detail · ]────────────────────────────────────────────────────┐"
"│ ││No artist selected │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"└──────────────────────────────┘└──────────────────────────────────────────────────────────────────┘"
@@ -0,0 +1,34 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"┌─[ Artists · 2 ]──────────────┐┌─[ Detail · UK · Alternative ]────────────────────────────────────┐"
"│◐ Radiohead 17/22 ││Radiohead │"
"│! Pink Floyd 0/10 ││ │"
"│ ││status ● Monitored path /music/Radiohead │"
"│ ││quality FLAC size 2.5 GB │"
"│ ││albums 2 tracks 17 / 22 │"
"│ ││ │"
"│ ││─ albums ─ 2 releases │"
"│ ││● OK Computer [Album] 1997 ▰▰▰▰▰▰▰▰▰▰ 12/12 FLAC │"
"│ ││◐ Kid A [Album] 2000 ▰▰▰▰▰▱▱▱▱▱ 5/10 FLAC │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││─ tracks · OK Computer ─ 12/12 │"
"│ ││(no album selected) │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"│ ││ │"
"└──────────────────────────────┘└──────────────────────────────────────────────────────────────────┘"
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"▰▰▰▰▰▰▰▰▰▰ "
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"▱▱▱▱▱▱▱▱▱▱ "
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
"▰▰▰▰▰▱▱▱▱▱ "
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
" ▲ harmony Library Wanted Queue History Calendar Settings ● Notifications "
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
" ▲ harmony Library Wanted Queue History Calendar Settings ● Notifications (5) "
@@ -0,0 +1,5 @@
---
source: tests/ui_snapshots.rs
expression: terminal.backend()
---
" ▲ harmony Library Wanted 12 Queue 5 History Calendar Settings ● Notifications (3) "
+245
View File
@@ -0,0 +1,245 @@
use ratatui::{Terminal, backend::TestBackend, widgets::Paragraph};
use ui_agregator::app::Tab;
use ui_agregator::data::{Album, AlbumStatus, Artist, MonitorState};
use ui_agregator::ui::library::{LibraryFocus, LibraryState, render_library};
use ui_agregator::ui::modals::render_help_modal;
use ui_agregator::ui::progress_bar::progress_bar;
use ui_agregator::ui::topbar::render_topbar;
fn test_artists() -> Vec<Artist> {
vec![
Artist {
id: "1".to_string(),
name: "Radiohead".to_string(),
country: "UK".to_string(),
genres: vec!["Alternative".to_string()],
monitor_state: MonitorState::Monitored,
path: "/music/Radiohead".to_string(),
quality: "FLAC".to_string(),
size_gb: 2.5,
albums: vec![
Album {
id: "a1".to_string(),
title: "OK Computer".to_string(),
year: 1997,
album_type: "Album".to_string(),
monitored: true,
total: 12,
have: 12,
quality: "FLAC".to_string(),
status: AlbumStatus::Complete,
},
Album {
id: "a2".to_string(),
title: "Kid A".to_string(),
year: 2000,
album_type: "Album".to_string(),
monitored: true,
total: 10,
have: 5,
quality: "FLAC".to_string(),
status: AlbumStatus::Partial,
},
],
},
Artist {
id: "2".to_string(),
name: "Pink Floyd".to_string(),
country: "UK".to_string(),
genres: vec!["Progressive Rock".to_string()],
monitor_state: MonitorState::Monitored,
path: "/music/Pink Floyd".to_string(),
quality: "FLAC".to_string(),
size_gb: 5.2,
albums: vec![Album {
id: "a3".to_string(),
title: "The Dark Side of the Moon".to_string(),
year: 1973,
album_type: "Album".to_string(),
monitored: true,
total: 10,
have: 0,
quality: "".to_string(),
status: AlbumStatus::Wanted,
}],
},
]
}
mod progress_bar_snapshots {
use super::*;
#[test]
fn complete() {
let mut terminal = Terminal::new(TestBackend::new(12, 1)).unwrap();
terminal
.draw(|f| {
let bar = progress_bar(10, 10, 10, AlbumStatus::Complete);
f.render_widget(Paragraph::new(bar), f.area());
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn partial() {
let mut terminal = Terminal::new(TestBackend::new(12, 1)).unwrap();
terminal
.draw(|f| {
let bar = progress_bar(5, 10, 10, AlbumStatus::Partial);
f.render_widget(Paragraph::new(bar), f.area());
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn empty() {
let mut terminal = Terminal::new(TestBackend::new(12, 1)).unwrap();
terminal
.draw(|f| {
let bar = progress_bar(0, 10, 10, AlbumStatus::Wanted);
f.render_widget(Paragraph::new(bar), f.area());
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
}
mod topbar_snapshots {
use super::*;
#[test]
fn library_active() {
let mut terminal = Terminal::new(TestBackend::new(100, 1)).unwrap();
terminal
.draw(|f| {
render_topbar(f, f.area(), Tab::Library, 0, 0, 0, false);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn with_badges() {
let mut terminal = Terminal::new(TestBackend::new(100, 1)).unwrap();
terminal
.draw(|f| {
render_topbar(f, f.area(), Tab::Queue, 5, 12, 3, false);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn notifications_open() {
let mut terminal = Terminal::new(TestBackend::new(100, 1)).unwrap();
terminal
.draw(|f| {
render_topbar(f, f.area(), Tab::Library, 0, 0, 5, true);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
}
mod library_snapshots {
use super::*;
#[test]
fn empty() {
let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
let mut state = LibraryState::new(vec![]);
terminal
.draw(|f| {
render_library(f, f.area(), &mut state);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn with_artists() {
let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
let mut state = LibraryState::new(test_artists());
terminal
.draw(|f| {
render_library(f, f.area(), &mut state);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn albums_focused() {
let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
let mut state = LibraryState::new(test_artists());
state.focus = LibraryFocus::Albums;
terminal
.draw(|f| {
render_library(f, f.area(), &mut state);
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
}
mod help_modal_snapshots {
use super::*;
#[test]
fn normal_viewport() {
let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
terminal
.draw(|f| {
render_help_modal(f, f.area());
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
#[test]
fn small_viewport() {
let mut terminal = Terminal::new(TestBackend::new(60, 20)).unwrap();
terminal
.draw(|f| {
render_help_modal(f, f.area());
})
.unwrap();
insta::assert_snapshot!(terminal.backend());
}
}
mod library_state {
use super::*;
#[test]
fn navigation() {
let mut state = LibraryState::new(test_artists());
assert_eq!(state.selected_artist_index(), Some(0));
assert_eq!(state.focus, LibraryFocus::Artists);
state.move_down();
assert_eq!(state.selected_artist_index(), Some(1));
state.focus_right();
assert_eq!(state.focus, LibraryFocus::Albums);
state.focus_left();
assert_eq!(state.focus, LibraryFocus::Artists);
}
#[test]
fn cycle_focus() {
let mut state = LibraryState::new(test_artists());
assert_eq!(state.focus, LibraryFocus::Artists);
state.cycle_focus();
assert_eq!(state.focus, LibraryFocus::Albums);
state.cycle_focus();
assert_eq!(state.focus, LibraryFocus::Tracks);
state.cycle_focus();
assert_eq!(state.focus, LibraryFocus::Artists);
}
}