You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/sandbox/grist/test_formula_undo.py

164 lines
7.8 KiB

# 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 "<Foo at 0xfe65d350>"), 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"}],
]
})