From f0cb7677e0d42f323dd38950e92016b7cc5ca4f6 Mon Sep 17 00:00:00 2001 From: Alexander Senier Date: Thu, 19 Dec 2024 21:42:27 +0100 Subject: [PATCH 1/4] Separate exercise list into current/previous exercises --- CHANGELOG.md | 1 + frontend/src/ui/component/exercise_list.rs | 162 ++++++++++++++------- 2 files changed, 108 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d755ce..9f70e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unify fonts in charts - Allow option to add exercise at any position in training session - Keep search term on exercises and routines pages when going back in history +- Display recent and previous exercises separately ### Fixed diff --git a/frontend/src/ui/component/exercise_list.rs b/frontend/src/ui/component/exercise_list.rs index 45d2f82..5647e23 100644 --- a/frontend/src/ui/component/exercise_list.rs +++ b/frontend/src/ui/component/exercise_list.rs @@ -1,10 +1,14 @@ +use chrono::{Duration, Local}; use seed::{prelude::*, *}; +use std::collections::BTreeSet; use crate::{ domain, ui::{common, data}, }; +const CURRENT_EXERCISE_CUTOFF_DAYS: i64 = 31; + // ------ ------ // Model // ------ ------ @@ -120,20 +124,49 @@ pub fn update(msg: Msg, model: &mut Model, _orders: &mut impl Orders) -> Ou // ------ ------ pub fn view(model: &Model, loading: bool, data_model: &data::Model) -> Vec> { + let cutoff = Local::now().date_naive() - Duration::days(CURRENT_EXERCISE_CUTOFF_DAYS); let muscle_filter = domain::Muscle::iter() .map(|m| (m, model.filter.muscles.contains(m))) .collect::>(); + let current_exercise_ids = data_model + .training_sessions + .iter() + .filter(|(_, s)| s.date >= cutoff) + .flat_map(|(_, session)| session.exercises()) + .collect::>(); + + let previous_exercise_ids = data_model + .training_sessions + .iter() + .filter(|(_, s)| s.date < cutoff) + .flat_map(|(_, session)| session.exercises()) + .collect::>(); + let exercises = data_model.exercises(&model.filter); - let mut exercises = exercises + + let mut current_exercises = exercises .iter() .filter(|e| { e.name .to_lowercase() .contains(model.search_term.to_lowercase().trim()) + && (current_exercise_ids.contains(&e.id) || !previous_exercise_ids.contains(&e.id)) }) .collect::>(); - exercises.sort_by(|a, b| a.name.cmp(&b.name)); + current_exercises.sort_by(|a, b| a.name.cmp(&b.name)); + + let mut previous_exercises = exercises + .iter() + .filter(|e| { + e.name + .to_lowercase() + .contains(model.search_term.to_lowercase().trim()) + && !current_exercise_ids.contains(&e.id) + && previous_exercise_ids.contains(&e.id) + }) + .collect::>(); + previous_exercises.sort_by(|a, b| a.name.cmp(&b.name)); nodes![ IF![model.view_filter_dialog => view_filter_dialog(&muscle_filter, exercises.len())], @@ -154,7 +187,7 @@ pub fn view(model: &Model, loading: bool, data_model: &data::Model) -> Vec Vec nodes!( + div![ + C!["container"], + C!["has-text-centered"], + C![format!("mb-3")], + common::view_element_with_description( + h1![C!["title"], C!["is-5"], "Previous exercises"], + &format!("Exercises not performed within the last {CURRENT_EXERCISE_CUTOFF_DAYS} days") + ), + ], + view_exercises(model, &previous_exercises)) + ] ] } +fn view_exercises(model: &Model, exercises: &[&&domain::Exercise]) -> Vec> { + if exercises.is_empty() { + return vec![]; + } + nodes![div![ + C!["table-container"], + C!["mt-2"], + table![ + C!["table"], + C!["is-fullwidth"], + C!["is-hoverable"], + tbody![exercises.iter().map(|e| { + tr![td![ + C!["is-flex"], + C!["is-justify-content-space-between"], + C!["has-text-link"], + span![ + ev(Ev::Click, { + let exercise_id = e.id; + move |_| Msg::Selected(exercise_id) + }), + e.name.to_string(), + ], + p![ + C!["is-flex is-flex-wrap-nowrap"], + if model.view_edit { + a![ + C!["icon"], + C!["mr-1"], + ev(Ev::Click, { + let exercise_id = e.id; + move |_| Msg::EditClicked(exercise_id) + }), + i![C!["fas fa-edit"]] + ] + } else { + empty![] + }, + if model.view_delete { + a![ + C!["icon"], + C!["ml-1"], + ev(Ev::Click, { + let exercise_id = e.id; + move |_| Msg::DeleteClicked(exercise_id) + }), + i![C!["fas fa-times"]] + ] + } else { + empty![] + } + ] + ]] + })], + ] + ]] +} + fn view_filter_dialog( muscle_filter: &[(&domain::Muscle, bool)], exercise_count: usize, From 27a5f75acbc1442238d1576fb5e042c410cd060d Mon Sep 17 00:00:00 2001 From: Alexander Senier Date: Sat, 21 Dec 2024 13:19:19 +0100 Subject: [PATCH 2/4] Fix typo --- valens/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valens/cli.py b/valens/cli.py index 6863337..8feb9f2 100644 --- a/valens/cli.py +++ b/valens/cli.py @@ -55,7 +55,7 @@ def main() -> int: parser_demo.add_argument( "--public", action="store_true", - help="make the server publicly available (sould be only used on a trusted network)", + help="make the server publicly available (should only be used on a trusted network)", ) parser_demo.add_argument( "--port", From 31d68f3e3826d2d1a810bf6de033883786274cd5 Mon Sep 17 00:00:00 2001 From: Alexander Senier Date: Thu, 26 Dec 2024 14:52:34 +0100 Subject: [PATCH 3/4] Run flask from venv --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f1bb611..ba5bfd6 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ run_frontend: PATH=~/.cargo/bin:${PATH} trunk --config frontend/Trunk.toml serve --port 8000 run_backend: $(CONFIG_FILE) - VALENS_CONFIG=$(CONFIG_FILE) uv run -- flask --app valens --debug run -h 0.0.0.0 + VALENS_CONFIG=$(CONFIG_FILE) uv run -- $(BUILD_DIR)/venv/bin/flask --app valens --debug run -h 0.0.0.0 $(CONFIG_FILE): $(BUILD_DIR) uv run -- valens config -d build From 3c7b77044d999f97b7bb347a29e6be5976eb4f6f Mon Sep 17 00:00:00 2001 From: Alexander Senier Date: Thu, 26 Dec 2024 17:56:55 +0100 Subject: [PATCH 4/4] Improve e2e and backend tests --- tests/backend/api_test.py | 214 ++++---------------------------------- tests/data.py | 78 +++++++------- tests/e2e/page.py | 24 ++++- tests/e2e/web_test.py | 142 ++++++++++++++++++------- tests/utils.py | 5 +- 5 files changed, 189 insertions(+), 274 deletions(-) diff --git a/tests/backend/api_test.py b/tests/backend/api_test.py index 3439349..29089e4 100644 --- a/tests/backend/api_test.py +++ b/tests/backend/api_test.py @@ -333,7 +333,7 @@ def test_delete_user(client: Client) -> None: "/api/exercises", [ {"id": 1, "name": "Exercise 1", "muscles": [{"muscle_id": 11, "stimulus": 100}]}, - {"id": 3, "name": "Exercise 2", "muscles": []}, + {"id": 3, "name": "Exercise 3", "muscles": []}, {"id": 5, "name": "Unused Exercise", "muscles": []}, ], ), @@ -470,7 +470,7 @@ def test_delete_user(client: Client) -> None: [ { "id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "routine_id": 1, "notes": "First Workout", "elements": [ @@ -555,7 +555,7 @@ def test_delete_user(client: Client) -> None: "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -567,7 +567,7 @@ def test_delete_user(client: Client) -> None: "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -598,30 +598,6 @@ def test_delete_user(client: Client) -> None: "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], @@ -726,7 +702,7 @@ def test_read_all(client: Client, user_id: int, route: str, data: list[dict[str, }, [ {"id": 1, "name": "Exercise 1", "muscles": [{"muscle_id": 11, "stimulus": 100}]}, - {"id": 3, "name": "Exercise 2", "muscles": []}, + {"id": 3, "name": "Exercise 3", "muscles": []}, { "id": 6, "name": "New Exercise", @@ -1095,7 +1071,7 @@ def test_create_workout( } result = [ { - "date": "2002-02-20", + "date": "2002-01-11", "id": 1, "notes": "First Workout", "routine_id": 1, @@ -1181,7 +1157,7 @@ def test_create_workout( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -1193,7 +1169,7 @@ def test_create_workout( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -1224,30 +1200,6 @@ def test_create_workout( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, created, @@ -1366,10 +1318,10 @@ def test_create_workout( {"muscle_id": 12, "stimulus": 100}, ], }, - {"id": 3, "name": "Exercise 2", "muscles": []}, + {"id": 3, "name": "Exercise 3", "muscles": []}, {"id": 5, "name": "Unused Exercise", "muscles": []}, ], - {"name": "Exercise 2", "muscles": []}, + {"name": "Exercise 3", "muscles": []}, ), ( "/api/routines/1", @@ -1716,7 +1668,7 @@ def test_create_workout( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -1728,7 +1680,7 @@ def test_create_workout( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -1759,30 +1711,6 @@ def test_create_workout( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], @@ -2833,7 +2761,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -2845,7 +2773,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -2876,30 +2804,6 @@ def test_replace( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], @@ -2912,7 +2816,7 @@ def test_replace( }, { "id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "routine_id": 1, "notes": "", "elements": [ @@ -2957,7 +2861,7 @@ def test_replace( [ { "id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "routine_id": 1, "notes": "", "elements": [ @@ -3042,7 +2946,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -3054,7 +2958,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -3085,30 +2989,6 @@ def test_replace( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], @@ -3151,7 +3031,7 @@ def test_replace( { "id": 1, "routine_id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "notes": "First Workout", "elements": [ { @@ -3187,7 +3067,7 @@ def test_replace( [ { "id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "routine_id": 1, "notes": "First Workout", "elements": [ @@ -3264,7 +3144,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 6, "time": None, "weight": None, @@ -3276,7 +3156,7 @@ def test_replace( "automatic": False, }, { - "exercise_id": 3, + "exercise_id": 4, "reps": 5, "time": None, "weight": None, @@ -3307,30 +3187,6 @@ def test_replace( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], @@ -3513,7 +3369,7 @@ def test_modify( [ { "id": 1, - "date": "2002-02-20", + "date": "2002-01-11", "routine_id": 1, "notes": "First Workout", "elements": [ @@ -3573,30 +3429,6 @@ def test_modify( "target_rpe": None, "automatic": False, }, - { - "exercise_id": 1, - "reps": 9, - "time": 4, - "weight": None, - "rpe": 8.0, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, - { - "exercise_id": 1, - "reps": None, - "time": 60, - "weight": None, - "rpe": 8.5, - "target_reps": None, - "target_time": None, - "target_weight": None, - "target_rpe": None, - "automatic": False, - }, ], }, ], diff --git a/tests/data.py b/tests/data.py index d6e1b9d..1535a6b 100644 --- a/tests/data.py +++ b/tests/data.py @@ -25,7 +25,11 @@ def users_only() -> list[User]: ] -def users() -> list[User]: +def users(today: datetime.date = datetime.date(2002, 3, 12)) -> list[User]: + + def days_ago(days: int) -> datetime.date: + return today - datetime.timedelta(days) + exercise_1 = Exercise( id=1, user_id=1, @@ -33,8 +37,8 @@ def users() -> list[User]: muscles=[ExerciseMuscle(user_id=1, muscle_id=11, stimulus=100)], ) exercise_2 = Exercise(id=2, user_id=2, name="Exercise 2") - exercise_3 = Exercise(id=3, user_id=1, name="Exercise 2") - exercise_4 = Exercise(id=4, user_id=2, name="Exercise 3") + exercise_3 = Exercise(id=3, user_id=1, name="Exercise 3") + exercise_4 = Exercise(id=4, user_id=2, name="Exercise 4") exercise_5 = Exercise(id=5, user_id=1, name="Unused Exercise") return [ @@ -43,14 +47,14 @@ def users() -> list[User]: name="Alice", sex=Sex.FEMALE, body_weight=[ - BodyWeight(user_id=1, date=datetime.date(2002, 2, 20), weight=67.5), - BodyWeight(user_id=1, date=datetime.date(2002, 2, 21), weight=67.7), - BodyWeight(user_id=1, date=datetime.date(2002, 2, 22), weight=67.3), + BodyWeight(user_id=1, date=days_ago(20), weight=67.5), + BodyWeight(user_id=1, date=days_ago(19), weight=67.7), + BodyWeight(user_id=1, date=days_ago(18), weight=67.3), ], body_fat=[ BodyFat( user_id=1, - date=datetime.date(2002, 2, 20), + date=days_ago(20), chest=1, abdominal=2, thigh=3, @@ -61,7 +65,7 @@ def users() -> list[User]: ), BodyFat( user_id=1, - date=datetime.date(2002, 2, 21), + date=days_ago(19), chest=None, abdominal=None, thigh=10, @@ -72,9 +76,9 @@ def users() -> list[User]: ), ], period=[ - Period(date=datetime.date(2002, 2, 20), intensity=2), - Period(date=datetime.date(2002, 2, 21), intensity=4), - Period(date=datetime.date(2002, 2, 22), intensity=1), + Period(date=days_ago(20), intensity=2), + Period(date=days_ago(19), intensity=4), + Period(date=days_ago(18), intensity=1), ], exercises=[ exercise_1, @@ -221,7 +225,7 @@ def users() -> list[User]: id=1, user_id=1, routine_id=1, - date=datetime.date(2002, 2, 20), + date=days_ago(60), notes="First Workout", elements=[ WorkoutSet(position=1, exercise=exercise_3, reps=10, time=4, rpe=8.0), @@ -232,26 +236,24 @@ def users() -> list[User]: Workout( id=3, user_id=1, - date=datetime.date(2002, 2, 22), + date=days_ago(18), notes=None, elements=[ WorkoutSet(position=1, exercise=exercise_3, reps=9), WorkoutSet(position=2, exercise=exercise_3, reps=8), WorkoutSet(position=3, exercise=exercise_3, reps=7), - WorkoutSet(position=4, exercise=exercise_3, reps=6), - WorkoutSet(position=5, exercise=exercise_3, reps=5), + WorkoutSet(position=4, exercise=exercise_4, reps=6), + WorkoutSet(position=5, exercise=exercise_4, reps=5), ], ), Workout( id=4, user_id=1, routine_id=1, - date=datetime.date(2002, 2, 24), + date=days_ago(16), notes=None, elements=[ WorkoutSet(position=1, exercise=exercise_3, reps=11, time=4, rpe=8.5), - WorkoutSet(position=2, exercise=exercise_1, reps=9, time=4, rpe=8), - WorkoutSet(position=3, exercise=exercise_1, time=60, rpe=8.5), ], ), ], @@ -261,28 +263,28 @@ def users() -> list[User]: name="Bob", sex=Sex.MALE, body_weight=[ - BodyWeight(user_id=2, date=datetime.date(2002, 2, 20), weight=100), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 21), weight=101), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 22), weight=102), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 24), weight=104), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 25), weight=105), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 26), weight=106), - BodyWeight(user_id=2, date=datetime.date(2002, 2, 28), weight=108), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 1), weight=109), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 2), weight=110), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 3), weight=111), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 5), weight=113), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 6), weight=114), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 7), weight=115), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 8), weight=116), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 10), weight=118), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 11), weight=119), - BodyWeight(user_id=2, date=datetime.date(2002, 3, 12), weight=120), + BodyWeight(user_id=2, date=days_ago(20), weight=100), + BodyWeight(user_id=2, date=days_ago(19), weight=101), + BodyWeight(user_id=2, date=days_ago(18), weight=102), + BodyWeight(user_id=2, date=days_ago(16), weight=104), + BodyWeight(user_id=2, date=days_ago(15), weight=105), + BodyWeight(user_id=2, date=days_ago(14), weight=106), + BodyWeight(user_id=2, date=days_ago(12), weight=108), + BodyWeight(user_id=2, date=days_ago(11), weight=109), + BodyWeight(user_id=2, date=days_ago(10), weight=110), + BodyWeight(user_id=2, date=days_ago(9), weight=111), + BodyWeight(user_id=2, date=days_ago(7), weight=113), + BodyWeight(user_id=2, date=days_ago(6), weight=114), + BodyWeight(user_id=2, date=days_ago(5), weight=115), + BodyWeight(user_id=2, date=days_ago(4), weight=116), + BodyWeight(user_id=2, date=days_ago(2), weight=118), + BodyWeight(user_id=2, date=days_ago(1), weight=119), + BodyWeight(user_id=2, date=days_ago(0), weight=120), ], body_fat=[ BodyFat( user_id=2, - date=datetime.date(2002, 2, 20), + date=days_ago(20), chest=15, abdominal=16, thigh=17, @@ -293,7 +295,7 @@ def users() -> list[User]: ), BodyFat( user_id=2, - date=datetime.date(2002, 2, 22), + date=days_ago(18), chest=22, abdominal=23, thigh=24, @@ -353,7 +355,7 @@ def users() -> list[User]: Workout( id=2, user_id=2, - date=datetime.date(2002, 2, 20), + date=days_ago(20), notes="", elements=[ WorkoutSet( diff --git a/tests/e2e/page.py b/tests/e2e/page.py index cf8f6c3..c9aee95 100644 --- a/tests/e2e/page.py +++ b/tests/e2e/page.py @@ -319,13 +319,29 @@ def wait_for_table_value(self, table: int, row: int, column: int, text: str) -> ) ) - def get_table_value(self, index: int) -> str: - return self._driver.find_element(by=By.XPATH, value=f"//tr/td[{index}]").text + def get_table_headers(self) -> dict[str, int]: + return { + th.text: index + for index, th in enumerate( + self._driver.find_elements( + by=By.XPATH, + value="//table[contains(@class, 'is-hoverable')]/thead/tr/th", + ), + start=1, + ) + } + + def get_table_value(self, table: int, row: int, column: int) -> str: + return self._driver.find_element( + by=By.XPATH, + value=f"(//table[contains(@class, 'is-hoverable')])" + f"[{table}]/tbody/tr[{row}]/td[{column}]", + ).text - def get_table_body(self) -> list[list[str]]: + def get_table_body(self, table: int = 1) -> list[list[str]]: return [ [td.text for td in tr.find_elements(By.TAG_NAME, "td")] - for tr in self._driver.find_elements(By.XPATH, "//tbody/tr") + for tr in self._driver.find_elements(By.XPATH, f"(//tbody)[{table}]/tr") ] def wait_for_fab(self, icon: str) -> None: diff --git a/tests/e2e/web_test.py b/tests/e2e/web_test.py index 08ba989..abc29e7 100644 --- a/tests/e2e/web_test.py +++ b/tests/e2e/web_test.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import os from collections.abc import Generator from pathlib import Path @@ -32,10 +33,31 @@ TrainingSessionPage, ) -USERS = tests.data.users() +TODAY = datetime.date.today() +USERS = tests.data.users(today=TODAY) USER = USERS[0] USERNAMES = [user.name for user in USERS] +EXERCISES_IN_CURRENT_WORKOUTS = { + e.exercise.name + for w in USER.workouts + for e in w.elements + if isinstance(e, models.WorkoutSet) and w.date >= TODAY - datetime.timedelta(31) +} + +PREVIOUS_WORKOUT_EXERCISES = { + e.exercise.name + for w in USER.workouts + for e in w.elements + if isinstance(e, models.WorkoutSet) and e.exercise.name not in EXERCISES_IN_CURRENT_WORKOUTS +} + +CURRENT_WORKOUT_EXERCISES = { + e.name + for e in USER.exercises + if e.name in EXERCISES_IN_CURRENT_WORKOUTS or e.name not in PREVIOUS_WORKOUT_EXERCISES +} + @pytest.fixture(autouse=True) def _fixture_backend() -> Generator[None, None, None]: @@ -47,7 +69,7 @@ def _fixture_backend() -> Generator[None, None, None]: with app.app_context(): app.config["DATABASE"] = f"sqlite:///{db_file}" app.config["SECRET_KEY"] = b"TEST_KEY" - tests.utils.init_db_data() + tests.utils.init_db_data(today=TODAY) with Popen( f"{VALENS} run --port {PORT}".split(), @@ -130,8 +152,8 @@ def test_body_weight_add(driver: webdriver.Chrome) -> None: page.body_weight_dialog.click_cancel() - assert page.get_table_value(1) != date - assert page.get_table_value(2) != weight + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != weight page.click_fab() @@ -139,8 +161,8 @@ def test_body_weight_add(driver: webdriver.Chrome) -> None: page.body_weight_dialog.set_weight(weight) - assert page.get_table_value(1) != date - assert page.get_table_value(2) != weight + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != weight page.body_weight_dialog.click_save() @@ -208,29 +230,49 @@ def test_body_fat_add(driver: webdriver.Chrome) -> None: page.wait_for_dialog() date = page.body_fat_dialog.get_date() - values = ("1", "2", "3", "4", "5", "6", "7") page.body_fat_dialog.click_cancel() - assert page.get_table_value(1) != date - for i, v in enumerate(values, start=4): - assert page.get_table_value(i) != v + current_values = { + v.date.isoformat(): { + "tricep": v.tricep or "-", + "suprailiac": v.suprailiac or "-", + "thigh": v.thigh or "-", + "chest": v.chest or "-", + "abdominal": v.abdominal or "-", + "subscapular": v.subscapular or "-", + "midaxillary": v.midaxillary or "-", + } + for v in USER.body_fat + } + headers = {k.lower().split(" ")[0]: v for k, v in page.get_table_headers().items()} + + for row in range(1, 2): + date = page.get_table_value(1, row, 1) + for entry, value in current_values[date].items(): + assert page.get_table_value(1, row, headers[entry]) == str(value) page.click_fab() page.wait_for_dialog() - page.body_fat_dialog.set_jp7(values) - - assert page.get_table_value(1) != date - for i, v in enumerate(values, start=4): - assert page.get_table_value(i) != v + items = ( + "tricep", + "suprailiac", + "thigh", + "chest", + "abdominal", + "subscapular", + "midaxillary", + ) + values = ("1", "2", "3", "4", "5", "6", "7") + page.body_fat_dialog.set_jp7(values) page.body_fat_dialog.click_save() - page.wait_for_table_value(1, 1, 1, date) - for i, v in enumerate(values, start=4): - page.wait_for_table_value(1, 1, i, v) + page.wait_for_table_value(1, 1, 1, TODAY.isoformat()) + for item, value in zip(items, values): + page.wait_for_table_value(1, 1, headers[item], value) def test_body_fat_edit(driver: webdriver.Chrome) -> None: @@ -306,8 +348,8 @@ def test_menstrual_cycle_add(driver: webdriver.Chrome) -> None: page.period_dialog.click_cancel() - assert page.get_table_value(1) != date - assert page.get_table_value(2) != intensity + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != intensity page.click_fab() @@ -315,8 +357,8 @@ def test_menstrual_cycle_add(driver: webdriver.Chrome) -> None: page.period_dialog.set_period(intensity) - assert page.get_table_value(1) != date - assert page.get_table_value(2) != intensity + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != intensity page.period_dialog.click_save() @@ -417,8 +459,8 @@ def test_training_add(driver: webdriver.Chrome) -> None: page.training_dialog.click_cancel() - assert page.get_table_value(1) != date - assert page.get_table_value(2) != routine + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != routine page.click_fab() @@ -426,8 +468,8 @@ def test_training_add(driver: webdriver.Chrome) -> None: page.training_dialog.set_routine(routine) - assert page.get_table_value(1) != date - assert page.get_table_value(2) != routine + assert page.get_table_value(1, 1, 1) != date + assert page.get_table_value(1, 1, 2) != routine page.training_dialog.click_save() @@ -490,7 +532,7 @@ def test_training_session(driver: webdriver.Chrome) -> None: page.load() page.wait_for_title(str(workout.date)) - assert page.get_sets() == [*sets, "Pecs", "2"] + assert page.get_sets() == sets page.edit() @@ -1287,7 +1329,7 @@ def test_routine_delete_training_session(driver: webdriver.Chrome) -> None: def test_exercises_add(driver: webdriver.Chrome) -> None: - exercise = sorted(USER.exercises, key=lambda x: x.name)[0] + exercise = sorted(USER.exercises, key=lambda x: x.name)[1] name = exercise.name new_name = "A Exercise" @@ -1296,6 +1338,15 @@ def test_exercises_add(driver: webdriver.Chrome) -> None: login(driver) page = ExercisesPage(driver) page.load() + + expected_current = {e.name for e in USER.exercises if e.name in CURRENT_WORKOUT_EXERCISES} + present_current = {e[0] for e in page.get_table_body(1)} + assert expected_current == present_current + + expected_previous = {e.name for e in USER.exercises if e.name in PREVIOUS_WORKOUT_EXERCISES} + present_previous = {e[0] for e in page.get_table_body(2)} + assert expected_previous == present_previous + page.click_fab() page.wait_for_dialog() @@ -1315,23 +1366,22 @@ def test_exercises_add(driver: webdriver.Chrome) -> None: def test_exercises_edit(driver: webdriver.Chrome) -> None: - exercise = sorted(USER.exercises, key=lambda x: x.name)[0] - name = str(exercise.name) + current_name = sorted(CURRENT_WORKOUT_EXERCISES)[0] new_name = "Changed Exercise" - assert name != new_name + assert current_name != new_name login(driver) page = ExercisesPage(driver) page.load() - page.wait_for_table_value(1, 1, 1, name) + page.wait_for_table_value(1, 1, 1, current_name) page.click_edit(0) page.exercises_dialog.set_name(new_name) page.exercises_dialog.click_cancel() - page.wait_for_table_value(1, 1, 1, name) + page.wait_for_table_value(1, 1, 1, current_name) page.click_edit(0) page.exercises_dialog.set_name(new_name) @@ -1341,25 +1391,39 @@ def test_exercises_edit(driver: webdriver.Chrome) -> None: def test_exercises_delete(driver: webdriver.Chrome) -> None: - exercises = sorted(USER.exercises, key=lambda x: x.name) - name_1 = str(exercises[0].name) - name_2 = str(exercises[1].name) + current_exercises = sorted(CURRENT_WORKOUT_EXERCISES) + current_name_1 = current_exercises[0] + current_name_2 = current_exercises[1] login(driver) page = ExercisesPage(driver) page.load() - page.wait_for_table_value(1, 1, 1, name_1) + page.wait_for_table_value(1, 1, 1, current_name_1) page.click_delete(0) page.delete_dialog.click_no() - page.wait_for_table_value(1, 1, 1, name_1) + page.wait_for_table_value(1, 1, 1, current_name_1) page.click_delete(0) page.delete_dialog.click_yes() - page.wait_for_table_value(1, 1, 1, name_2) + page.wait_for_table_value(1, 1, 1, current_name_2) + + previous_name = sorted(PREVIOUS_WORKOUT_EXERCISES)[0] + + page.wait_for_table_value(2, 1, 1, previous_name) + + page.click_delete(0) + page.delete_dialog.click_no() + + page.wait_for_table_value(2, 1, 1, previous_name) + + page.click_delete(0) + page.delete_dialog.click_yes() + + assert page.get_table_body(2) == [] def test_exercise_delete_workout(driver: webdriver.Chrome) -> None: diff --git a/tests/utils.py b/tests/utils.py index 0c79a8a..f5d00c4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,4 @@ +import datetime import sqlite3 from sqlalchemy import select @@ -13,8 +14,8 @@ def init_db_users() -> None: db.session.commit() -def init_db_data() -> None: - for user in tests.data.users(): +def init_db_data(today: datetime.date = datetime.date(2002, 3, 12)) -> None: + for user in tests.data.users(today=today): db.session.add(user) db.session.commit()