Repository: feder-observatory/stellarphot

Issue:
Need way to cancel an edit of a camera, etc
Right now there is no easy way to stop editing something if you decide you don't want to make any changes. The obvious way to do that is with the "Revert" button (the yellow counter-clockwise arrow button).

Clicking on that button should set the UI to the same state it would be in if you had selected that item.


Discussion:
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/feder-observatory/stellarphot/pull/315?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=feder-observatory) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 73.90%. Comparing base [(`d901d65`)](https://app.codecov.io/gh/feder-observatory/stellarphot/commit/d901d65473ae1f34f00ef9b7dc88c655df7dc798?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=feder-observatory) to head [(`955e174`)](https://app.codecov.io/gh/feder-observatory/stellarphot/pull/315?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=feder-observatory).
> Report is 4 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #315      +/-   ##
==========================================
+ Coverage   73.86%   73.90%   +0.03%     
==========================================
  Files          27       27              
  Lines        3409     3414       +5     
==========================================
+ Hits         2518     2523       +5     
  Misses        891      891              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/feder-observatory/stellarphot/pull/315?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=feder-observatory).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=feder-observatory).


Patch:
diff --git a/stellarphot/settings/custom_widgets.py b/stellarphot/settings/custom_widgets.py
index 1a26f59..e50a9c3 100644
--- a/stellarphot/settings/custom_widgets.py
+++ b/stellarphot/settings/custom_widgets.py
@@ -224,6 +224,9 @@ class ChooseOrMakeNew(ipw.VBox):
         # Update the current state of the widget
         self._editing = True
 
+        # Enable the revert button so that the user can cancel the edit
+        self._item_widget.savebuttonbar.bn_revert.disabled = False
+
     def _set_disable_state_nested_models(self, top, value):
         """
         When a one model contains another and the top-level model widget
@@ -297,9 +300,26 @@ class ChooseOrMakeNew(ipw.VBox):
                 # Make sure the edit button is displayed
                 self._edit_button.layout.display = "flex"
 
+        def revert_to_saved_value():
+            """
+            Revert the widget to the saved value and end editing.
+
+            This should only apply while editing. If you are making a new
+            item you can either select a different item (if there are any) or
+            you really need to make a new one.
+            """
+            if self._editing:
+                # We have a selection so we need to stop editing...
+                self._editing = False
+
+                # ...and trigger the selection handler.
+                self._handle_selection({"new": self._choose_existing.value})
+
         # This is the mechanism for adding callbacks to the save button.
         new_widget.savebuttonbar.fns_onsave_add_action(saver)
         new_widget.savebuttonbar.fns_onsave_add_action(update_choices_and_select_new)
+        new_widget.savebuttonbar.fns_onrevert_add_action(revert_to_saved_value)
+
         return new_widget, new_widget.value.copy()
 
     def _handle_confirmation(self):


Test patch:
diff --git a/stellarphot/settings/tests/test_custom_widgets.py b/stellarphot/settings/tests/test_custom_widgets.py
index 153fd3b..d26f767 100644
--- a/stellarphot/settings/tests/test_custom_widgets.py
+++ b/stellarphot/settings/tests/test_custom_widgets.py
@@ -331,6 +331,76 @@ class TestChooseOrMakeNew:
         # The save button should be disabled
         assert choose_or_make_new._item_widget.savebuttonbar.bn_save.disabled
 
+    def test_revert_button_is_enabled_after_clicking_edit(self, tmp_path):
+        # The revert button should be enabled after clicking the edit button so
+        # the user can cancel the edit.
+        saved = SavedSettings(_testing_path=tmp_path)
+        camera = Camera(**TEST_CAMERA_VALUES)
+        saved.add_item(camera)
+
+        choose_or_make_new = ChooseOrMakeNew("camera", _testing_path=tmp_path)
+        # Simulate a click on the edit button...
+        choose_or_make_new._edit_button.click()
+
+        # The revert button should be enabled
+        assert not choose_or_make_new._item_widget.savebuttonbar.bn_revert.disabled
+
+    def test_clicking_revert_button_cancels_edit(self, tmp_path):
+        # Clicking the revert button should cancel the edit
+        saved = SavedSettings(_testing_path=tmp_path)
+        camera = Camera(**TEST_CAMERA_VALUES)
+        saved.add_item(camera)
+
+        choose_or_make_new = ChooseOrMakeNew("camera", _testing_path=tmp_path)
+        # Simulate a click on the edit button...
+        choose_or_make_new._edit_button.click()
+
+        # Simulate a click on the revert button...
+        choose_or_make_new._item_widget.savebuttonbar.bn_revert.click()
+
+        # The camera should not have been changed
+        assert choose_or_make_new._item_widget.value == TEST_CAMERA_VALUES
+
+        # The edit button should be displayed
+        assert choose_or_make_new._edit_button.layout.display != "none"
+
+        # We should not be in editing mode anymore
+        assert not choose_or_make_new._editing
+
+    def test_revert_button_remains_enabled_with_invalid_value_and_actually_reverts(
+        self, tmp_path
+    ):
+        # The revert button should remain enabled if the value is invalid and reverting
+        # should actually revert the value.
+        saved = SavedSettings(_testing_path=tmp_path)
+        camera = Camera(**TEST_CAMERA_VALUES)
+        saved.add_item(camera)
+
+        choose_or_make_new = ChooseOrMakeNew("camera", _testing_path=tmp_path)
+        # Simulate a click on the edit button...
+        choose_or_make_new._edit_button.click()
+
+        # Set an invalid value
+        choose_or_make_new._item_widget.di_widgets["name"].value = ""
+
+        # unsaved_changes should be true
+        assert choose_or_make_new._item_widget.savebuttonbar.unsaved_changes
+
+        # Make sure the change is really there
+        assert choose_or_make_new._item_widget.value != TEST_CAMERA_VALUES
+
+        # The revert button should be enabled
+        assert not choose_or_make_new._item_widget.savebuttonbar.bn_revert.disabled
+
+        # Save should still be disabled
+        assert choose_or_make_new._item_widget.savebuttonbar.bn_save.disabled
+
+        # Click the revert button
+        choose_or_make_new._item_widget.savebuttonbar.bn_revert.click()
+
+        # The camera should not have been changed
+        assert choose_or_make_new._item_widget.value == TEST_CAMERA_VALUES
+
 
 class TestConfirm:
     def test_initial_value(self):