mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
b82eec714a
Summary: this moves sandbox/grist to core, and adds a requirements.txt file for reconstructing the content of sandbox/thirdparty. Test Plan: existing tests pass. Tested core functionality manually. Tested docker build manually. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2563
454 lines
19 KiB
Python
454 lines
19 KiB
Python
import logger
|
|
|
|
import testutil
|
|
import test_engine
|
|
from test_engine import Table, Column, View, Section, Field
|
|
|
|
log = logger.Logger(__name__, logger.INFO)
|
|
|
|
class TestColumnActions(test_engine.EngineTestCase):
|
|
sample = testutil.parse_test_sample({
|
|
"SCHEMA": [
|
|
[1, "Address", [
|
|
[21, "city", "Text", False, "", "", ""],
|
|
]]
|
|
],
|
|
"DATA": {
|
|
"Address": [
|
|
["id", "city" ],
|
|
[11, "New York" ],
|
|
[12, "Colombia" ],
|
|
[13, "New Haven" ],
|
|
[14, "West Haven" ]],
|
|
}
|
|
})
|
|
|
|
@test_engine.test_undo
|
|
def test_column_updates(self):
|
|
# Verify various automatic adjustments for column updates
|
|
# (1) that label gets synced to colId unless untieColIdFromLabel is set.
|
|
# (2) that unsetting untieColId syncs the label to colId.
|
|
# (3) that a complex BulkUpdateRecord for _grist_Tables_column is processed correctly.
|
|
self.load_sample(self.sample)
|
|
|
|
self.apply_user_action(["AddColumn", "Address", "foo", {"type": "Numeric"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "city", "", "Text", False ],
|
|
[ 22, 1, "foo", "foo", "Numeric", False ],
|
|
])
|
|
|
|
# Check that label is synced to colId, via either ModifyColumn or UpdateRecord useraction.
|
|
self.apply_user_action(["ModifyColumn", "Address", "city", {"label": "Hello"}])
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "World"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Hello", "Hello", "Text", False ],
|
|
[ 22, 1, "World", "World", "Numeric", False ],
|
|
])
|
|
|
|
# But check that a rename or an update that includes colId is not affected by label.
|
|
self.apply_user_action(["RenameColumn", "Address", "Hello", "Hola"])
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22,
|
|
{"label": "Foo", "colId": "Bar"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Hola", "Hello", "Text", False ],
|
|
[ 22, 1, "Bar", "Foo", "Numeric", False ],
|
|
])
|
|
|
|
# Check that setting untieColIdFromLabel doesn't change anything immediately.
|
|
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
|
|
{"untieColIdFromLabel": [True, True]}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Hola", "Hello", "Text", True ],
|
|
[ 22, 1, "Bar", "Foo", "Numeric", True ],
|
|
])
|
|
|
|
# Check that ModifyColumn and UpdateRecord useractions no longer copy label to colId.
|
|
self.apply_user_action(["ModifyColumn", "Address", "Hola", {"label": "Hello"}])
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "World"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Hola", "Hello", "Text", True ],
|
|
[ 22, 1, "Bar", "World", "Numeric", True ],
|
|
])
|
|
|
|
# Check that unsetting untieColIdFromLabel syncs label, whether label is provided or not.
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21,
|
|
{"untieColIdFromLabel": False, "label": "Alice"}])
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22,
|
|
{"untieColIdFromLabel": False}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Alice", "Alice", "Text", False ],
|
|
[ 22, 1, "World", "World", "Numeric", False ],
|
|
])
|
|
|
|
# Check that column names still get sanitized and disambiguated.
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21, {"label": "Alice M"}])
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "Alice-M"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Alice_M", "Alice M", "Text", False ],
|
|
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
|
|
])
|
|
|
|
# Check that a column rename doesn't avoid its own name.
|
|
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21, {"label": "Alice*M"}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Alice_M", "Alice*M", "Text", False ],
|
|
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
|
|
])
|
|
|
|
# Untie colIds and tie them again, and make sure it doesn't cause unneeded renames.
|
|
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
|
|
{ "untieColIdFromLabel": [True, True] }])
|
|
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
|
|
{ "untieColIdFromLabel": [False, False] }])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Alice_M", "Alice*M", "Text", False ],
|
|
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
|
|
])
|
|
|
|
# Check that disambiguating also works correctly for bulk updates.
|
|
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
|
|
{"label": ["Bob Z", "Bob-Z"]}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Bob_Z", "Bob Z", "Text", False ],
|
|
[ 22, 1, "Bob_Z2", "Bob-Z", "Numeric", False ],
|
|
])
|
|
|
|
# Same for changing colIds directly.
|
|
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
|
|
{"colId": ["Carol X", "Carol-X"]}])
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Carol_X", "Bob Z", "Text", False ],
|
|
[ 22, 1, "Carol_X2", "Bob-Z", "Numeric", False ],
|
|
])
|
|
|
|
# Check confusing bulk updates with different keys changing for different records.
|
|
out_actions = self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22], {
|
|
"label": ["Bob Z", "Bob-Z"], # Unchanged from before.
|
|
"untieColIdFromLabel": [True, False]
|
|
}])
|
|
self.assertPartialOutActions(out_actions, { "stored": [
|
|
["RenameColumn", "Address", "Carol_X2", "Bob_Z"],
|
|
["BulkUpdateRecord", "_grist_Tables_column", [21, 22],
|
|
{"colId": ["Carol_X", "Bob_Z"], # Note that only one column is changing.
|
|
"untieColIdFromLabel": [True, False]
|
|
# No update to label, they get trimmed as unchanged.
|
|
}
|
|
],
|
|
]})
|
|
self.assertTableData("_grist_Tables_column", cols="subset", data=[
|
|
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
|
|
[ 21, 1, "Carol_X", "Bob Z", "Text", True ],
|
|
[ 22, 1, "Bob_Z", "Bob-Z", "Numeric", False ],
|
|
])
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
address_table_data = [
|
|
["id", "city", "state", "amount" ],
|
|
[ 21, "New York", "NY" , 1. ],
|
|
[ 22, "Albany", "NY" , 2. ],
|
|
[ 23, "Seattle", "WA" , 3. ],
|
|
[ 24, "Chicago", "IL" , 4. ],
|
|
[ 25, "Bedford", "MA" , 5. ],
|
|
[ 26, "New York", "NY" , 6. ],
|
|
[ 27, "Buffalo", "NY" , 7. ],
|
|
[ 28, "Bedford", "NY" , 8. ],
|
|
[ 29, "Boston", "MA" , 9. ],
|
|
[ 30, "Yonkers", "NY" , 10. ],
|
|
[ 31, "New York", "NY" , 11. ],
|
|
]
|
|
|
|
sample2 = testutil.parse_test_sample({
|
|
"SCHEMA": [
|
|
[1, "Address", [
|
|
[11, "city", "Text", False, "", "", ""],
|
|
[12, "state", "Text", False, "", "", ""],
|
|
[13, "amount", "Numeric", False, "", "", ""],
|
|
]]
|
|
],
|
|
"DATA": {
|
|
"Address": address_table_data
|
|
}
|
|
})
|
|
|
|
def init_sample_data(self):
|
|
# Add a new view with a section, and a new table to that view, and a summary table.
|
|
self.load_sample(self.sample2)
|
|
self.apply_user_action(["CreateViewSection", 1, 0, "record", None])
|
|
self.apply_user_action(["CreateViewSection", 0, 1, "record", None])
|
|
self.apply_user_action(["CreateViewSection", 1, 1, "record", [12]])
|
|
self.apply_user_action(["BulkAddRecord", "Table1", [None]*3, {
|
|
"A": ["a", "b", "c"],
|
|
"B": ["d", "e", "f"],
|
|
"C": ["", "", ""]
|
|
}])
|
|
|
|
# Verify the new structure of tables and views.
|
|
self.assertTables([
|
|
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
|
|
Column(11, "city", "Text", False, "", 0),
|
|
Column(12, "state", "Text", False, "", 0),
|
|
Column(13, "amount", "Numeric", False, "", 0),
|
|
]),
|
|
Table(2, "Table1", 2, 0, columns=[
|
|
Column(14, "manualSort", "ManualSortPos", False, "", 0),
|
|
Column(15, "A", "Text", False, "", 0),
|
|
Column(16, "B", "Text", False, "", 0),
|
|
Column(17, "C", "Text", False, "", 0),
|
|
]),
|
|
Table(3, "GristSummary_7_Address", 0, 1, columns=[
|
|
Column(18, "state", "Text", False, "", summarySourceCol=12),
|
|
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
|
|
formula="table.getSummarySourceGroup(rec)"),
|
|
Column(20, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
|
|
Column(21, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
|
|
]),
|
|
])
|
|
self.assertViews([
|
|
View(1, sections=[
|
|
Section(1, parentKey="record", tableRef=1, fields=[
|
|
Field(1, colRef=11),
|
|
Field(2, colRef=12),
|
|
Field(3, colRef=13),
|
|
]),
|
|
Section(3, parentKey="record", tableRef=2, fields=[
|
|
Field(7, colRef=15),
|
|
Field(8, colRef=16),
|
|
Field(9, colRef=17),
|
|
]),
|
|
Section(4, parentKey="record", tableRef=3, fields=[
|
|
Field(10, colRef=18),
|
|
Field(11, colRef=20),
|
|
Field(12, colRef=21),
|
|
]),
|
|
]),
|
|
View(2, sections=[
|
|
Section(2, parentKey="record", tableRef=2, fields=[
|
|
Field(4, colRef=15),
|
|
Field(5, colRef=16),
|
|
Field(6, colRef=17),
|
|
]),
|
|
])
|
|
])
|
|
self.assertTableData('Address', data=self.address_table_data)
|
|
self.assertTableData('Table1', data=[
|
|
["id", "A", "B", "C", "manualSort"],
|
|
[ 1, "a", "d", "", 1.0],
|
|
[ 2, "b", "e", "", 2.0],
|
|
[ 3, "c", "f", "", 3.0],
|
|
])
|
|
self.assertTableData("GristSummary_7_Address", cols="subset", data=[
|
|
[ "id", "state", "count", "amount" ],
|
|
[ 1, "NY", 7, 1.+2+6+7+8+10+11 ],
|
|
[ 2, "WA", 1, 3. ],
|
|
[ 3, "IL", 1, 4. ],
|
|
[ 4, "MA", 2, 5.+9 ],
|
|
])
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
@test_engine.test_undo
|
|
def test_column_removals(self):
|
|
# Verify removal of fields when columns are removed.
|
|
|
|
self.init_sample_data()
|
|
|
|
# Add link{Src,Target}ColRef to ViewSections. These aren't actually meaningful links, but they
|
|
# should still get cleared automatically when columns get removed.
|
|
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 2, {
|
|
'linkSrcSectionRef': 1,
|
|
'linkSrcColRef': 11,
|
|
'linkTargetColRef': 16
|
|
}])
|
|
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
|
|
["id", "linkSrcSectionRef", "linkSrcColRef", "linkTargetColRef"],
|
|
[2, 1, 11, 16 ],
|
|
])
|
|
|
|
# Test that we can remove multiple columns using BulkUpdateRecord.
|
|
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [11, 16]])
|
|
|
|
# Test that link{Src,Target}colRef back-references get unset.
|
|
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
|
|
["id", "linkSrcSectionRef", "linkSrcColRef", "linkTargetColRef"],
|
|
[2, 1, 0, 0 ],
|
|
])
|
|
|
|
# Test that columns and section fields got removed.
|
|
self.assertTables([
|
|
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
|
|
Column(12, "state", "Text", False, "", 0),
|
|
Column(13, "amount", "Numeric", False, "", 0),
|
|
]),
|
|
Table(2, "Table1", 2, 0, columns=[
|
|
Column(14, "manualSort", "ManualSortPos", False, "", 0),
|
|
Column(15, "A", "Text", False, "", 0),
|
|
Column(17, "C", "Text", False, "", 0),
|
|
]),
|
|
Table(3, "GristSummary_7_Address", 0, 1, columns=[
|
|
Column(18, "state", "Text", False, "", summarySourceCol=12),
|
|
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
|
|
formula="table.getSummarySourceGroup(rec)"),
|
|
Column(20, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
|
|
Column(21, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
|
|
]),
|
|
])
|
|
self.assertViews([
|
|
View(1, sections=[
|
|
Section(1, parentKey="record", tableRef=1, fields=[
|
|
Field(2, colRef=12),
|
|
Field(3, colRef=13),
|
|
]),
|
|
Section(3, parentKey="record", tableRef=2, fields=[
|
|
Field(7, colRef=15),
|
|
Field(9, colRef=17),
|
|
]),
|
|
Section(4, parentKey="record", tableRef=3, fields=[
|
|
Field(10, colRef=18),
|
|
Field(11, colRef=20),
|
|
Field(12, colRef=21),
|
|
]),
|
|
]),
|
|
View(2, sections=[
|
|
Section(2, parentKey="record", tableRef=2, fields=[
|
|
Field(4, colRef=15),
|
|
Field(6, colRef=17),
|
|
]),
|
|
])
|
|
])
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
@test_engine.test_undo
|
|
def test_summary_column_removals(self):
|
|
# Verify that when we remove a column used for summary-table group-by, it updates summary
|
|
# tables appropriately.
|
|
|
|
self.init_sample_data()
|
|
|
|
# Test that we cannot remove group-by columns from summary tables directly.
|
|
with self.assertRaisesRegexp(ValueError, "cannot remove .* group-by"):
|
|
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [20,18]])
|
|
|
|
# Test that group-by columns in summary tables get removed.
|
|
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [11,12,16]])
|
|
|
|
# Verify the new structure of tables and views.
|
|
self.assertTables([
|
|
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
|
|
Column(13, "amount", "Numeric", False, "", 0),
|
|
]),
|
|
Table(2, "Table1", 2, 0, columns=[
|
|
Column(14, "manualSort", "ManualSortPos", False, "", 0),
|
|
Column(15, "A", "Text", False, "", 0),
|
|
Column(17, "C", "Text", False, "", 0),
|
|
]),
|
|
# Note that the summary table here switches to a new one, without the deleted group-by.
|
|
Table(4, "GristSummary_7_Address2", 0, 1, columns=[
|
|
Column(22, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
|
|
Column(23, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
|
|
Column(24, "group", "RefList:Address", True, summarySourceCol=0,
|
|
formula="table.getSummarySourceGroup(rec)"),
|
|
]),
|
|
])
|
|
self.assertViews([
|
|
View(1, sections=[
|
|
Section(1, parentKey="record", tableRef=1, fields=[
|
|
Field(3, colRef=13),
|
|
]),
|
|
Section(3, parentKey="record", tableRef=2, fields=[
|
|
Field(7, colRef=15),
|
|
Field(9, colRef=17),
|
|
]),
|
|
Section(4, parentKey="record", tableRef=4, fields=[
|
|
Field(11, colRef=22),
|
|
Field(12, colRef=23),
|
|
]),
|
|
]),
|
|
View(2, sections=[
|
|
Section(2, parentKey="record", tableRef=2, fields=[
|
|
Field(4, colRef=15),
|
|
Field(6, colRef=17),
|
|
]),
|
|
])
|
|
])
|
|
|
|
# Verify the data itself.
|
|
self.assertTableData('Address', data=[
|
|
["id", "amount" ],
|
|
[ 21, 1. ],
|
|
[ 22, 2. ],
|
|
[ 23, 3. ],
|
|
[ 24, 4. ],
|
|
[ 25, 5. ],
|
|
[ 26, 6. ],
|
|
[ 27, 7. ],
|
|
[ 28, 8. ],
|
|
[ 29, 9. ],
|
|
[ 30, 10. ],
|
|
[ 31, 11. ],
|
|
])
|
|
self.assertTableData('Table1', data=[
|
|
["id", "A", "C", "manualSort"],
|
|
[ 1, "a", "", 1.0],
|
|
[ 2, "b", "", 2.0],
|
|
[ 3, "c", "", 3.0],
|
|
])
|
|
self.assertTableData("GristSummary_7_Address2", cols="subset", data=[
|
|
[ "id", "count", "amount" ],
|
|
[ 1, 7+1+1+2, 1.+2+6+7+8+10+11+3+4+5+9 ],
|
|
])
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
@test_engine.test_undo
|
|
def test_column_sort_removals(self):
|
|
# Verify removal of sort spec entries when columns are removed.
|
|
|
|
self.init_sample_data()
|
|
|
|
# Add sortSpecs to ViewSections.
|
|
self.apply_user_action(['BulkUpdateRecord', '_grist_Views_section', [2, 3, 4],
|
|
{'sortColRefs': ['[15, -16]', '[-15, 16, 17]', '[19]']}
|
|
])
|
|
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
|
|
["id", "sortColRefs" ],
|
|
[2, '[15, -16]' ],
|
|
[3, '[-15, 16, 17]'],
|
|
[4, '[19]' ],
|
|
])
|
|
|
|
# Remove column, and check that the correct sortColRefs items are removed.
|
|
self.apply_user_action(["RemoveRecord", '_grist_Tables_column', 16])
|
|
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
|
|
["id", "sortColRefs"],
|
|
[2, '[15]' ],
|
|
[3, '[-15, 17]' ],
|
|
[4, '[19]' ],
|
|
])
|
|
|
|
# Update sortColRefs for next test.
|
|
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 3,
|
|
{'sortColRefs': '[-15, -16, 17]'}
|
|
])
|
|
|
|
# Remove multiple columns using BulkUpdateRecord, and check that the sortSpecs are updated.
|
|
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [15, 17, 19]])
|
|
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
|
|
["id", "sortColRefs"],
|
|
[2, '[]' ],
|
|
[3, '[-16]' ],
|
|
[4, '[]' ],
|
|
])
|