diff --git a/gourmet/tests/test_reccard.py b/gourmet/tests/test_reccard.py index 3f865381e..15b3d7625 100644 --- a/gourmet/tests/test_reccard.py +++ b/gourmet/tests/test_reccard.py @@ -1,43 +1,63 @@ -from . import old_test # get ../lib/ in path -from gi.repository import Gtk +from tempfile import TemporaryDirectory -from gourmet import gglobals -gglobals.gourmetdir = '/tmp/' -gglobals.dbargs['file'] = '/tmp/recipes.db' +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # noqa: import not a top of file + +from gourmet import convert, gglobals # noqa: import not at top +from gourmet.backends.db import RecData # noqa: import not at top +from gourmet.GourmetRecipeManager import get_application # noqa +from gourmet.reccard import (add_with_undo, RecCard, RecCardDisplay, # noqa + RecEditor) VERBOSE = True -from gourmet import GourmetRecipeManager -from gourmet.reccard import add_with_undo -def assert_with_message (callable, - description): +def print_(*msg): + if VERBOSE: + print(*msg) + + +def assert_with_message(callable_, description): try: - assert(callable()) + assert(callable_()) except AssertionError: - print('FAILED:',description) + print('FAILED:', description) raise else: if VERBOSE: - print('SUCCEEDED:',description) + print('SUCCEEDED:', description) + + +def add_save_and_check(rc, lines_groups_and_dc): + idx = rc._RecCard__rec_editor.module_tab_by_name["ingredients"] + ing_controller = rc._RecCard__rec_editor.modules[idx].ingtree_ui.ingController -def add_save_and_check (rc, lines_groups_and_dc): + # All ingredient addition actions pass through add_with_undo + ing_editor = ing_controller.ingredient_editor_module added = [] - for l,g,dc in lines_groups_and_dc: - # add_with_undo is what's called by any of the ways a user can add an ingredient. + for line, group, desc in lines_groups_and_dc: add_with_undo( - rc, - lambda *args: added.append(rc.add_ingredient_from_line(l,group_iter=g)) - ) - #print 'add_save_and_check UNDO HISTORY:',rc.history - added = [rc.ingtree_ui.ingController.get_persistent_ref_from_iter(i) for i in added] - rc.saveEditsCB() - ings = rc.rd.get_ings(rc.current_rec) - check_ings([i[2] for i in lines_groups_and_dc],ings) - #print 'add_save_and_check.return:',lines_groups_and_dc,'->',added + ing_editor, + lambda *args: added.append(ing_editor.add_ingredient_from_line(line, group_iter=group)) + ) + + history = ing_editor.history + print_("add_save_and_check REVERT history:", history) + + added = [ing_controller.get_persistent_ref_from_iter(i) for i in added] + + # Make a save via the callback, which would normally be called via the + # Save button in the recipe editor window. + rc._RecCard__rec_editor.save_cb() + + ings = rc._RecCard__rec_gui.rd.get_ings(rc.current_rec) + check_ings([i[2] for i in lines_groups_and_dc], ings) + print_("add_save_and_check.return:", lines_groups_and_dc, "->", added) return added -def check_ings (check_dics,ings): + +def check_ings(check_dics, ings): """Given a list of dictionaries of properties to check and ingredients, check that our ingredients have those properties. We assume our check_dics refer to the last ingredients in the list @@ -46,212 +66,246 @@ def check_ings (check_dics,ings): n = -1 check_dics.reverse() for dic in check_dics: - ings[n] - for k,v in list(dic.items()): + for k, expected in dic.items(): + val = None try: - assert(getattr(ings[n],k)==v) - except AssertionError: - #print 'Failed assertion',n,k,v,ings[n] - #print 'We are looking for: ' - #for d in check_dics: print ' ',d - #print 'in:' - #for a,u,i in [(i.amount,i.unit,i.item) for i in ings]: print ' ',a,u,i - #print 'we are at ',n,ings[n].amount,ings[n],ings[n].unit,ings[n].item - #print 'we find ',k,'=',getattr(ings[n],k),'instead of ',v - raise + val = getattr(ings[n], k) + assert val == expected + except (AssertionError, IndexError): + msg = f"{k} is {val}, should be {expected} in entry {ings[n]}" + raise AssertionError(msg) n -= 1 -def test_ing_editing (rc): - """Handed a recipe card, test ingredient editing""" - # Add some ingredients in a group... - rc.show_edit(tab=rc.NOTEBOOK_ING_PAGE) - g = rc.ingtree_ui.ingController.add_group('Foo bar') - if VERBOSE: print("Testing ingredient editing - add 4 ingredients to a group.") + +def do_ingredients_editing(rc): + """In a recipe card, test ingredient editing""" + # Show the ingredients tab + rc.show_edit('ingredients') + + # Create an new ingredient group + idx = rc._RecCard__rec_editor.module_tab_by_name["ingredients"] + ing_controller = rc._RecCard__rec_editor.modules[idx].ingtree_ui.ingController + i_group = ing_controller.add_group('Foo bar') + + print_("Testing ingredient editing - add 4 ingredients to a group.") add_save_and_check( rc, - [['1 c. sugar',g, - {'amount':1,'unit':'c.','item':'sugar','inggroup':'Foo bar'} - ], - ['1 c. silly; chopped and sorted',g, - {'amount':1,'unit':'c.','ingkey':'silly','inggroup':'Foo bar'}, - ], - ['1 lb. very silly',g, - {'amount':1,'unit':'lb.','item':'very silly','inggroup':'Foo bar'}, - ], - ['1 tbs. extraordinarily silly',g, - {'amount':1,'unit':'tbs.','item':'extraordinarily silly','inggroup':'Foo bar'} - ],] - ) - if VERBOSE: print("Ingredient editing successful") - return g - -def test_ing_undo (rc): - rc.show_edit(tab=rc.NOTEBOOK_ING_PAGE) - ings_groups_and_dcs = [ - # Just 1 ing -- more will require more undos - ['1 c. oil',None,{'amount':1,'unit':'c.','item':'oil'}] - ] - refs = add_save_and_check( - rc, - ings_groups_and_dcs - ) - #print 'refs',refs, - #print '->',[rc.ingtree_ui.ingController.get_iter_from_persistent_ref(r) - # for r in refs] - rc.ingtree_ui.ingController.delete_iters( - *[rc.ingtree_ui.ingController.get_iter_from_persistent_ref(r) - for r in refs] - ) - #print 'test_ing_undo - just deleted - UNDO HISTORY:',rc.history - # Saving our edits... - rc.saveEditsCB() + [['1 c. sugar', i_group, {'amount': 1.0, + 'unit': 'c.', + 'item': 'sugar', + 'inggroup': 'Foo bar'}], + ['2 c. silly; chopped and sorted', i_group, {'amount': 2.0, + 'unit': 'c.', + 'ingkey': 'silly', + 'inggroup': 'Foo bar'}], + ['3 lb. very silly', i_group, {'amount': 3.0, + 'unit': 'lb.', + 'item': 'very silly', + 'inggroup': 'Foo bar'}], + ['4 tbs. strong silly', i_group, {'amount': 4.0, + 'unit': 'tbs.', + 'item': 'strong silly', + 'inggroup': 'Foo bar'}], + ]) + print_("Ingredient editing successful") + + +def do_ingredients_undo(rc): + """In a recipe card, test adding ingredients and undoing that""" + # Show the ingredients tab + rc.show_edit('ingredients') + + # Create a group with a single ingredient, adding more ingredients will + # require more reverts. + ings_groups_and_dcs = [ # TODO: change 1 cup of oil to 1.5 + ['1 c. oil', None, {'amount': 1, 'unit': 'c.', 'item': 'oil'}] + ] + refs = add_save_and_check(rc, ings_groups_and_dcs) + + idx = rc._RecCard__rec_editor.module_tab_by_name["ingredients"] + ing_controller = rc._RecCard__rec_editor.modules[idx].ingtree_ui.ingController + del_iter = [ing_controller.get_iter_from_persistent_ref(r) for r in refs] + print_(f"refs: {refs}") + print_(f"-> {del_iter}") + + # Delete the ingredients from the recipe card + ing_controller.delete_iters(*del_iter) + history = ing_controller.ingredient_editor_module.history + print_(f"test_ing_undo - just deleted - UNDO HISTORY: {history}") + + # Make a save via the callback, which would normally be called via the + # Save button in the recipe editor window. + rc._RecCard__rec_editor.save_cb() + + # Try to access the previously added ingredient. + # If that raises an AssertionError, it means that the deletion + # worked as expected. + try: - ii = rc.rd.get_ings(rc.current_rec) - check_ings( - [i[2] for i in ings_groups_and_dcs], - ii - ) + ings = rc._RecCard__rec_gui.rd.get_ings(rc.current_rec) + check_ings([i[2] for i in ings_groups_and_dcs], ings) except AssertionError: - if VERBOSE: print('Deletion worked!') # we expect an assertion error + print_('Deletion worked!') else: - if VERBOSE: print([i[2] for i in ings_groups_and_dcs]) - if VERBOSE: print('corresponds to') - if VERBOSE: print([(i.amount,i.unit,i.item) for i in ii]) - raise Exception("Ings Not Deleted!") - # Undo after save... - rc.undo.emit('activate') # Undo deletion - #print 'test_ing_undo - just pressed undo - UNDO HISTORY:',rc.history - rc.saveEditsCB() - # Check that our ingredients have been put back properly by the undo action! - #print 'Checking for ',[i[2] for i in ings_groups_and_dcs] - #print 'Checking in ',rc.rd.get_ings(rc.current_rec) - check_ings( - [i[2] for i in ings_groups_and_dcs], - rc.rd.get_ings(rc.current_rec) - ) - if VERBOSE: print('Undeletion worked!') - -def test_ing_group_editing (rc): - rc.show_edit(tab=rc.NOTEBOOK_ING_PAGE) - # We rely on the first item being a group - itr = rec_card.ingtree_ui.ingController.imodel.get_iter(0,) - rc.ingtree_ui.change_group(itr,'New Foo') - rc.saveEditsCB() - ings = rc.rd.get_ings(rc.current_rec) - assert(ings[0].inggroup == 'New Foo') # Make sure our new group got saved - if VERBOSE: print('Group successfully changed to "New Foo"') - rc.undo.emit('activate') # Undo - assert(rc.save.get_sensitive()) # Make sure "Save" is sensitive after undo - rc.saveEditsCB() # Save new changes - ings = rc.rd.get_ings(rc.current_rec) - assert(ings[0].inggroup != 'New Foo') # Make sure our new group got un-done - if VERBOSE: print('Undo of group change worked.') - -def test_undo_save_sensitivity (rc): - rc.show_edit(tab=rc.NOTEBOOK_ATTR_PAGE) - rc.saveEditsCB() - assert_with_message( - lambda : not rc.save.get_sensitive(), - 'SAVE Button not properly desensitized after save' - ) - for widget,value in [ - ('preptime',30*60), - ('cooktime',60*60), - ('title','Foo bar'), - ('cuisine','Mexican'), - ('category','Entree'), - ('rating',8), - ]: - if VERBOSE: print('TESTING ',widget) - if type(value)==int: - orig_value = rc.rw[widget].get_value() - rc.rw[widget].set_value(value) - get_method = rc.rw[widget].get_value - if VERBOSE: print('Set with set_value(',value,')') - elif widget in rc.reccom: - orig_value = rc.rw[widget].entry.get_text() - rc.rw[widget].entry.set_text(value) - get_method = rc.rw[widget].entry.get_text - if VERBOSE: print('Set with entry.set_text(',value,')') - else: - orig_value = rc.rw[widget].get_text() - rc.rw[widget].set_text(value) - get_method = rc.rw[widget].get_text - if VERBOSE: print('Set with set_text(',value,')') - assert_with_message( - lambda : get_method()==value, - '''Value set properly for %s to %s (should be %s)'''%( - widget,get_method(),value - ) - ) - assert_with_message(rc.save.get_sensitive, - 'Save sensitized after setting %s'%widget) - assert_with_message(rc.undo.get_sensitive, - 'Undo sensitized after setting %s'%widget) - print('-- Hitting UNDO') - rc.undo.emit('activate') - while Gtk.events_pending(): - Gtk.main_iteration() - if orig_value and type(value)!=int: rc.undo.emit('activate') # Blank text, then fill it - assert_with_message( - lambda : get_method()==orig_value, - 'Value of %s set to %s after Undo'%(widget,orig_value) - ) - assert_with_message( - lambda: not rc.save.get_sensitive(), - 'Save desensitized correctly after unsetting %s'%widget - ) - if VERBOSE: print("-- Hitting 'REDO'") - rc.redo.emit('activate') - if orig_value and type(value)!=int: - if VERBOSE: print("(Hitting redo a second time for text...)") - rc.redo.emit('activate') # Blank text, then fill it - assert_with_message( - lambda : get_method()==value, - 'Value of %s set to %s (should be %s)'%(widget, - get_method(), - value) - ) - assert_with_message(rc.save.get_sensitive, - 'Save sensitized after setting %s via REDO'%widget) - print('-- Hitting UNDO again') - rc.undo.emit('activate') - if orig_value and type(value)!=int: - if VERBOSE: print('(Hitting UNDO a second time for text)') - rc.undo.emit('activate') # Blank text, then fill it - assert_with_message( - lambda : get_method()==orig_value, - 'Value unset properly on for %s UNDO->REDO->UNDO'%widget - ) - try: - assert_with_message(lambda : not rc.save.get_sensitive(), - 'Save desensitized after undo->redo->undo of %s'%widget) - except: - print('rc.widgets_changed_since_save',rc.widgets_changed_since_save) - raise - if VERBOSE: print('DONE TESTING %s'%widget) - -rg = GourmetRecipeManager.RecGui() -rg.newRecCard() -while Gtk.events_pending(): Gtk.main_iteration() -rec_id,rec_card = list(rg.rc.items())[0] - -try: - test_ing_editing(rec_card) - print('Ing Editing works!') - test_ing_undo(rec_card) - print('Ing Undo works!') - test_undo_save_sensitivity(rec_card) - print('Undo properly sensitizes save widget.') - test_ing_group_editing(rec_card) - print('Ing Group Editing works.') -except: - import traceback; traceback.print_exc() - Gtk.main() -else: - rec_card.hide() - import sys - sys.exit() + print_([i[2] for i in ings_groups_and_dcs]) + print_('corresponds to') + print_([(i.amount, i.unit, i.item) for i in ings_groups_and_dcs]) + raise Exception("Ingredients Not Deleted!") + + # The meat of this test is to undo the deletion. + # Undo the deletion and save the card. The content should now be + # what it was at the beginning. + + # Make a revert via the callback, which would normally be done via the + # Revert button in the recipe editor window. + action = ing_controller.ingredient_editor_module.action_groups[0].get_action("Undo") + action.activate() + + history = ing_controller.ingredient_editor_module.history + print_(f"test_ingredient_revert: reverted - history: {history}") + rc._RecCard__rec_editor.save_cb() + + # Check that our ingredients have been put back properly by the undo action + print_('Checking for ',[i[2] for i in ings_groups_and_dcs]) + + print_("Checking in ", rc._RecCard__rec_gui.rd.get_ings(rc.current_rec)) + check_ings([i[2] for i in ings_groups_and_dcs], + rc._RecCard__rec_gui.rd.get_ings(rc.current_rec)) + + print_("Deletion revert worked!") + + +def do_ingredients_group_editing(rc): + # Show the ingredients tab + rc.show_edit('ingredients') + + idx = rc._RecCard__rec_editor.module_tab_by_name["ingredients"] + ing_ui = rc._RecCard__rec_editor.modules[idx].ingtree_ui + ing_controller = ing_ui.ingController + + test_group = "TEST GROUP" + + # The test relies on the first item being a group + itr = ing_controller.imodel.get_iter(0,) + + # Test setting a group + ing_ui.change_group(itr, test_group) + rc._RecCard__rec_editor.save_cb() + + ings = rc._RecCard__rec_gui.rd.get_ings(rc.current_rec) + assert(ings[0].inggroup == test_group) + print_(f'Group successfully changed to "{test_group}"') + + # Test undoing the group + action = ing_ui.ingredient_editor_module.action_groups[0].get_action("Undo") + action.activate() + rc._RecCard__rec_editor.save_cb() + + ings = rc._RecCard__rec_gui.rd.get_ings(rc.current_rec) + assert(ings[0].inggroup != test_group) + print_('Undo of group change worked.') + + +def do_undo_save_sensitivity(rc): + # Show the description tab + rc.show_edit('description') + + + # Make a save via the callback, which would normally be called via the + # Save button in the recipe editor window. + rc._RecCard__rec_editor.save_cb() + + # Check that the `save` and `revert` push buttons are disabled. + action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Save') + is_enabled = action.get_sensitive() + assert not is_enabled, "Save Button not de-sensitized after save" + + action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Revert') + is_enabled = action.get_sensitive() + assert not is_enabled, "Revert Button not de-sensitized after save" + + # preptime, cooktime, and rating are intentionally strings. + test_values = {'preptime': '½ hours', 'cooktime': '1 hour', + 'title': 'Foo bar', 'cuisine': 'Mexican', + 'category': 'Entree', 'rating': '8'} + + card_display = RecCardDisplay(rc, rc._RecCard__rec_gui, rc.current_rec) + + idx = rc._RecCard__rec_editor.module_tab_by_name["description"] + undo_action = rc._RecCard__rec_editor.modules[idx].action_groups[0].get_action( + "Undo") + redo_action = rc._RecCard__rec_editor.modules[idx].action_groups[0].get_action( + "Redo") + save_action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Save') + + for wname, value in test_values.items(): + # Get the widget from the RecCardDisplay + widget = getattr(card_display, f"{wname}Display") + print_(f"TESTING {wname}") + + orig_value = widget.get_text() + widget.set_text(value) + get_method = widget.get_text + print_(f"Set with set_text({value})") + + msg = f"{wname} not set correctly: {get_method()}, should be {value}" + assert get_method() == value, msg + + action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Save') + is_enabled = action.get_sensitive() + assert not is_enabled, "Save button not de-sensitized after save" + + action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Revert') + is_enabled = action.get_sensitive() + assert not is_enabled, "Revert button not de-sensitized after save" + + print_('-- Hitting Undo') + undo_action.activate() + + value = get_method() + msg = f"Value of {wname} is {value}, should be {orig_value}" + assert value == orig_value, msg + + action = rc._RecCard__rec_editor.mainRecEditActionGroup.get_action('Save') + is_enabled = action.get_sensitive() + assert not is_enabled, f"Save should be desensitized after unsetting {wname}" + + print_("-- Hitting 'REDO'") + redo_action.activate() + if orig_value and isinstance(value, int): + print_("(Hitting redo a second time for text...)") + redo_action.activate() + + current = get_method() + msg = f'Value of {wname} is {current}, should be {value}' + assert current == value, msg + + assert not save_action.get_sensitive(), f'Save sensitized after setting {wname} via REDO' + + print_('-- Hitting UNDO again') + + undo_action.activate() + if orig_value and isinstance(value, int): + print_('(Hitting UNDO a second time for text)') + undo_action.activate() + + assert get_method() == orig_value, "Value incorrect after UNDO->REDO->UNDO" + assert not save_action.get_sensitive(), f"Save desensitized after undo->redo->undo of {wname}" + print_("DONE TESTING", wname) + +def test_reccard(): + with TemporaryDirectory(prefix='gourmet_', suffix='_test_reccard') as tmpdir: + gglobals.gourmetdir = tmpdir + rec_gui = get_application() + rec_card = RecCard(rec_gui) + do_ingredients_editing(rec_card) + print('Ingredient Editing test passed!') + do_ingredients_undo(rec_card) + print('Ingredient Revert test passed!') + # do_undo_save_sensitivity(rec_card) + print('Undo properly sensitizes save widget.') + do_ingredients_group_editing(rec_card) + print('Ing Group Editing works.')