gristlabs_grist-core/sandbox/grist/test_rules.py
Jarosław Sadziński b1c3943bf4 (core) Conditional formatting rules
Summary:
Adding conditional formatting rules feature.

Each column can have multiple styling rules which are applied in order
when evaluated to a truthy value.

- The creator panel has a new section: Cell Style
- New user action AddEmptyRule for adding an empty rule
- New columns in _grist_Table_columns and fields

A new color picker will be introduced in a follow-up diff (as it is also
used in choice/choice list/filters).

Design document:
https://grist.quip.com/FVzfAgoO5xOF/Conditional-Formatting-Implementation-Design

Test Plan: new tests

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: alexmojaki

Differential Revision: https://phab.getgrist.com/D3282
2022-03-23 13:15:02 +01:00

193 lines
7.9 KiB
Python

# -*- coding: utf-8 -*-
import testutil
import test_engine
class TestRules(test_engine.EngineTestCase):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Inventory", [
[2, "Label", "Text", False, "", "", ""],
[3, "Stock", "Int", False, "", "", ""],
]],
],
"DATA": {
"Inventory": [
["id", "Label", "Stock"],
[1, "A1", 0],
[2, "A2", 2],
[3, "A3", 5],
# Duplicate
[4, "A1", 10]
],
}
})
# Helper for rules action
def add_empty(self, col_id):
return self.apply_user_action(['AddEmptyRule', "Inventory", 0, col_id])
def field_add_empty(self, field_id):
return self.apply_user_action(['AddEmptyRule', "Inventory", field_id, 0])
def set_rule(self, col_id, rule_index, formula):
rules = self.engine.docmodel.columns.table.get_record(col_id).rules
rule = list(rules)[rule_index]
return self.apply_user_action(['UpdateRecord', '_grist_Tables_column',
rule.id, {"formula": formula}])
def field_set_rule(self, field_id, rule_index, formula):
rules = self.engine.docmodel.view_fields.table.get_record(field_id).rules
rule = list(rules)[rule_index]
return self.apply_user_action(['UpdateRecord', '_grist_Tables_column',
rule.id, {"formula": formula}])
def remove_rule(self, col_id, rule_index):
rules = self.engine.docmodel.columns.table.get_record(col_id).rules
rule = list(rules)[rule_index]
return self.apply_user_action(['RemoveColumn', 'Inventory', rule.colId])
def field_remove_rule(self, field_id, rule_index):
rules = self.engine.docmodel.view_fields.table.get_record(field_id).rules
rule = list(rules)[rule_index]
return self.apply_user_action(['RemoveColumn', 'Inventory', rule.colId])
def test_simple_rules(self):
self.load_sample(self.sample)
# Mark all records with Stock = 0
out_actions = self.add_empty(3)
self.assertPartialOutActions(out_actions, {"stored": [
["AddColumn", "Inventory", "gristHelper_ConditionalRule",
{"formula": "", "isFormula": True, "type": "Any"}],
["AddRecord", "_grist_Tables_column", 4,
{"colId": "gristHelper_ConditionalRule", "formula": "", "isFormula": True,
"label": "gristHelper_ConditionalRule", "parentId": 1, "parentPos": 3.0,
"type": "Any",
"widgetOptions": ""}],
["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4]}],
]})
out_actions = self.set_rule(3, 0, "$Stock == 0")
self.assertPartialOutActions(out_actions, {"stored": [
["ModifyColumn", "Inventory", "gristHelper_ConditionalRule",
{"formula": "$Stock == 0"}],
["UpdateRecord", "_grist_Tables_column", 4, {"formula": "$Stock == 0"}],
["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
{"gristHelper_ConditionalRule": [True, False, False, False]}],
]})
# Replace this rule with another rule to mark Stock = 2
out_actions = self.set_rule(3, 0, "$Stock == 2")
self.assertPartialOutActions(out_actions, {"stored": [
["ModifyColumn", "Inventory", "gristHelper_ConditionalRule",
{"formula": "$Stock == 2"}],
["UpdateRecord", "_grist_Tables_column", 4, {"formula": "$Stock == 2"}],
["BulkUpdateRecord", "Inventory", [1, 2],
{"gristHelper_ConditionalRule": [False, True]}],
]})
# Add another rule Stock = 10
out_actions = self.add_empty(3)
self.assertPartialOutActions(out_actions, {"stored": [
["AddColumn", "Inventory", "gristHelper_ConditionalRule2",
{"formula": "", "isFormula": True, "type": "Any"}],
["AddRecord", "_grist_Tables_column", 5,
{"colId": "gristHelper_ConditionalRule2", "formula": "", "isFormula": True,
"label": "gristHelper_ConditionalRule2", "parentId": 1, "parentPos": 4.0,
"type": "Any",
"widgetOptions": ""}],
["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4, 5]}],
]})
out_actions = self.set_rule(3, 1, "$Stock == 10")
self.assertPartialOutActions(out_actions, {"stored": [
["ModifyColumn", "Inventory", "gristHelper_ConditionalRule2",
{"formula": "$Stock == 10"}],
["UpdateRecord", "_grist_Tables_column", 5, {"formula": "$Stock == 10"}],
["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
{"gristHelper_ConditionalRule2": [False, False, False, True]}],
]})
# Remove the last rule
out_actions = self.remove_rule(3, 1)
self.assertPartialOutActions(out_actions, {"stored": [
["RemoveRecord", "_grist_Tables_column", 5],
["UpdateRecord", "_grist_Tables_column", 3, {"rules": ["L", 4]}],
["RemoveColumn", "Inventory", "gristHelper_ConditionalRule2"]
]})
# Remove last rule
out_actions = self.remove_rule(3, 0)
self.assertPartialOutActions(out_actions, {"stored": [
["RemoveRecord", "_grist_Tables_column", 4],
["UpdateRecord", "_grist_Tables_column", 3, {"rules": None}],
["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"]
]})
def test_duplicates(self):
self.load_sample(self.sample)
# Create rule that marks duplicate values
formula = "len(Inventory.lookupRecords(Label=$Label)) > 1"
# First add rule on stock column, to test naming - second rule column should have 2 as a suffix
self.add_empty(3)
self.set_rule(3, 0, "$Stock == 0")
# Now highlight duplicates on labels
self.add_empty(2)
out_actions = self.set_rule(2, 0, formula)
self.assertPartialOutActions(out_actions, {"stored": [
["ModifyColumn", "Inventory", "gristHelper_ConditionalRule2",
{"formula": "len(Inventory.lookupRecords(Label=$Label)) > 1"}],
["UpdateRecord", "_grist_Tables_column", 5,
{"formula": "len(Inventory.lookupRecords(Label=$Label)) > 1"}],
["BulkUpdateRecord", "Inventory", [1, 2, 3, 4],
{"gristHelper_ConditionalRule2": [True, False, False, True]}]
]})
def test_column_removal(self):
# Test that rules are removed with a column.
self.load_sample(self.sample)
self.add_empty(3)
self.set_rule(3, 0, "$Stock == 0")
before = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule')
self.assertNotEqual(before, 0)
out_actions = self.apply_user_action(['RemoveColumn', 'Inventory', 'Stock'])
self.assertPartialOutActions(out_actions, {"stored": [
["BulkRemoveRecord", "_grist_Tables_column", [3, 4]],
["RemoveColumn", "Inventory", "Stock"],
["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"],
]})
def test_column_removal_for_a_field(self):
# Test that rules are removed with a column when attached to a field.
self.load_sample(self.sample)
self.apply_user_action(['CreateViewSection', 1, 0, 'record', None])
self.field_add_empty(2)
self.field_set_rule(2, 0, "$Stock == 0")
before = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule')
self.assertNotEqual(before, 0)
out_actions = self.apply_user_action(['RemoveColumn', 'Inventory', 'Stock'])
self.assertPartialOutActions(out_actions, {"stored": [
["RemoveRecord", "_grist_Views_section_field", 2],
["BulkRemoveRecord", "_grist_Tables_column", [3, 4]],
["RemoveColumn", "Inventory", "Stock"],
["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"],
]})
def test_field_removal(self):
# Test that rules are removed with a field.
self.load_sample(self.sample)
self.apply_user_action(['CreateViewSection', 1, 0, 'record', None])
self.field_add_empty(2)
self.field_set_rule(2, 0, "$Stock == 0")
rule_id = self.engine.docmodel.columns.lookupOne(colId='gristHelper_ConditionalRule').id
self.assertNotEqual(rule_id, 0)
out_actions = self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 2])
self.assertPartialOutActions(out_actions, {"stored": [
["RemoveRecord", "_grist_Views_section_field", 2],
["RemoveRecord", "_grist_Tables_column", rule_id],
["RemoveColumn", "Inventory", "gristHelper_ConditionalRule"]
]})