gristlabs_grist-core/sandbox/grist/test_docmodel.py
Alex Hall fa9e6eee88 (core) Create an extra raw data widget when creating a table
Summary: This is the first step towards raw data views, merely adding metadata without any UI. Every 'normal' table now has a widget referenced by `rawViewSectionRef`. It has no parent view/page and cannot actually be viewed for now. The widget is created during the AddTable user action, and the migration creates a widget for existing tables.

Test Plan: Many tests had to be updated, especially tests that listed all view sections and/or fields.

Reviewers: jarek, dsagal

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D3232
2022-02-01 21:19:30 +02:00

256 lines
9.8 KiB
Python

import actions
import logger
import testsamples
import test_engine
from test_engine import Table, Column
log = logger.Logger(__name__, logger.INFO)
class TestDocModel(test_engine.EngineTestCase):
def test_meta_tables(self):
"""
Test changes to records accessed via lookup.
"""
self.load_sample(testsamples.sample_students)
self.assertPartialData("_grist_Tables", ["id", "columns"], [
[1, [1,2,4,5,6]],
[2, [10,12]],
[3, [21]],
])
# Test that adding a column produces a change to 'columns' without emitting an action.
out_actions = self.add_column('Students', 'test', type='Text', isFormula=False)
self.assertPartialData("_grist_Tables", ["id", "columns"], [
[1, [1,2,4,5,6,22]],
[2, [10,12]],
[3, [21]],
])
self.assertPartialOutActions(out_actions, {
"calc": [],
"stored": [
["AddColumn", "Students", "test",
{"formula": "", "isFormula": False, "type": "Text"}
],
["AddRecord", "_grist_Tables_column", 22,
{"colId": "test", "formula": "", "isFormula": False, "label": "test",
"parentId": 1, "parentPos": 6.0, "type": "Text", "widgetOptions": ""}
],
],
"undo": [
["RemoveColumn", "Students", "test"],
["RemoveRecord", "_grist_Tables_column", 22],
]
})
# Undo the AddColumn action. Check that actions are in correct order, and still produce undos.
out_actions = self.apply_user_action(
['ApplyUndoActions', [actions.get_action_repr(a) for a in out_actions.undo]])
self.assertPartialOutActions(out_actions, {
"calc": [],
"stored": [
["RemoveRecord", "_grist_Tables_column", 22],
["RemoveColumn", "Students", "test"],
],
"undo": [
["AddRecord", "_grist_Tables_column", 22, {"colId": "test", "label": "test",
"parentId": 1, "parentPos": 6.0, "type": "Text"}],
["AddColumn", "Students", "test", {"formula": "", "isFormula": False, "type": "Text"}],
]
})
# Test that when we add a table, .column is set correctly.
out_actions = self.apply_user_action(['AddTable', 'Test2', [
{'id': 'A', 'type': 'Text'},
{'id': 'B', 'type': 'Numeric'},
{'id': 'C', 'type': 'Numeric', 'formula': 'len($A)', 'isFormula': True}
]])
self.assertPartialData("_grist_Tables", ["id", "columns"], [
[1, [1,2,4,5,6]],
[2, [10,12]],
[3, [21]],
[4, [22,23,24,25]],
])
self.assertPartialData("_grist_Tables_column", ["id", "colId", "parentId"], [
[1, "firstName", 1],
[2, "lastName", 1],
[4, "schoolName", 1],
[5, "schoolIds", 1],
[6, "schoolCities", 1],
[10, "name", 2],
[12, "address", 2],
[21, "city", 3],
# Newly added columns:
[22, 'manualSort', 4],
[23, 'A', 4],
[24, 'B', 4],
[25, 'C', 4],
])
def test_add_column_position(self):
self.load_sample(testsamples.sample_students)
# Client may send AddColumn actions with fractional positions. Test that it works.
# TODO: this should probably use parentPos in the future and be done via metadata AddRecord.
out_actions = self.add_column('Students', 'test', type='Text', _position=2.75)
self.assertPartialData("_grist_Tables", ["id", "columns"], [
[1, [1,2,22,4,5,6]],
[2, [10,12]],
[3, [21]],
])
out_actions = self.add_column('Students', None, type='Text', _position=6)
self.assertPartialData("_grist_Tables", ["id", "columns"], [
[1, [1,2,22,4,5,6,23]],
[2, [10,12]],
[3, [21]],
])
self.assertPartialData("_grist_Tables_column", ["id", "colId", "parentId"], [
[1, "firstName", 1],
[2, "lastName", 1],
[4, "schoolName", 1],
[5, "schoolIds", 1],
[6, "schoolCities", 1],
[10, "name", 2],
[12, "address", 2],
[21, "city", 3],
[22, "test", 1],
[23, "A", 1],
])
def assertRecordSet(self, record_set, expected_row_ids):
self.assertEqual(list(record_set.id), expected_row_ids)
def test_lookup_recompute(self):
self.load_sample(testsamples.sample_students)
self.apply_user_action(['AddTable', 'Test2', [
{'id': 'A', 'type': 'Text'},
{'id': 'B', 'type': 'Numeric'},
]])
self.apply_user_action(['AddTable', 'Test3', [
{'id': 'A', 'type': 'Text'},
{'id': 'B', 'type': 'Numeric'},
]])
self.apply_user_action(['AddViewSection', 'Section2', 'record', 1, 'Test2'])
self.apply_user_action(['AddViewSection', 'Section3', 'record', 1, 'Test3'])
self.assertPartialData('_grist_Views', ["id"], [
[1],
[2],
])
self.assertPartialData('_grist_Views_section', ["id", "parentId", "tableRef"], [
[1, 1, 4],
[2, 0, 4],
[3, 2, 5],
[4, 0, 5],
[5, 1, 4],
[6, 1, 5],
])
self.assertPartialData('_grist_Views_section_field', ["id", "parentId", "parentPos"], [
[1, 1, 1.0],
[2, 1, 2.0],
[3, 2, 3.0],
[4, 2, 4.0],
[5, 3, 5.0],
[6, 3, 6.0],
[7, 4, 7.0],
[8, 4, 8.0],
[9, 5, 9.0],
[10, 5, 10.0],
[11, 6, 11.0],
[12, 6, 12.0],
])
table = self.engine.docmodel.tables.lookupOne(tableId='Test2')
self.assertRecordSet(table.viewSections, [1, 2, 5])
self.assertRecordSet(list(table.viewSections)[0].fields, [1, 2])
self.assertRecordSet(list(table.viewSections)[2].fields, [9, 10])
view = self.engine.docmodel.views.lookupOne(id=1)
self.assertRecordSet(view.viewSections, [1, 5, 6])
self.engine.docmodel.remove(f for vs in table.viewSections for f in vs.fields)
self.engine.docmodel.remove(table.viewSections)
self.assertRecordSet(view.viewSections, [6])
def test_modifications(self):
# Test the add/remove/update methods of DocModel.
self.load_sample(testsamples.sample_students)
table = self.engine.docmodel.get_table('Students')
records = table.lookupRecords(lastName='Bush')
self.assertEqual([r.id for r in records], [2, 4])
self.assertEqual([r.schoolName for r in records], ["Yale", "Yale"])
self.assertEqual([r.firstName for r in records], ["George W", "George H"])
# Test the update() method.
self.engine.docmodel.update(records, schoolName="Test", firstName=["george w", "george h"])
self.assertEqual([r.schoolName for r in records], ["Test", "Test"])
self.assertEqual([r.firstName for r in records], ["george w", "george h"])
# Test the remove() method.
self.engine.docmodel.remove(records)
records = table.lookupRecords(lastName='Bush')
self.assertEqual(list(records), [])
self.assertTableData("Students", cols="subset", data=[
["id","firstName","lastName", "schoolName" ],
[1, "Barack", "Obama", "Columbia" ],
[3, "Bill", "Clinton", "Columbia" ],
[5, "Ronald", "Reagan", "Eureka" ],
[6, "Gerald", "Ford", "Yale" ]])
# Test the add() method.
self.engine.docmodel.add(table, schoolName="Foo", firstName=["X", "Y"])
self.assertTableData("Students", cols="subset", data=[
["id","firstName","lastName", "schoolName" ],
[1, "Barack", "Obama", "Columbia" ],
[3, "Bill", "Clinton", "Columbia" ],
[5, "Ronald", "Reagan", "Eureka" ],
[6, "Gerald", "Ford", "Yale" ],
[7, "X", "", "Foo" ],
[8, "Y", "", "Foo" ],
])
def test_inserts(self):
# Test the insert() method. We do this on the columns metadata table, so that we can sort by
# a PositionNumber column.
self.load_sample(testsamples.sample_students)
student_columns = self.engine.docmodel.tables.lookupOne(tableId='Students').columns
school_columns = self.engine.docmodel.tables.lookupOne(tableId='Schools').columns
# Should go at the end of the Students table.
cols = self.engine.docmodel.insert(student_columns, None, colId=["a", "b"], type="Text")
# Should go at the start of the Schools table.
self.engine.docmodel.insert_after(school_columns, None, colId="foo", type="Int")
# Should go before the new "a", "b" columns of the Students table.
self.engine.docmodel.insert(student_columns, cols[0].parentPos, colId="bar", type="Date")
# Verify that the right columns were added to the right tables. This doesn't check positions.
self.assertTables([
Table(1, "Students", 0, 0, columns=[
Column(1, "firstName", "Text", False, "", 0),
Column(2, "lastName", "Text", False, "", 0),
Column(4, "schoolName", "Text", False, "", 0),
Column(5, "schoolIds", "Text", True,
"':'.join(str(id) for id in Schools.lookupRecords(name=$schoolName).id)", 0),
Column(6, "schoolCities", "Text", True,
"':'.join(r.address.city for r in Schools.lookupRecords(name=$schoolName))", 0),
Column(22, "a", "Text", False, "", 0),
Column(23, "b", "Text", False, "", 0),
Column(25, "bar", "Date", False, "", 0),
]),
Table(2, "Schools", 0, 0, columns=[
Column(10, "name", "Text", False, "", 0),
Column(12, "address", "Ref:Address",False, "", 0),
Column(24, "foo", "Int", False, "", 0),
]),
Table(3, "Address", 0, 0, columns=[
Column(21, "city", "Text", False, "", 0),
])
])
# Verify that positions are set such that the order is what we asked for.
student_columns = self.engine.docmodel.tables.lookupOne(tableId='Students').columns
self.assertEqual(list(map(int, student_columns)), [1,2,4,5,6,25,22,23])
school_columns = self.engine.docmodel.tables.lookupOne(tableId='Schools').columns
self.assertEqual(list(map(int, school_columns)), [24,10,12])