def test_make_a_link_on_matching_insert(tree_3v_goals): goals = tree_3v_goals goals.accept(ToggleAutoLink("me")) # Add a goal to the root goals.accept_all(Select(1), HoldSelect(), Select(3), Insert("Link ME please")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(-12, EdgeType.PARENT), (4, EdgeType.PARENT)] }, -12: { "name": "Autolink: 'me'", "edge": [(2, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [(4, EdgeType.BLOCKER)] }, 4: { "name": "Link ME please", "edge": [(3, EdgeType.PARENT)] }, 3: { "name": "Another subgoal", "edge": [] }, }
def test_select_goal_by_full_id_with_non_empty_cache(goal_chain_11): e = Enumeration(goal_chain_11) assert e.q(keys="name,select") == { 11: {"name": "a", "select": "select"}, 12: {"name": "b", "select": None}, 13: {"name": "c", "select": None}, 14: {"name": "d", "select": None}, 15: {"name": "e", "select": None}, 16: {"name": "f", "select": None}, 17: {"name": "g", "select": None}, 18: {"name": "h", "select": None}, 19: {"name": "i", "select": None}, 10: {"name": "j", "select": None}, 21: {"name": "k", "select": None}, } e.accept_all(Select(2), Select(13)) assert e.q(keys="name,select") == { 11: {"name": "a", "select": "prev"}, 12: {"name": "b", "select": None}, 13: {"name": "c", "select": "select"}, 14: {"name": "d", "select": None}, 15: {"name": "e", "select": None}, 16: {"name": "f", "select": None}, 17: {"name": "g", "select": None}, 18: {"name": "h", "select": None}, 19: {"name": "i", "select": None}, 10: {"name": "j", "select": None}, 21: {"name": "k", "select": None}, }
def test_non_switchable_goals_disappear_on_selection_change(): e = SwitchableView(Goals("root")) e.accept_all(Add("1"), Add("2"), Select(2), ToggleSwitchableView(), Select(2)) assert e.q("name,switchable,select") == { 1: { "name": "root", "switchable": False, "select": "prev" }, 2: { "name": "1", "switchable": True, "select": "select" }, 3: { "name": "2", "switchable": True, "select": None }, } e.accept(HoldSelect()) assert e.q("name,switchable,select") == { 2: { "name": "1", "switchable": True, "select": "select" }, 3: { "name": "2", "switchable": True, "select": None }, }
def test_change_progress_on_close(goaltree): goaltree.accept_all(ToggleProgress(), Select(4), ToggleClose()) assert goaltree.q("name") == { 1: { "name": "[1/4] Root" }, 2: { "name": "[0/1] With blocker" }, 3: { "name": "[1/2] With subgoal" }, 4: { "name": "[1/1] Top goal" }, } goaltree.accept_all(Select(2), ToggleClose()) assert goaltree.q("name") == { 1: { "name": "[2/4] Root" }, 2: { "name": "[1/1] With blocker" }, 3: { "name": "[1/2] With subgoal" }, 4: { "name": "[1/1] Top goal" }, }
def test_autolink_on_all_matching_goals(tree_3v_goals): goals = tree_3v_goals # make 2 autolinks goals.accept_all(ToggleAutoLink("me"), Select(3), ToggleAutoLink("plea")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(-12, EdgeType.PARENT), (-13, EdgeType.PARENT)] }, -12: { "name": "Autolink: 'me'", "edge": [(2, EdgeType.PARENT)] }, -13: { "name": "Autolink: 'plea'", "edge": [(3, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [] }, 3: { "name": "Another subgoal", "edge": [] }, } # add 2-mathing goal goals.accept_all(Select(1), Add("Link me to both please")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [ (-12, EdgeType.PARENT), (-13, EdgeType.PARENT), (4, EdgeType.PARENT), ], }, -12: { "name": "Autolink: 'me'", "edge": [(2, EdgeType.PARENT)] }, -13: { "name": "Autolink: 'plea'", "edge": [(3, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [(4, EdgeType.BLOCKER)] }, 3: { "name": "Another subgoal", "edge": [(4, EdgeType.BLOCKER)] }, 4: { "name": "Link me to both please", "edge": [] }, }
def test_selection_cache_should_avoid_overflow(goal_chain_11): e = Enumeration(goal_chain_11) assert e.q(keys="select")[11] == {"select": "select"} e.accept(Select(5)) assert e.q(keys="select")[11] == {"select": "select"} e.accept(Select(1)) assert e.q(keys="select")[11] == {"select": "select"} assert e.q(keys="select")[14] == {"select": None} e.accept(Select(4)) assert e.q(keys="select")[11] == {"select": "prev"} assert e.q(keys="select")[14] == {"select": "select"}
def test_close_goal_again(self): self.goals = self.build( open_(1, "Root", [2], select=selected), open_(2, "A", [3]), clos_(3, "Ab"), ) self.goals.accept_all(Select(2), ToggleClose()) assert self.goals.q(keys="open,switchable") == { 1: { "open": True, "switchable": True }, 2: { "open": False, "switchable": True }, 3: { "open": False, "switchable": False }, } self.goals.accept_all(Select(2), ToggleClose()) assert self.goals.q(keys="open,switchable") == { 1: { "open": True, "switchable": False }, 2: { "open": True, "switchable": True }, 3: { "open": False, "switchable": True }, } self.goals.accept_all(Select(2), ToggleClose()) assert self.goals.q(keys="open,switchable") == { 1: { "open": True, "switchable": True }, 2: { "open": False, "switchable": True }, 3: { "open": False, "switchable": False }, }
def test_closed_goals_are_shown_when_selected(): v = OpenView( build_goaltree( open_(1, "Root", [2, 3], select=selected), clos_(2, "closed"), clos_(3, "closed too", [4]), clos_(4, "closed and not selected"), )) v.accept_all(ToggleOpenView(), Select(2), HoldSelect(), Select(3)) assert v.q("name,select,open") == { 1: { "name": "Root", "open": True, "select": None }, 2: { "name": "closed", "open": False, "select": "prev" }, 3: { "name": "closed too", "open": False, "select": "select" }, 4: { "name": "closed and not selected", "open": False, "select": None }, } v.accept(ToggleOpenView()) # Still show: open goals, selected goals assert v.q("name,select,open") == { 1: { "name": "Root", "open": True, "select": None }, 2: { "name": "closed", "open": False, "select": "prev" }, 3: { "name": "closed too", "open": False, "select": "select" }, }
def test_do_not_make_a_link_on_matching_subgoal_insert(tree_3i_goals): goals = tree_3i_goals goals.accept(ToggleAutoLink("me")) # Add a sub goal to the same subgoal goals.accept_all(HoldSelect(), Select(3), Insert("Do NOT link me please")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(-12, EdgeType.PARENT)] }, -12: { "name": "Autolink: 'me'", "edge": [(2, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [(4, EdgeType.PARENT)] }, 4: { "name": "Do NOT link me please", "edge": [(3, EdgeType.PARENT)] }, 3: { "name": "Another subgoal", "edge": [] }, }
def test_replace_same_autolink(tree_3v_goals): goals = tree_3v_goals goals.accept_all(ToggleAutoLink("same"), Select(3), ToggleAutoLink("same")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(2, EdgeType.PARENT), (-13, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [] }, -13: { "name": "Autolink: 'same'", "edge": [(3, EdgeType.PARENT)] }, 3: { "name": "Another subgoal", "edge": [] }, } assert _autolink_events(goals) == [ ("add_autolink", 2, "same"), ("remove_autolink", 2), ("add_autolink", 3, "same"), ]
def build_actions(command): simple_commands = { "c": ToggleClose(), "d": Delete(), "h": HoldSelect(), "k": ToggleLink(edge_type=EdgeType.PARENT), "l": ToggleLink(), "n": ToggleOpenView(), "p": ToggleProgress(), "t": ToggleSwitchableView(), "z": ToggleZoom(), } if command and all(c in "1234567890" for c in command): return [Select(int(c)) for c in command] if command.startswith("a "): return [Add(command[2:])] if command.startswith("i "): return [Insert(command[2:])] if command.startswith("r "): return [Rename(command[2:])] if command.startswith("f"): return [FilterBy(command[1:].lstrip())] if command.startswith("` "): return [ToggleAutoLink(command[2:])] if command in simple_commands: return [simple_commands[command]] return []
def test_new_goal_is_added_to_the_selected_node(self): self.goals.accept_all(Add("A"), Select(2)) assert self.goals.q(keys="name,select") == { 1: { "name": "Root", "select": "prev" }, 2: { "name": "A", "select": "select" }, } self.goals.accept(Add("B")) assert self.goals.q(keys="name,select,edge") == { 1: { "name": "Root", "select": "prev", "edge": [(2, EdgeType.PARENT)] }, 2: { "name": "A", "select": "select", "edge": [(3, EdgeType.PARENT)] }, 3: { "name": "B", "select": None, "edge": [] }, }
def test_insert_goal_in_the_middle(self): self.goals.accept_all(Add("B"), HoldSelect(), Select(2)) assert self.goals.q(keys="name,edge,switchable") == { 1: { "name": "Root", "edge": [(2, EdgeType.PARENT)], "switchable": False }, 2: { "name": "B", "edge": [], "switchable": True }, } self.goals.accept(Insert("A")) assert self.goals.q(keys="name,edge,switchable") == { 1: { "name": "Root", "edge": [(3, EdgeType.PARENT)], "switchable": False }, 2: { "name": "B", "edge": [], "switchable": True }, 3: { "name": "A", "edge": [(2, EdgeType.PARENT)], "switchable": False }, }
def test_zoomed_parent_goal_must_not_be_filtered_out(zoomed_goaltree): zoomed_goaltree.accept_all(HoldSelect(), Select(2), ToggleZoom(), FilterBy("mm")) assert zoomed_goaltree.q("name,edge,select") == { -1: { "name": "Alpha", "edge": [(-2, EdgeType.BLOCKER), (2, EdgeType.BLOCKER)], "select": "prev", }, -2: { "name": "Filter by 'mm'", "edge": [(2, EdgeType.BLOCKER), (3, EdgeType.BLOCKER)], "select": None, }, 2: { "name": "Beta", "edge": [(3, EdgeType.PARENT)], "select": "select" }, 3: { "name": "Gamma", "edge": [], "select": None }, }
def test_select_goal_by_id_parts(goal_chain_11): e = Enumeration(goal_chain_11) e.accept_all(Select(1), Select(6)) assert e.q(keys="name,select") == { 11: {"name": "a", "select": "prev"}, 12: {"name": "b", "select": None}, 13: {"name": "c", "select": None}, 14: {"name": "d", "select": None}, 15: {"name": "e", "select": None}, 16: {"name": "f", "select": "select"}, 17: {"name": "g", "select": None}, 18: {"name": "h", "select": None}, 19: {"name": "i", "select": None}, 10: {"name": "j", "select": None}, 21: {"name": "k", "select": None}, }
def test_closed_leaf_goal_could_not_be_reopened(self): self.goals = self.build(open_(1, "Root", [2], select=selected), clos_(2, "A", [3]), clos_(3, "B")) assert self.goals.q(keys="open,switchable") == { 1: { "open": True, "switchable": True }, 2: { "open": False, "switchable": True }, 3: { "open": False, "switchable": False }, } self.goals.accept_all(Select(3), ToggleClose()) # nothing should change assert self.goals.q(keys="open,switchable") == { 1: { "open": True, "switchable": True }, 2: { "open": False, "switchable": True }, 3: { "open": False, "switchable": False }, }
def test_make_a_link_on_matching_add(tree_2_goals): goals = tree_2_goals goals.accept(ToggleAutoLink("me")) # Add a goal to the root goals.accept_all(Select(1), Add("Link ME please")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(-12, EdgeType.PARENT), (3, EdgeType.PARENT)] }, -12: { "name": "Autolink: 'me'", "edge": [(2, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [(3, EdgeType.BLOCKER)] }, 3: { "name": "Link ME please", "edge": [] }, } assert _autolink_events(goals) == [ ("add_autolink", 2, "me"), ]
def test_do_not_make_a_link_on_not_old_matching_add(tree_2_goals): goals = tree_2_goals goals.accept_all(ToggleAutoLink("old"), ToggleAutoLink("new")) # Add a goal to the root goals.accept_all(Select(1), Add("This is old subgoal")) assert goals.q("name,edge") == { 1: { "name": "Root", "edge": [(-12, EdgeType.PARENT), (3, EdgeType.PARENT)] }, -12: { "name": "Autolink: 'new'", "edge": [(2, EdgeType.PARENT)] }, 2: { "name": "Autolink on me", "edge": [] }, 3: { "name": "This is old subgoal", "edge": [] }, } assert _autolink_events(goals) == [ ("add_autolink", 2, "old"), ("remove_autolink", 2), ("add_autolink", 2, "new"), ]
class Enumeration(Graph): def __init__(self, goaltree: Graph) -> None: super().__init__(goaltree) self.selection_cache: List[int] = [] def _id_mapping( self, keys: str = "name") -> Tuple[Dict[int, Any], BidirectionalIndex]: goals = self.goaltree.q(keys) return goals, BidirectionalIndex(goals) def q(self, keys: str = "name") -> Dict[int, Any]: result: Dict[int, Any] = dict() goals, index = self._id_mapping(keys) for old_id, val in goals.items(): new_id = index.forward(old_id) result[new_id] = {k: v for k, v in val.items() if k != "edge"} if "edge" in val: result[new_id]["edge"] = [(index.forward(edge[0]), edge[1]) for edge in val["edge"]] return result def accept_Select(self, command: Select): goals, index = self._id_mapping() if (goal_id := command.goal_id) >= 10: self.selection_cache = [] if self.selection_cache: goal_id = 10 * self.selection_cache.pop() + goal_id if goal_id > max(index.forward(k) for k in goals.keys()): goal_id %= int(pow(10, int(math.log(goal_id, 10)))) if (original_id := index.backward(goal_id)) != BidirectionalIndex.NOT_FOUND: self.goaltree.accept(Select(original_id)) self.selection_cache = []
def insert(self, d): event("insert") candidates = sorted( list(goal_id for goal_id, attrs in self.goaltree.q("select").items() if attrs["select"] != "select")) random_goal = d.draw(sampled_from(candidates)) self._accept_all(HoldSelect(), Select(random_goal), Insert("i"))
def test_selection_cache_should_be_reset_after_view_switch(goal_chain_11): e = Enumeration(SwitchableView(goal_chain_11)) e.accept_all(Add("Also top")) e.accept(Select(1)) # Select(1) is kept in a cache and not applied yet e.accept(ToggleSwitchableView()) assert e.q("name,select") == { 1: {"name": "a", "select": "select"}, 2: {"name": "k", "select": None}, 3: {"name": "Also top", "select": None}, } # Select(2) is being applied without any effect from the previous selection # This happens because selection cache was reset e.accept(Select(2)) assert e.q("name,select") == { 1: {"name": "a", "select": "prev"}, 2: {"name": "k", "select": "select"}, 3: {"name": "Also top", "select": None}, }
def select_random_goal(self, d): event("select") max_key = max(self.goaltree.q().keys()) assume(max_key >= 1) event("valid select 1") random_goal = d.draw(integers(min_value=1, max_value=max_key)) assume(random_goal in self.goaltree.q()) event("valid select 2") self._accept(Select(random_goal)) # Any valid goal must be selectable assert self.goaltree.q("select")[random_goal]["select"] == "select"
def test_save_and_load(): file_name = NamedTemporaryFile().name goals = Enumeration(all_layers(Goals("Root"))) goals.accept_all( Add("Top"), Add("Middle"), Select(3), HoldSelect(), Select(2), ToggleLink(), Add("Closed"), Select(4), ToggleClose(), Select(2), ToggleZoom(), ) save(goals, file_name) new_goals = load(file_name) goals.accept_all(ToggleOpenView()) new_goals.accept_all(ToggleOpenView()) assert goals.q(keys="open,name,edge,select,switchable") == new_goals.q( keys="open,name,edge,select,switchable")
def accept_ToggleClose(self, command: ToggleClose) -> None: if self.selection in self.closed: if self._may_be_reopened(): self.closed.remove(self.selection) self._events.append(("toggle_close", True, self.selection)) else: self.error( "This goal can't be reopened because other subgoals block it" ) else: if self._may_be_closed(): self.closed.add(self.selection) self._events.append(("toggle_close", False, self.selection)) if self.previous_selection != self.selection: self.accept_Select(Select(self.previous_selection)) else: self.accept_Select( Select(self._first_open_and_switchable(command.root))) self.accept(HoldSelect()) else: self.error( "This goal can't be closed because it have open subgoals")
def test_remove_autolink_on_parent_delete(tree_3i_goals): goals = tree_3i_goals goals.accept_all(Select(3), ToggleAutoLink("test")) assert goals.q("edge") == { 1: { "edge": [(2, EdgeType.PARENT)] }, 2: { "edge": [(-13, EdgeType.PARENT)] }, -13: { "edge": [(3, EdgeType.PARENT)] }, 3: { "edge": [] }, } goals.accept_all(Select(2), Delete()) assert goals.q("edge") == {1: {"edge": []}} assert _autolink_events(goals) == [ ("add_autolink", 3, "test"), ("remove_autolink", 3), ]
def test_use_mapping_in_selection(goal_chain_10): e = Enumeration(goal_chain_10) e.accept(Select(0)) assert e.q(keys="name,select") == { 1: {"name": "a", "select": "prev"}, 2: {"name": "b", "select": None}, 3: {"name": "c", "select": None}, 4: {"name": "d", "select": None}, 5: {"name": "e", "select": None}, 6: {"name": "f", "select": None}, 7: {"name": "g", "select": None}, 8: {"name": "h", "select": None}, 9: {"name": "i", "select": None}, 0: {"name": "j", "select": "select"}, }
def test_restore_goals_from_db(): file_name = NamedTemporaryFile().name with sqlite3.connect(file_name) as conn: run_migrations(conn) setup_sample_db(conn) actual_goals = load(file_name) actual_goals.accept(ToggleOpenView()) expected_goals = Goals("Root") expected_goals.accept_all( Add("A"), Add("B"), Select(2), HoldSelect(), Select(3), ToggleLink(), Select(3), ToggleClose(), Select(1), HoldSelect(), Select(2), ) keys = "name,edge,open,select" assert expected_goals.q(keys=keys) == actual_goals.q(keys=keys) assert not actual_goals.events()
def test_do_not_select_goal_by_partial_id(goal_chain_11): e = Enumeration(goal_chain_11) # Select(1) is kept in cache, and selection is not changed yet e.accept_all(Select(1)) assert e.q(keys="name,select") == { 11: {"name": "a", "select": "select"}, 12: {"name": "b", "select": None}, 13: {"name": "c", "select": None}, 14: {"name": "d", "select": None}, 15: {"name": "e", "select": None}, 16: {"name": "f", "select": None}, 17: {"name": "g", "select": None}, 18: {"name": "h", "select": None}, 19: {"name": "i", "select": None}, 10: {"name": "j", "select": None}, 21: {"name": "k", "select": None}, }
def test_remove_blocked_goal_without_children(self): self.goals = self.build( open_(1, "Root", [2, 3]), open_(2, "A", [4]), open_(3, "B", blockers=[4]), open_(4, "C", select=selected), ) assert self.goals.q(keys="name,edge") == { 1: { "name": "Root", "edge": [(2, EdgeType.PARENT), (3, EdgeType.PARENT)] }, 2: { "name": "A", "edge": [(4, EdgeType.PARENT)] }, 3: { "name": "B", "edge": [(4, EdgeType.BLOCKER)] }, 4: { "name": "C", "edge": [] }, } self.goals.accept_all(Select(3), Delete()) assert self.goals.q(keys="name,edge,switchable") == { 1: { "name": "Root", "edge": [(2, EdgeType.PARENT)], "switchable": False }, 2: { "name": "A", "edge": [(4, EdgeType.PARENT)], "switchable": False }, 4: { "name": "C", "edge": [], "switchable": True }, }
def test_previously_selected_goal_must_not_be_filtered_out(goaltree): goaltree.accept_all(Select(3), FilterBy("matching no one")) assert goaltree.q("name,edge,select") == { 1: { "name": "Alpha", "edge": [(-2, EdgeType.BLOCKER)], "select": "prev" }, -2: { "name": "Filter by 'matching no one'", "edge": [(3, EdgeType.BLOCKER)], "select": None, }, 3: { "name": "Gamma", "edge": [], "select": "select" }, }