# pylint: disable=line-too-long import testsamples import test_engine from objtypes import RecordSetStub class TestFormulaUndo(test_engine.EngineTestCase): def setUp(self): super(TestFormulaUndo, self).setUp() def test_change_and_undo(self): self.load_sample(testsamples.sample_students) # Test that regular lookup results behave well on undo. self.apply_user_action(['ModifyColumn', 'Students', 'schoolCities', { "type": "Any", "formula": "Schools.lookupRecords(name=$schoolName)" }]) # Add a formula that produces different results on different invocations. This is # similar to some realistic scenarious (such as returning a time, a rich python object, or a # string like ""), but is convoluted to keep values deterministic. self.apply_user_action(['AddColumn', 'Students', 'counter', { "formula": """ table.my_counter = getattr(table, 'my_counter', 0) + 1 return '#%s %s' % (table.my_counter, $schoolName) """ }]) self.assertTableData("Students", cols="subset", data=[ ["id", "schoolName", "schoolCities", "counter" ], [1, "Columbia", RecordSetStub("Schools", [1, 2]), "#1 Columbia",], [2, "Yale", RecordSetStub("Schools", [3, 4]), "#2 Yale", ], [3, "Columbia", RecordSetStub("Schools", [1, 2]), "#3 Columbia",], [4, "Yale", RecordSetStub("Schools", [3, 4]), "#4 Yale", ], [5, "Eureka", RecordSetStub("Schools", []), "#5 Eureka", ], [6, "Yale", RecordSetStub("Schools", [3, 4]), "#6 Yale", ], ]) # Applying an action produces expected changes to all formula columns, and corresponding undos. out_actions = self.apply_user_action(['UpdateRecord', 'Students', 6, {"schoolName": "Columbia"}]) self.assertOutActions(out_actions, { "stored": [ ["UpdateRecord", "Students", 6, {"schoolName": "Columbia"}], ["UpdateRecord", "Students", 6, {"counter": "#7 Columbia"}], ["UpdateRecord", "Students", 6, {"schoolCities": ["r", "Schools", [1, 2]]}], ["UpdateRecord", "Students", 6, {"schoolIds": "1:2"}], ], "direct": [True, False, False, False], "undo": [ ["UpdateRecord", "Students", 6, {"schoolName": "Yale"}], ["UpdateRecord", "Students", 6, {"counter": "#6 Yale"}], ["UpdateRecord", "Students", 6, {"schoolCities": ["r", "Schools", [3, 4]]}], ["UpdateRecord", "Students", 6, {"schoolIds": "3:4"}], ], }) # Applying the undo actions (which include calculated values) will trigger recalculations, but # they should not produce extraneous actions even when the calculation results differ. out_actions = self.apply_user_action(['ApplyUndoActions', out_actions.get_repr()["undo"]]) # TODO Note the double update when applying undo to non-deterministic formula. It would be # nice to fix, but requires further refactoring (perhaps moving towards processing actions # using summaries). self.assertOutActions(out_actions, { "stored": [ ["UpdateRecord", "Students", 6, {"schoolIds": "3:4"}], ["UpdateRecord", "Students", 6, {"schoolCities": ["r", "Schools", [3, 4]]}], ["UpdateRecord", "Students", 6, {"counter": "#6 Yale"}], ["UpdateRecord", "Students", 6, {"schoolName": "Yale"}], ["UpdateRecord", "Students", 6, {"counter": "#8 Yale"}], ], "direct": [True, True, True, True, False], # undos currently fully direct; formula update is indirect. "undo": [ ["UpdateRecord", "Students", 6, {"schoolIds": "1:2"}], ["UpdateRecord", "Students", 6, {"schoolCities": ["r", "Schools", [1, 2]]}], ["UpdateRecord", "Students", 6, {"counter": "#7 Columbia"}], ["UpdateRecord", "Students", 6, {"schoolName": "Columbia"}], ["UpdateRecord", "Students", 6, {"counter": "#6 Yale"}], ], }) self.assertTableData("Students", cols="subset", data=[ ["id", "schoolName", "schoolCities", "counter" ], [1, "Columbia", RecordSetStub("Schools", [1, 2]), "#1 Columbia"], [2, "Yale", RecordSetStub("Schools", [3, 4]), "#2 Yale", ], [3, "Columbia", RecordSetStub("Schools", [1, 2]), "#3 Columbia"], [4, "Yale", RecordSetStub("Schools", [3, 4]), "#4 Yale", ], [5, "Eureka", RecordSetStub("Schools", []), "#5 Eureka", ], # This counter got updated [6, "Yale", RecordSetStub("Schools", [3, 4]), "#8 Yale", ], ]) def test_save_to_empty_column(self): # When we enter data into an empty column, it gets turned from a formula into a data column. # Check that this operation works. self.load_sample(testsamples.sample_students) self.apply_user_action(['AddColumn', 'Students', 'newCol', {"isFormula": True}]) out_actions = self.apply_user_action(['UpdateRecord', 'Students', 6, {"newCol": "Boo!"}]) self.assertTableData("Students", cols="subset", data=[ ["id", "schoolName", "newCol" ], [1, "Columbia", "" ], [2, "Yale", "" ], [3, "Columbia", "" ], [4, "Yale", "" ], [5, "Eureka", "" ], [6, "Yale", "Boo!" ], ]) # Check that the actions look reasonable. self.assertOutActions(out_actions, { "stored": [ ["ModifyColumn", "Students", "newCol", {"type": "Text"}], ["UpdateRecord", "_grist_Tables_column", 22, {"type": "Text"}], ["ModifyColumn", "Students", "newCol", {"isFormula": False}], ["BulkUpdateRecord", "Students", [1,2,3,4,5,6], {"newCol": ["", "", "", "", "", ""]}], ["UpdateRecord", "_grist_Tables_column", 22, {"isFormula": False}], ["UpdateRecord", "Students", 6, {"newCol": "Boo!"}], ], "direct": [False, False, False, False, False, True], "undo": [ ["ModifyColumn", "Students", "newCol", {"type": "Any"}], ["UpdateRecord", "_grist_Tables_column", 22, {"type": "Any"}], ["BulkUpdateRecord", "Students", [1,2,3,4,5,6], {"newCol": [None, None, None, None, None, None]}], ["ModifyColumn", "Students", "newCol", {"isFormula": True}], ["UpdateRecord", "_grist_Tables_column", 22, {"isFormula": True}], ["UpdateRecord", "Students", 6, {"newCol": ""}], ] }) out_actions = self.apply_user_action(['ApplyUndoActions', out_actions.get_repr()["undo"]]) self.assertTableData("Students", cols="subset", data=[ ["id", "schoolName", "newCol" ], [1, "Columbia", None ], [2, "Yale", None ], [3, "Columbia", None ], [4, "Yale", None ], [5, "Eureka", None ], [6, "Yale", None ], ]) # Check that undo actions are a reversal of the above, without any surprises. self.assertOutActions(out_actions, { "stored": [ ["UpdateRecord", "Students", 6, {"newCol": ""}], ["UpdateRecord", "_grist_Tables_column", 22, {"isFormula": True}], ["ModifyColumn", "Students", "newCol", {"isFormula": True}], ["BulkUpdateRecord", "Students", [1,2,3,4,5,6], {"newCol": [None, None, None, None, None, None]}], ["UpdateRecord", "_grist_Tables_column", 22, {"type": "Any"}], ["ModifyColumn", "Students", "newCol", {"type": "Any"}], ], "direct": [True, True, True, True, True, True], # undos are currently fully direct. "undo": [ ["UpdateRecord", "Students", 6, {"newCol": "Boo!"}], ["UpdateRecord", "_grist_Tables_column", 22, {"isFormula": False}], ["ModifyColumn", "Students", "newCol", {"isFormula": False}], ["BulkUpdateRecord", "Students", [1,2,3,4,5,6], {"newCol": ["", "", "", "", "", ""]}], ["UpdateRecord", "_grist_Tables_column", 22, {"type": "Text"}], ["ModifyColumn", "Students", "newCol", {"type": "Text"}], ] })