test: Implemented UI snapshot tests

This commit is contained in:
2025-12-12 15:44:11 -07:00
parent c3fa689617
commit 82f30f126d
121 changed files with 3720 additions and 43 deletions
@@ -2,9 +2,12 @@
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::HorizontallyScrollableText;
use crate::models::servarr_data::sonarr::sonarr_data::{ADD_SERIES_BLOCKS, ActiveSonarrBlock};
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::add_series_ui::AddSeriesUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_add_series_ui_accepts() {
@@ -16,4 +19,35 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_add_series_ui_renders_loading_state() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
AddSeriesUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_add_series_ui_renders_search_input() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
AddSeriesUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -2,9 +2,16 @@
mod tests {
use strum::IntoEnumIterator;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS};
use crate::app::App;
use crate::models::BlockSelectionState;
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, DELETE_SERIES_BLOCKS, DELETE_SERIES_SELECTION_BLOCKS,
};
use crate::models::sonarr_models::Series;
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::delete_series_ui::DeleteSeriesUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_delete_series_ui_accepts() {
@@ -16,4 +23,28 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_delete_series_ui_renders_delete_series_toggle() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::DeleteSeriesPrompt.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
title: "Test Series".into(),
..Series::default()
}]);
app.data.sonarr_data.selected_block =
BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
DeleteSeriesUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -1,10 +1,19 @@
#[cfg(test)]
mod tests {
use bimap::BiMap;
use strum::IntoEnumIterator;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS};
use crate::app::App;
use crate::models::BlockSelectionState;
use crate::models::servarr_data::sonarr::modals::EditSeriesModal;
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EDIT_SERIES_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS,
};
use crate::models::sonarr_models::Series;
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::edit_series_ui::EditSeriesUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_edit_series_ui_accepts() {
@@ -16,4 +25,34 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_edit_series_ui_renders_edit_series_modal() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPathInput.into());
app.data.sonarr_data.quality_profile_map = BiMap::from_iter(vec![(1, "HD-1080p".to_owned())]);
app.data.sonarr_data.language_profiles_map =
BiMap::from_iter(vec![(1, "English".to_owned())]);
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
title: "Test Series".into(),
path: "/tv/test".to_owned(),
quality_profile_id: 1,
language_profile_id: 1,
..Series::default()
}]);
app.data.sonarr_data.selected_block = BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::from(&app.data.sonarr_data));
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
EditSeriesUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -1,11 +1,17 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal};
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS,
};
use crate::models::sonarr_models::{Episode, Season, Series};
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::episode_details_ui::EpisodeDetailsUi;
use strum::IntoEnumIterator;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_episode_details_ui_accepts() {
@@ -17,4 +23,111 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_episode_details_ui_renders_loading_state() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season {
season_number: 1,
..Season::default()
}]),
..Series::default()
}]);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
EpisodeDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_episode_details_ui_renders_episode_details_tab() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season {
season_number: 1,
..Season::default()
}]),
..Series::default()
}]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
season_details_modal.episode_details_modal = Some(EpisodeDetailsModal::default());
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
EpisodeDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_episode_details_ui_renders_episode_history_tab() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season {
season_number: 1,
..Season::default()
}]),
..Series::default()
}]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
let mut episode_details_modal = EpisodeDetailsModal::default();
episode_details_modal.episode_details_tabs.set_index(1);
season_details_modal.episode_details_modal = Some(episode_details_modal);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
EpisodeDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_episode_details_ui_renders_manual_search_tab() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season {
season_number: 1,
..Season::default()
}]),
..Series::default()
}]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
let mut episode_details_modal = EpisodeDetailsModal::default();
episode_details_modal.episode_details_tabs.set_index(3);
season_details_modal.episode_details_modal = Some(episode_details_modal);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
EpisodeDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -245,4 +245,41 @@ mod tests {
assert_eq!(style, row.indeterminate());
}
mod snapshot_tests {
use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::LibraryUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_library_ui_renders_loading_state() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
LibraryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_library_ui_renders_empty_series() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.data.sonarr_data.series = StatefulTable::default();
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
LibraryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -1,12 +1,18 @@
#[cfg(test)]
mod tests {
use bimap::BiMap;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::sonarr::modals::SeasonDetailsModal;
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, SEASON_DETAILS_BLOCKS,
};
use crate::models::sonarr_models::{Season, Series};
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::season_details_ui::SeasonDetailsUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_season_details_ui_accepts() {
@@ -21,4 +27,121 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_season_details_ui_renders_loading_state() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
SeasonDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_season_details_ui_renders_episodes_tab() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
SeasonDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_season_details_ui_renders_manual_search_tab() {
use crate::models::sonarr_models::{Episode, EpisodeFile, SonarrRelease};
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
app.data.sonarr_data.quality_profile_map = BiMap::from_iter(vec![(0, "Any".to_owned())]);
app.data.sonarr_data.language_profiles_map =
BiMap::from_iter(vec![(0, "English".to_owned())]);
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
app
.data
.sonarr_data
.seasons
.set_items(vec![Season::default()]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal.season_details_tabs.set_index(2);
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
season_details_modal
.episode_files
.set_items(vec![EpisodeFile::default()]);
season_details_modal
.season_releases
.set_items(vec![SonarrRelease::default()]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
SeasonDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[test]
fn test_season_details_ui_renders_season_history_tab() {
use crate::models::sonarr_models::{Episode, EpisodeFile, SonarrHistoryItem};
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
app.data.sonarr_data.quality_profile_map = BiMap::from_iter(vec![(0, "Any".to_owned())]);
app.data.sonarr_data.language_profiles_map =
BiMap::from_iter(vec![(0, "English".to_owned())]);
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
seasons: Some(vec![Season::default()]),
..Series::default()
}]);
app
.data
.sonarr_data
.seasons
.set_items(vec![Season::default()]);
let mut season_details_modal = SeasonDetailsModal::default();
season_details_modal.season_details_tabs.set_index(1);
season_details_modal
.episodes
.set_items(vec![Episode::default()]);
season_details_modal
.episode_files
.set_items(vec![EpisodeFile::default()]);
season_details_modal
.season_history
.set_items(vec![SonarrHistoryItem::default()]);
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
SeasonDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -1,12 +1,17 @@
#[cfg(test)]
mod tests {
use bimap::BiMap;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS,
};
use crate::models::sonarr_models::{Season, Series};
use crate::models::stateful_table::StatefulTable;
use crate::ui::DrawUi;
use crate::ui::sonarr_ui::library::series_details_ui::SeriesDetailsUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_series_details_ui_accepts() {
@@ -22,4 +27,33 @@ mod tests {
}
});
}
mod snapshot_tests {
use super::*;
#[test]
fn test_series_details_ui_renders_series_details() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
app.data.sonarr_data.quality_profile_map = BiMap::from_iter(vec![(1, "HD-1080p".to_owned())]);
app.data.sonarr_data.language_profiles_map =
BiMap::from_iter(vec![(1, "English".to_owned())]);
app.data.sonarr_data.series = StatefulTable::default();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
title: "Test Series".into(),
seasons: Some(vec![Season::default()]),
quality_profile_id: 1,
language_profile_id: 1,
..Series::default()
}]);
app.data.sonarr_data.series_history = Some(StatefulTable::default());
let output = render_to_string_with_app(120, 30, &mut app, |f, app| {
SeriesDetailsUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
}
}
@@ -0,0 +1,29 @@
---
source: src/ui/sonarr_ui/library/add_series_ui_tests.rs
expression: output
---
╭──────────────────────────────────── Add Series ────────────────────────────────────╮
│ │
╰──────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,29 @@
---
source: src/ui/sonarr_ui/library/add_series_ui_tests.rs
expression: output
---
╭──────────────────────────────────── Add Series ────────────────────────────────────╮
│ │
╰──────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,24 @@
---
source: src/ui/sonarr_ui/library/delete_series_ui_tests.rs
expression: output
---
╭───────────── Delete Series ─────────────╮
│ Do you really want to delete the series: │
│ Test Series? │
│ │
│ ╭───╮ │
│ Delete Series File: │ │ │
│ ╰───╯ │
│ ╭───╮ │
│ Add List Exclusion: │ │ │
│ ╰───╯ │
╰───────────────────────────────────────────╯
@@ -0,0 +1,30 @@
---
source: src/ui/sonarr_ui/library/edit_series_ui_tests.rs
expression: output
---
╭─────────────────────────── Edit - Test Series ───────────────────────────╮
│ │
│ │
│ │
│ ╭───╮ │
│ Monitored: ╰───╯ │
│ ╭───╮ │
│ Season Folder: │ │ │
│ ╰───╯ │
│ ╭───────────────────────────────────╮ │
│ Quality Profile: ╰───────────────────────────────────╯ │
│ ╭───────────────────────────────────╮ │
│ Language Profile: │English ▼ │ │
│ ╰───────────────────────────────────╯ │
│ ╭───────────────────────────────────╮ │
│ Series Type: ╰───────────────────────────────────╯ │
│ ╭───────────────────────────────────╮ │
│ Path: │/tv/test │ │
│ ╰───────────────────────────────────╯ │
│ ╭───────────────────────────────────╮ │
│ Tags: ╰───────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,30 @@
---
source: src/ui/sonarr_ui/library/episode_details_ui_tests.rs
expression: output
---
╭ Episode Details ─────────────────────────────────────────────────────────────────────╮
│ Details │ History │ File │ Manual Search │
│────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,30 @@
---
source: src/ui/sonarr_ui/library/episode_details_ui_tests.rs
expression: output
---
╭ Episode Details ─────────────────────────────────────────────────────────────────────╮
│ Details │ History │ File │ Manual Search │
│────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,5 @@
---
source: src/ui/sonarr_ui/library/episode_details_ui_tests.rs
expression: output
---
@@ -0,0 +1,30 @@
---
source: src/ui/sonarr_ui/library/episode_details_ui_tests.rs
expression: output
---
╭ Episode Details ─────────────────────────────────────────────────────────────────────╮
│ Details │ History │ File │ Manual Search │
│────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ │
│ Loading ... │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,5 @@
---
source: src/ui/sonarr_ui/library/library_ui_tests.rs
expression: output
---
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,8 @@
---
source: src/ui/sonarr_ui/library/library_ui_tests.rs
expression: output
---
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
@@ -0,0 +1,5 @@
---
source: src/ui/sonarr_ui/library/season_details_ui_tests.rs
expression: output
---
@@ -0,0 +1,5 @@
---
source: src/ui/sonarr_ui/library/season_details_ui_tests.rs
expression: output
---
@@ -0,0 +1,31 @@
---
source: src/ui/sonarr_ui/library/season_details_ui_tests.rs
expression: output
---
╭ Season 0 Details ─────────────────────────────────────────────────────────────────────────────╮
│ Episodes │ History │ Manual Search │
│─────────────────────────────────────────────────────────────────────────────────────────────────│
│ Source Age ⛔ Title Indexer Size Peers Languag Quality│
│=> 0 days 0.0 GB │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/sonarr_ui/library/season_details_ui_tests.rs
expression: output
---
╭ Season 0 Details ─────────────────────────────────────────────────────────────────────────────╮
│ Episodes │ History │ Manual Search │
│─────────────────────────────────────────────────────────────────────────────────────────────────│
│ Source Title Event Type Language Quality Date │
│=> unknown 1970-01-01 00:00:00│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,33 @@
---
source: src/ui/sonarr_ui/library/series_details_ui_tests.rs
expression: output
---
╭ Test Series ───────────────────────────────────────────────────────────────────────────────────────────╮
│Title: Test Series │
│Overview: │
│Network: │
│Status: Continuing │
│Genres: │
│Rating: 0% │
│Year: 0 │
│Runtime: 0 minutes │
│Path: │
│╭ Series Details ──────────────────────────────────────────────────────────────────────────────────────╮│
││ Seasons │ History ││
││────────────────────────────────────────────────────────────────────────────────────────────────────────││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
││ ││
│╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯