Exemplo n.º 1
0
def test_data_collection_combo_helper():

    callback = MagicMock()
    state = ExampleState()
    state.add_callback('combo', callback)

    dc = DataCollection([])

    helper = DataCollectionComboHelper(state, 'combo', dc)  # noqa

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    assert callback.call_count == 0

    dc.append(data1)

    assert callback.call_count == 1

    assert selection_choices(state, 'combo') == "data1"

    data1.label = 'mydata1'
    assert selection_choices(state, 'combo') == "mydata1"

    assert callback.call_count == 2

    dc.remove(data1)

    assert callback.call_count == 3

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 2
0
def test_data_collection_combo_helper():

    callback = MagicMock()
    state = ExampleState()
    state.add_callback('combo', callback)

    dc = DataCollection([])

    helper = DataCollectionComboHelper(state, 'combo', dc)  # noqa

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    assert callback.call_count == 0

    dc.append(data1)

    assert callback.call_count == 1

    assert selection_choices(state, 'combo') == "data1"

    data1.label = 'mydata1'
    assert selection_choices(state, 'combo') == "mydata1"

    assert callback.call_count == 2

    dc.remove(data1)

    assert callback.call_count == 3

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 3
0
def test_component_id_combo_helper():

    state = ExampleState()

    dc = DataCollection([])

    helper = ComponentIDComboHelper(state, 'combo', dc)

    assert selection_choices(state, 'combo') == ""

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)
    helper.append_data(data1)

    assert selection_choices(state, 'combo') == "x:y"

    data2 = Data(a=[1, 2, 3], b=['a', 'b', 'c'], label='data2')

    dc.append(data2)
    helper.append_data(data2)

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a:b"

    helper.categorical = False

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a"

    helper.numeric = False

    assert selection_choices(state, 'combo') == "data1:data2"

    helper.categorical = True
    helper.numeric = True

    helper.visible = False
    assert selection_choices(
        state, 'combo'
    ) == "data1:x:Pixel Axis 0 [x]:World 0:y:data2:a:Pixel Axis 0 [x]:World 0:b"
    helper.visible = True

    dc.remove(data2)

    assert selection_choices(state, 'combo') == "x:y"

    # TODO: check that renaming a component updates the combo
    # data1.id['x'].label = 'z'
    # assert selection_choices(state, 'combo') == "z:y"

    helper.remove_data(data1)

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 4
0
def test_component_id_combo_helper():

    combo = QtWidgets.QComboBox()

    dc = DataCollection([])

    helper = ComponentIDComboHelper(combo, dc)

    assert combo_as_string(combo) == ""

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)
    helper.append_data(data1)

    assert combo_as_string(combo) == "x:y"

    data2 = Data(a=[1, 2, 3], b=['a', 'b', 'c'], label='data2')

    dc.append(data2)
    helper.append_data(data2)

    assert combo_as_string(combo) == "data1:x:y:data2:a:b"

    helper.categorical = False

    assert combo_as_string(combo) == "data1:x:y:data2:a"

    helper.numeric = False

    assert combo_as_string(combo) == "data1:data2"

    helper.categorical = True
    helper.numeric = True

    helper.visible = False
    assert combo_as_string(
        combo
    ) == "data1:Pixel Axis 0 [x]:World 0:x:y:data2:Pixel Axis 0 [x]:World 0:a:b"
    helper.visible = True

    dc.remove(data2)

    assert combo_as_string(combo) == "x:y"

    # TODO: check that renaming a component updates the combo
    # data1.id['x'].label = 'z'
    # assert combo_as_string(combo) == "z:y"

    helper.remove_data(data1)

    assert combo_as_string(combo) == ""
Exemplo n.º 5
0
    def test_close_on_last_layer_remove(self):
        # regression test for 391

        d1 = Data(x=np.random.random((2,) * self.ndim))
        d2 = Data(y=np.random.random((2,) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        with patch.object(self.widget_cls, 'close') as close:
            w = app.new_data_viewer(self.widget_cls, data=d1)
            w.add_data(d2)
            dc.remove(d1)
            dc.remove(d2)
        assert close.call_count >= 1
Exemplo n.º 6
0
    def test_close_on_last_layer_remove(self):
        # regression test for 391

        d1 = Data(x=np.random.random((2,) * self.ndim))
        d2 = Data(y=np.random.random((2,) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        with patch.object(self.widget_cls, 'close') as close:
            w = app.new_data_viewer(self.widget_cls, data=d1)
            w.add_data(d2)
            dc.remove(d1)
            dc.remove(d2)
        assert close.call_count >= 1
Exemplo n.º 7
0
def test_component_id_combo_helper():

    combo = QtWidgets.QComboBox()

    dc = DataCollection([])

    helper = ComponentIDComboHelper(combo, dc)

    assert _items_as_string(combo) == ""

    data1 = Data(x=[1,2,3], y=[2,3,4], label='data1')

    dc.append(data1)
    helper.append_data(data1)

    assert _items_as_string(combo) == "x:y"

    data2 = Data(a=[1,2,3], b=['a','b','c'], label='data2')

    dc.append(data2)
    helper.append_data(data2)

    assert _items_as_string(combo) == "data1:x:y:data2:a:b"

    helper.categorical = False

    assert _items_as_string(combo) == "data1:x:y:data2:a"

    helper.numeric = False

    assert _items_as_string(combo) == "data1:data2"

    helper.categorical = True
    helper.numeric = True

    helper.visible = False
    assert _items_as_string(combo) == "data1:Pixel Axis 0 [x]:World 0:x:y:data2:Pixel Axis 0 [x]:World 0:a:b"
    helper.visible = True

    dc.remove(data2)

    assert _items_as_string(combo) == "x:y"

    # TODO: check that renaming a component updates the combo
    # data1.id['x'].label = 'z'
    # assert _items_as_string(combo) == "z:y"

    helper.remove_data(data1)

    assert _items_as_string(combo) == ""
Exemplo n.º 8
0
    def test_close_on_last_layer_remove(self):

        # regression test for 391

        d1 = Data(x=np.random.random((2,) * self.ndim))
        d2 = Data(y=np.random.random((2,) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        w = app.new_data_viewer(self.widget_cls, data=d1)
        w.add_data(d2)
        process_events()
        assert len(app.viewers[0]) == 1
        dc.remove(d1)
        process_events()
        assert len(app.viewers[0]) == 1
        dc.remove(d2)
        process_events()
        assert len(app.viewers[0]) == 0
        app.close()
Exemplo n.º 9
0
    def test_close_on_last_layer_remove(self):

        # regression test for 391

        d1 = Data(x=np.random.random((2, ) * self.ndim))
        d2 = Data(y=np.random.random((2, ) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        w = app.new_data_viewer(self.widget_cls, data=d1)
        w.add_data(d2)
        process_events()
        assert len(app.viewers[0]) == 1
        dc.remove(d1)
        process_events()
        assert len(app.viewers[0]) == 1
        dc.remove(d2)
        process_events()
        assert len(app.viewers[0]) == 0
        app.close()
Exemplo n.º 10
0
def test_data_collection_combo_helper():

    combo = QtWidgets.QComboBox()

    dc = DataCollection([])

    helper = DataCollectionComboHelper(combo, dc)

    data1 = Data(x=[1,2,3], y=[2,3,4], label='data1')

    dc.append(data1)

    assert _items_as_string(combo) == "data1"

    data1.label = 'mydata1'
    assert _items_as_string(combo) == "mydata1"

    dc.remove(data1)

    assert _items_as_string(combo) == ""
Exemplo n.º 11
0
def test_data_collection_combo_helper():

    combo = QtWidgets.QComboBox()

    dc = DataCollection([])

    helper = DataCollectionComboHelper(combo, dc)

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)

    assert combo_as_string(combo) == "data1"

    data1.label = 'mydata1'
    assert combo_as_string(combo) == "mydata1"

    dc.remove(data1)

    assert combo_as_string(combo) == ""
Exemplo n.º 12
0
def test_data_collection_combo_helper():

    state = ExampleState()

    dc = DataCollection([])

    helper = DataCollectionComboHelper(state, 'combo', dc)  # noqa

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)

    assert selection_choices(state, 'combo') == "data1"

    data1.label = 'mydata1'
    assert selection_choices(state, 'combo') == "mydata1"

    dc.remove(data1)

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 13
0
def test_manual_data_combo_helper(initialize_data_collection):

    # The case with initialize_data_collection=False is a regression test for a
    # bug which meant that when a ManualDataComboHelper was initialized without
    # a data collection, it did not change when a data object added later has a
    # label changed.

    callback = MagicMock()
    state = ExampleState()
    state.add_callback('combo', callback)

    dc = DataCollection([])

    if initialize_data_collection:
        helper = ManualDataComboHelper(state, 'combo', dc)
    else:
        helper = ManualDataComboHelper(state, 'combo')

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)

    assert callback.call_count == 0

    assert selection_choices(state, 'combo') == ""

    helper.append_data(data1)
    assert callback.call_count == 1

    assert selection_choices(state, 'combo') == "data1"

    data1.label = 'mydata1'
    assert selection_choices(state, 'combo') == "mydata1"
    assert callback.call_count == 2

    if initialize_data_collection:

        dc.remove(data1)

        assert selection_choices(state, 'combo') == ""
        assert callback.call_count == 3
Exemplo n.º 14
0
def test_manual_data_combo_helper(initialize_data_collection):

    # The case with initialize_data_collection=False is a regression test for a
    # bug which meant that when a ManualDataComboHelper was initialized without
    # a data collection, it did not change when a data object added later has a
    # label changed.

    callback = MagicMock()
    state = ExampleState()
    state.add_callback('combo', callback)

    dc = DataCollection([])

    if initialize_data_collection:
        helper = ManualDataComboHelper(state, 'combo', dc)
    else:
        helper = ManualDataComboHelper(state, 'combo')

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)

    assert callback.call_count == 0

    assert selection_choices(state, 'combo') == ""

    helper.append_data(data1)
    assert callback.call_count == 1

    assert selection_choices(state, 'combo') == "data1"

    data1.label = 'mydata1'
    assert selection_choices(state, 'combo') == "mydata1"
    assert callback.call_count == 2

    if initialize_data_collection:

        dc.remove(data1)

        assert selection_choices(state, 'combo') == ""
        assert callback.call_count == 3
Exemplo n.º 15
0
    def test_close_on_last_layer_remove(self):

        # regression test for 391

        # Note: processEvents is needed for things to work correctly with PySide2
        qtapp = get_qapp()

        d1 = Data(x=np.random.random((2,) * self.ndim))
        d2 = Data(y=np.random.random((2,) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        w = app.new_data_viewer(self.widget_cls, data=d1)
        w.add_data(d2)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 1
        dc.remove(d1)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 1
        dc.remove(d2)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 0
        app.close()
Exemplo n.º 16
0
    def test_close_on_last_layer_remove(self):

        # regression test for 391

        # Note: processEvents is needed for things to work correctly with PySide2
        qtapp = get_qapp()

        d1 = Data(x=np.random.random((2, ) * self.ndim))
        d2 = Data(y=np.random.random((2, ) * self.ndim))
        dc = DataCollection([d1, d2])
        app = GlueApplication(dc)
        w = app.new_data_viewer(self.widget_cls, data=d1)
        w.add_data(d2)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 1
        dc.remove(d1)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 1
        dc.remove(d2)
        qtapp.processEvents()
        assert len(app.viewers[0]) == 0
        app.close()
Exemplo n.º 17
0
def test_manual_data_combo_helper():

    combo = QtGui.QComboBox()

    dc = DataCollection([])

    helper = ManualDataComboHelper(combo, dc)

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)

    assert _items_as_string(combo) == ""

    helper.append(data1)

    assert _items_as_string(combo) == "data1"

    data1.label = 'mydata1'
    assert _items_as_string(combo) == "mydata1"

    dc.remove(data1)

    assert _items_as_string(combo) == ""
Exemplo n.º 18
0
class TestLinkEditor:

    def setup_method(self, method):

        self.data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1')
        self.data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2')
        self.data3 = Data(i=[5, 4, 3], j=[2, 2, 1], label='data3')

        self.data_collection = DataCollection([self.data1, self.data2, self.data3])

    def test_defaults(self):
        # Make sure the dialog opens and closes and check default settings.
        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None
        assert not link_widget.button_add_link.isEnabled()
        assert not link_widget.button_remove_link.isEnabled()

        link_widget.state.data1 = self.data2

        assert not link_widget.button_add_link.isEnabled()
        assert not link_widget.button_remove_link.isEnabled()

        link_widget.state.data2 = self.data1

        assert link_widget.button_add_link.isEnabled()
        assert link_widget.button_remove_link.isEnabled()

        dialog.accept()

        assert len(self.data_collection.external_links) == 0

    def test_defaults_two(self):
        # Make sure the dialog opens and closes and check default settings. With
        # two datasets, the datasets should be selected by default.
        self.data_collection.remove(self.data3)
        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2
        assert link_widget.button_add_link.isEnabled()
        assert link_widget.button_remove_link.isEnabled()
        dialog.accept()
        assert len(self.data_collection.external_links) == 0

    def test_ui_behavior(self):

        # This is a bit more detailed test that checks that things update
        # correctly as we change various settings

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        add_identity_link = get_action(link_widget, 'identity')
        add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume')

        # At this point, there should be no links in the main list widget
        # and nothing on the right.
        assert link_widget.listsel_current_link.count() == 0
        assert link_widget.link_details.text() == ''
        assert link_widget.link_io.itemAt(0) is None

        # Let's add an identity link
        add_identity_link.trigger()

        # Ensure that all events get processed
        process_events()

        # Now there should be one link in the main list and content in the
        # right hand panel.
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a'

        # Let's change the current components for the link
        link_widget.state.current_link.x = self.data1.id['y']
        link_widget.state.current_link.y = self.data2.id['b']

        # and make sure the UI gets updated
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # We now add another link of a different type
        add_lengths_volume_link.trigger()

        # Ensure that all events get processed
        process_events()

        # and make sure the UI has updated
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Convert between linear measurements and volume'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a'

        # Now switch back to the first link
        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[0]

        # and make sure the UI updates and has preserved the correct settings
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # Next up, we try changing the data

        link_widget.state.data1 = self.data3

        # At this point there should be no links in the list

        assert link_widget.listsel_current_link.count() == 0
        assert link_widget.link_details.text() == ''
        assert link_widget.link_io.itemAt(0) is None

        # Add another identity link
        add_identity_link.trigger()

        # Ensure that all events get processed
        process_events()

        # Now there should be one link in the main list
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a'

        # Switch back to the original data
        link_widget.state.data1 = self.data1

        # And check the output is as before
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # Let's now remove this link
        link_widget.button_remove_link.click()

        # Ensure that all events get processed
        process_events()

        # We should now see the lengths/volume link
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Convert between linear measurements and volume'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 2

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_from_ids()[1] is self.data1.id['y']
        assert links[0].get_from_ids()[2] is self.data1.id['z']
        assert links[0].get_to_id() is self.data2.id['a']

        assert isinstance(links[1], ComponentLink)
        assert links[1].get_from_ids()[0] is self.data3.id['i']
        assert links[1].get_to_id() is self.data2.id['a']

    def test_graph(self):

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        add_identity_link = get_action(link_widget, 'identity')

        graph = link_widget.graph_widget

        def click(node_or_edge):
            # We now simulate a selection - since we can't deterministically
            # figure out the exact pixel coordinates to use, we patch
            # 'find_object' to return the object we want to select.
            with patch.object(graph, 'find_object', return_value=node_or_edge):
                graph.mousePressEvent(None)

        def hover(node_or_edge):
            # Same as for select, we patch find_object
            with patch.object(graph, 'find_object', return_value=node_or_edge):
                graph.mouseMoveEvent(None)

        # To start with, no data should be selected
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # and the graph should have three nodes and no edges
        assert len(graph.nodes) == 3
        assert len(graph.edges) == 0

        click(graph.nodes[0])

        # Check that this has caused one dataset to be selected
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        # Click on the same node again and this should deselect the data
        # (but only once we move off from the node)

        click(graph.nodes[0])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        hover(None)

        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Select it again
        click(graph.nodes[0])

        # and now select another node too
        click(graph.nodes[1])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        assert len(graph.nodes) == 3
        assert len(graph.edges) == 0

        add_identity_link.trigger()

        assert len(graph.nodes) == 3
        assert len(graph.edges) == 1

        # Unselect and select another node
        click(graph.nodes[1])
        click(graph.nodes[2])

        # and check the data selections have been updated
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data3

        # Deselect it and move off
        click(graph.nodes[2])
        hover(None)

        # and the second dataset should now once again be None
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        # Now change the data manually
        link_widget.state.data1 = self.data2
        link_widget.state.data2 = self.data3

        # and check that if we select the edge the datasets change back
        click(graph.edges[0])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        # Unselect and hover over nothing
        click(graph.edges[0])
        hover(None)
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Hover over the edge and the datasets should change back
        hover(graph.edges[0])
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        # And check that clicking outside of nodes/edges deselects everything
        click(None)
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Select a node, select another, then make sure that selecting a third
        # one will deselect the two original ones
        click(graph.nodes[0])
        click(graph.nodes[1])
        click(graph.nodes[2])
        assert link_widget.state.data1 is self.data3
        assert link_widget.state.data2 is None

        dialog.accept()

    def test_preexisting_links(self):

        # Check that things work properly if there are pre-existing links

        link1 = ComponentLink([self.data1.id['x']], self.data2.id['c'])

        def add(x, y):
            return x + y

        def double(x):
            return x * 2

        def halve(x):
            return x / 2

        link2 = ComponentLink([self.data2.id['a'], self.data2.id['b']], self.data3.id['j'], using=add)
        link3 = ComponentLink([self.data3.id['i']], self.data2.id['c'], using=double, inverse=halve)

        self.data_collection.add_link(link1)
        self.data_collection.add_link(link2)
        self.data_collection.add_link(link3)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        link_widget.state.data1 = self.data3

        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 6
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'a'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'b'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'j'

        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1]

        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 3

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_to_id() is self.data2.id['c']
        assert links[0].get_using() is identity

        assert isinstance(links[1], ComponentLink)
        assert links[1].get_from_ids()[0] is self.data2.id['a']
        assert links[1].get_from_ids()[1] is self.data2.id['b']
        assert links[1].get_to_id() is self.data3.id['j']
        assert links[1].get_using() is add

        assert isinstance(links[2], ComponentLink)
        assert links[2].get_from_ids()[0] is self.data3.id['i']
        assert links[2].get_to_id() is self.data2.id['c']
        assert links[2].get_using() is double
        assert links[2].get_inverse() is halve

    def test_add_helper(self):

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic')

        # Add a coordinate link
        add_coordinate_link.trigger()

        # Ensure that all events get processed
        process_events()

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link ICRS and Galactic coordinates'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'a'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], ICRS_to_Galactic)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data1.id['y']
        assert links[0].cids2[0] is self.data2.id['a']
        assert links[0].cids2[1] is self.data2.id['b']

    def test_preexisting_helper(self):

        link1 = Galactic_to_FK5(cids1=[self.data1.id['x'], self.data1.id['y']],
                                cids2=[self.data2.id['c'], self.data2.id['b']])

        self.data_collection.add_link(link1)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.listsel_current_link.count() == 0

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link Galactic and FK5 (J2000) Equatorial coordinates'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'c'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], Galactic_to_FK5)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data1.id['y']
        assert links[0].cids2[0] is self.data2.id['c']
        assert links[0].cids2[1] is self.data2.id['b']

    def test_cancel(self):

        # Make sure that changes aren't saved if dialog is cancelled
        # This is a bit more detailed test that checks that things update
        # correctly as we change various settings

        link1 = ComponentLink([self.data1.id['x']], self.data2.id['c'])

        self.data_collection.add_link(link1)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        link_widget.state.current_link.x = self.data1.id['y']

        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'

        add_identity_link = get_action(link_widget, 'identity')
        add_identity_link.trigger()

        assert link_widget.listsel_current_link.count() == 2

        dialog.reject()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_to_id() is self.data2.id['c']

    def test_functional_link_collection(self):

        # Test that if we use a @link_helper in 'legacy' mode, i.e. with only
        # input labels, both datasets are available from the combos in the
        # link editor dialog. Also test the new-style @link_helper.

        def deg_arcsec(degree, arcsecond):
            return [ComponentLink([degree], arcsecond, using=lambda d: d * 3600),
                    ComponentLink([arcsecond], degree, using=lambda a: a / 3600)]

        # Old-style link helper

        helper1 = functional_link_collection(deg_arcsec, description='Legacy link',
                                             labels1=['deg', 'arcsec'], labels2=[])

        link1 = helper1(cids1=[self.data1.id['x'], self.data2.id['c']])

        self.data_collection.add_link(link1)

        # New-style link helper

        helper2 = functional_link_collection(deg_arcsec, description='New-style link',
                                             labels1=['deg'], labels2=['arcsec'])

        link2 = helper2(cids1=[self.data1.id['x']], cids2=[self.data2.id['c']])

        self.data_collection.add_link(link2)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.listsel_current_link.count() == 0

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 2

        assert link_widget.link_details.text() == 'Legacy link'
        assert non_empty_rows_count(get_link_io(link_widget)) == 4
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'c'

        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1]

        assert link_widget.link_details.text() == 'New-style link'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 2

        assert isinstance(links[0], helper1)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data2.id['c']

        assert isinstance(links[1], helper2)
        assert links[1].cids1[0] is self.data1.id['x']
        assert links[1].cids2[0] is self.data2.id['c']

    def test_same_data(self):

        # Test that we can't set the same data twice

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.state.data1 == self.data1
        assert link_widget.state.data2 == self.data2

        link_widget.state.data1 = self.data2

        assert link_widget.state.data1 == self.data2
        assert link_widget.state.data2 == self.data1

        link_widget.state.data2 = self.data2

        assert link_widget.state.data1 == self.data1
        assert link_widget.state.data2 == self.data2
Exemplo n.º 19
0
def test_component_id_combo_helper():

    state = ExampleState()

    dc = DataCollection([])

    helper = ComponentIDComboHelper(state, 'combo', dc)

    assert selection_choices(state, 'combo') == ""

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)
    helper.append_data(data1)

    assert selection_choices(state, 'combo') == "x:y"

    data2 = Data(a=[1, 2, 3], b=['a', 'b', 'c'], label='data2')

    dc.append(data2)
    helper.append_data(data2)

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a:b"

    helper.categorical = False

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a"

    helper.numeric = False

    assert selection_choices(state, 'combo') == "data1:data2"

    helper.categorical = True
    helper.numeric = True

    helper.pixel_coord = True
    assert selection_choices(
        state, 'combo'
    ) == "data1:main:x:y:coord:Pixel Axis 0 [x]:data2:main:a:b:coord:Pixel Axis 0 [x]"

    helper.world_coord = True
    assert selection_choices(
        state, 'combo'
    ) == "data1:main:x:y:coord:Pixel Axis 0 [x]:World 0:data2:main:a:b:coord:Pixel Axis 0 [x]:World 0"

    helper.pixel_coord = False
    assert selection_choices(
        state,
        'combo') == "data1:main:x:y:coord:World 0:data2:main:a:b:coord:World 0"

    helper.world_coord = False

    dc.remove(data2)

    assert selection_choices(state, 'combo') == "x:y"

    data1['z'] = data1.id['x'] + 1

    assert selection_choices(state, 'combo') == "main:x:y:derived:z"

    helper.derived = False

    assert selection_choices(state, 'combo') == "x:y"

    data1.id['x'].label = 'z'
    assert selection_choices(state, 'combo') == "z:y"

    helper.remove_data(data1)

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 20
0
def test_component_id_combo_helper():

    state = ExampleState()

    dc = DataCollection([])

    helper = ComponentIDComboHelper(state, 'combo', dc)

    assert selection_choices(state, 'combo') == ""

    data1 = Data(x=[1, 2, 3], y=[2, 3, 4], label='data1')

    dc.append(data1)
    helper.append_data(data1)

    assert selection_choices(state, 'combo') == "x:y"

    data2 = Data(a=[1, 2, 3], b=['a', 'b', 'c'], label='data2')

    dc.append(data2)
    helper.append_data(data2)

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a:b"

    helper.categorical = False

    assert selection_choices(state, 'combo') == "data1:x:y:data2:a"

    helper.numeric = False

    assert selection_choices(state, 'combo') == "data1:data2"

    helper.categorical = True
    helper.numeric = True

    helper.pixel_coord = True
    assert selection_choices(state, 'combo') == "data1:main:x:y:coord:Pixel Axis 0 [x]:data2:main:a:b:coord:Pixel Axis 0 [x]"

    helper.world_coord = True
    assert selection_choices(state, 'combo') == "data1:main:x:y:coord:Pixel Axis 0 [x]:World 0:data2:main:a:b:coord:Pixel Axis 0 [x]:World 0"

    helper.pixel_coord = False
    assert selection_choices(state, 'combo') == "data1:main:x:y:coord:World 0:data2:main:a:b:coord:World 0"

    helper.world_coord = False

    dc.remove(data2)

    assert selection_choices(state, 'combo') == "x:y"

    data1['z'] = data1.id['x'] + 1

    assert selection_choices(state, 'combo') == "main:x:y:derived:z"

    helper.derived = False

    assert selection_choices(state, 'combo') == "x:y"

    data1.id['x'].label = 'z'
    assert selection_choices(state, 'combo') == "z:y"

    helper.remove_data(data1)

    assert selection_choices(state, 'combo') == ""
Exemplo n.º 21
0
class TestDendroClient():

    def setup_method(self, method):

        self.data = Data(parent=[4, 4, 5, 5, 5, -1],
                         height=[5, 4, 3, 2, 1, 0],
                         label='dendro')
        self.dc = DataCollection([self.data])
        self.hub = self.dc.hub
        self.client = DendroClient(self.dc, figure=FIGURE)
        EditSubsetMode().data_collection = self.dc

    def add_subset_via_hub(self):
        self.connect()
        self.client.add_layer(self.data)
        s = self.data.new_subset()
        return s

    def connect(self):
        self.client.register_to_hub(self.hub)
        self.dc.register_to_hub(self.hub)

    def click(self, x, y):
        roi = PointROI(x=x, y=y)
        self.client.apply_roi(roi)

    def test_data_present_after_adding(self):
        assert self.data not in self.client
        self.client.add_layer(self.data)
        assert self.data in self.client

    def test_add_data_adds_subsets(self):
        s1 = self.data.new_subset()
        self.client.add_layer(self.data)
        assert s1 in self.client

    def test_remove_data(self):

        self.client.add_layer(self.data)
        self.client.remove_layer(self.data)

        assert self.data not in self.client

    def test_remove_data_removes_subsets(self):
        s = self.data.new_subset()
        self.client.add_layer(self.data)

        self.client.remove_layer(self.data)
        assert s not in self.client

    def test_add_subset_hub(self):
        s = self.add_subset_via_hub()
        assert s in self.client

    def test_new_subset_autoadd(self):
        self.connect()
        self.client.add_layer(self.data)
        s = self.data.new_subset()
        assert s in self.client

    def test_remove_subset_hub(self):

        s = self.add_subset_via_hub()
        s.delete()

        assert s not in self.client

    def test_subset_sync(self):
        s = self.add_subset_via_hub()

        self.client._update_layer = MagicMock()
        s.style.color = 'blue'
        self.client._update_layer.assert_called_once_with(s)

    def test_data_sync(self):
        self.connect()
        self.client.add_layer(self.data)

        self.client._update_layer = MagicMock()
        self.data.style.color = 'blue'
        self.client._update_layer.assert_called_once_with(self.data)

    def test_data_remove(self):
        s = self.add_subset_via_hub()
        self.dc.remove(self.data)

        assert self.data not in self.dc
        assert self.data not in self.client
        assert s not in self.client

    def test_log(self):
        self.client.ylog = True
        assert self.client.axes.get_yscale() == 'log'

    def test_1d_data_required(self):
        d = Data(x=[[1, 2], [2, 3]])
        self.dc.append(d)
        self.client.add_layer(d)
        assert d not in self.client

    def test_apply_roi(self):
        self.client.add_layer(self.data)
        self.client.select_substruct = False

        self.click(0, 4)
        s = self.data.subsets[0]

        assert_array_equal(s.to_index_list(), [1])

        self.click(0, 3)
        assert_array_equal(s.to_index_list(), [1])

        self.click(0, 0)
        assert_array_equal(s.to_index_list(), [4])

        self.click(.75, 4)
        assert_array_equal(s.to_index_list(), [0])

        self.click(0, 10)
        assert_array_equal(s.to_index_list(), [])

    def test_apply_roi_children_select(self):
        self.client.select_substruct = True
        self.client.add_layer(self.data)

        self.click(.5, .5)
        s = self.data.subsets[0]

        assert_array_equal(s.to_index_list(), [0, 1, 4])

    def test_attribute_change_triggers_relayout(self):
        self.client.add_layer(self.data)

        l = self.client._layout
        self.client.height_attr = self.data.id['parent']
        assert self.client._layout is not l

        l = self.client._layout
        self.client.parent_attr = self.data.id['height']
        assert self.client._layout is not l

        l = self.client._layout
        self.client.order_attr = self.data.id['parent']
        assert self.client._layout is not l
Exemplo n.º 22
0
class TestDendroClient():
    def setup_method(self, method):

        self.data = Data(parent=[4, 4, 5, 5, 5, -1],
                         height=[5, 4, 3, 2, 1, 0],
                         label='dendro')
        self.dc = DataCollection([self.data])
        self.hub = self.dc.hub
        self.client = DendroClient(self.dc, figure=FIGURE)
        EditSubsetMode().data_collection = self.dc

    def add_subset_via_hub(self):
        self.connect()
        self.client.add_layer(self.data)
        s = self.data.new_subset()
        return s

    def connect(self):
        self.client.register_to_hub(self.hub)
        self.dc.register_to_hub(self.hub)

    def click(self, x, y):
        roi = PointROI(x=x, y=y)
        self.client.apply_roi(roi)

    def test_data_present_after_adding(self):
        assert self.data not in self.client
        self.client.add_layer(self.data)
        assert self.data in self.client

    def test_add_data_adds_subsets(self):
        s1 = self.data.new_subset()
        self.client.add_layer(self.data)
        assert s1 in self.client

    def test_remove_data(self):

        self.client.add_layer(self.data)
        self.client.remove_layer(self.data)

        assert self.data not in self.client

    def test_remove_data_removes_subsets(self):
        s = self.data.new_subset()
        self.client.add_layer(self.data)

        self.client.remove_layer(self.data)
        assert s not in self.client

    def test_add_subset_hub(self):
        s = self.add_subset_via_hub()
        assert s in self.client

    def test_new_subset_autoadd(self):
        self.connect()
        self.client.add_layer(self.data)
        s = self.data.new_subset()
        assert s in self.client

    def test_remove_subset_hub(self):

        s = self.add_subset_via_hub()
        s.delete()

        assert s not in self.client

    def test_subset_sync(self):
        s = self.add_subset_via_hub()

        self.client._update_layer = MagicMock()
        s.style.color = 'blue'
        self.client._update_layer.assert_called_once_with(s)

    def test_data_sync(self):
        self.connect()
        self.client.add_layer(self.data)

        self.client._update_layer = MagicMock()
        self.data.style.color = 'blue'
        self.client._update_layer.assert_called_once_with(self.data)

    def test_data_remove(self):
        s = self.add_subset_via_hub()
        self.dc.remove(self.data)

        assert self.data not in self.dc
        assert self.data not in self.client
        assert s not in self.client

    def test_log(self):
        self.client.ylog = True
        assert self.client.axes.get_yscale() == 'log'

    def test_1d_data_required(self):
        d = Data(x=[[1, 2], [2, 3]])
        self.dc.append(d)
        self.client.add_layer(d)
        assert d not in self.client

    def test_apply_roi(self):
        self.client.add_layer(self.data)
        self.client.select_substruct = False

        self.click(0, 4)
        s = self.data.subsets[0]

        assert_array_equal(s.to_index_list(), [1])

        self.click(0, 3)
        assert_array_equal(s.to_index_list(), [1])

        self.click(0, 0)
        assert_array_equal(s.to_index_list(), [4])

        self.click(.75, 4)
        assert_array_equal(s.to_index_list(), [0])

        self.click(0, 10)
        assert_array_equal(s.to_index_list(), [])

    def test_apply_roi_children_select(self):
        self.client.select_substruct = True
        self.client.add_layer(self.data)

        self.click(.5, .5)
        s = self.data.subsets[0]

        assert_array_equal(s.to_index_list(), [0, 1, 4])

    def test_attribute_change_triggers_relayout(self):
        self.client.add_layer(self.data)

        l = self.client._layout
        self.client.height_attr = self.data.id['parent']
        assert self.client._layout is not l

        l = self.client._layout
        self.client.parent_attr = self.data.id['height']
        assert self.client._layout is not l

        l = self.client._layout
        self.client.order_attr = self.data.id['parent']
        assert self.client._layout is not l
Exemplo n.º 23
0
class TestLinkEditor:

    def setup_method(self, method):

        self.data1 = Data(x=[1, 2, 3], y=[2, 3, 4], z=[6, 5, 4], label='data1')
        self.data2 = Data(a=[2, 3, 4], b=[4, 5, 4], c=[3, 4, 1], label='data2')
        self.data3 = Data(i=[5, 4, 3], j=[2, 2, 1], label='data3')

        self.data_collection = DataCollection([self.data1, self.data2, self.data3])

    def test_defaults(self):
        # Make sure the dialog opens and closes and check default settings.
        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None
        assert not link_widget.button_add_link.isEnabled()
        assert not link_widget.button_remove_link.isEnabled()

        link_widget.state.data1 = self.data2

        assert not link_widget.button_add_link.isEnabled()
        assert not link_widget.button_remove_link.isEnabled()

        link_widget.state.data2 = self.data1

        assert link_widget.button_add_link.isEnabled()
        assert link_widget.button_remove_link.isEnabled()

        dialog.accept()

        assert len(self.data_collection.external_links) == 0

    def test_defaults_two(self):
        # Make sure the dialog opens and closes and check default settings. With
        # two datasets, the datasets should be selected by default.
        self.data_collection.remove(self.data3)
        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2
        assert link_widget.button_add_link.isEnabled()
        assert link_widget.button_remove_link.isEnabled()
        dialog.accept()
        assert len(self.data_collection.external_links) == 0

    def test_ui_behavior(self):

        # This is a bit more detailed test that checks that things update
        # correctly as we change various settings

        app = get_qapp()

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        add_identity_link = get_action(link_widget, 'identity')
        add_lengths_volume_link = get_action(link_widget, 'lengths_to_volume')

        # At this point, there should be no links in the main list widget
        # and nothing on the right.
        assert link_widget.listsel_current_link.count() == 0
        assert link_widget.link_details.text() == ''
        assert link_widget.link_io.itemAt(0) is None

        # Let's add an identity link
        add_identity_link.trigger()

        # Ensure that all events get processed
        app.processEvents()

        # Now there should be one link in the main list and content in the
        # right hand panel.
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a'

        # Let's change the current components for the link
        link_widget.state.current_link.x = self.data1.id['y']
        link_widget.state.current_link.y = self.data2.id['b']

        # and make sure the UI gets updated
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # We now add another link of a different type
        add_lengths_volume_link.trigger()

        # Ensure that all events get processed
        app.processEvents()

        # and make sure the UI has updated
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Convert between linear measurements and volume'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a'

        # Now switch back to the first link
        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[0]

        # and make sure the UI updates and has preserved the correct settings
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # Next up, we try changing the data

        link_widget.state.data1 = self.data3

        # At this point there should be no links in the list

        assert link_widget.listsel_current_link.count() == 0
        assert link_widget.link_details.text() == ''
        assert link_widget.link_io.itemAt(0) is None

        # Add another identity link
        add_identity_link.trigger()

        # Ensure that all events get processed
        app.processEvents()

        # Now there should be one link in the main list
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'a'

        # Switch back to the original data
        link_widget.state.data1 = self.data1

        # And check the output is as before
        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == 'Link conceptually identical components'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'b'

        # Let's now remove this link
        link_widget.button_remove_link.click()

        # Ensure that all events get processed
        app.processEvents()

        # We should now see the lengths/volume link
        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Convert between linear measurements and volume'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(3, 1).widget().currentText() == 'z'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'a'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 2

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_from_ids()[1] is self.data1.id['y']
        assert links[0].get_from_ids()[2] is self.data1.id['z']
        assert links[0].get_to_id() is self.data2.id['a']

        assert isinstance(links[1], ComponentLink)
        assert links[1].get_from_ids()[0] is self.data3.id['i']
        assert links[1].get_to_id() is self.data2.id['a']

    def test_graph(self):

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        add_identity_link = get_action(link_widget, 'identity')

        graph = link_widget.graph_widget

        def click(node_or_edge):
            # We now simulate a selection - since we can't deterministically
            # figure out the exact pixel coordinates to use, we patch
            # 'find_object' to return the object we want to select.
            with patch.object(graph, 'find_object', return_value=node_or_edge):
                graph.mousePressEvent(None)

        def hover(node_or_edge):
            # Same as for select, we patch find_object
            with patch.object(graph, 'find_object', return_value=node_or_edge):
                graph.mouseMoveEvent(None)

        # To start with, no data should be selected
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # and the graph should have three nodes and no edges
        assert len(graph.nodes) == 3
        assert len(graph.edges) == 0

        click(graph.nodes[0])

        # Check that this has caused one dataset to be selected
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        # Click on the same node again and this should deselect the data
        # (but only once we move off from the node)

        click(graph.nodes[0])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        hover(None)

        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Select it again
        click(graph.nodes[0])

        # and now select another node too
        click(graph.nodes[1])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        assert len(graph.nodes) == 3
        assert len(graph.edges) == 0

        add_identity_link.trigger()

        assert len(graph.nodes) == 3
        assert len(graph.edges) == 1

        # Unselect and select another node
        click(graph.nodes[1])
        click(graph.nodes[2])

        # and check the data selections have been updated
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data3

        # Deselect it and move off
        click(graph.nodes[2])
        hover(None)

        # and the second dataset should now once again be None
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is None

        # Now change the data manually
        link_widget.state.data1 = self.data2
        link_widget.state.data2 = self.data3

        # and check that if we select the edge the datasets change back
        click(graph.edges[0])

        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        # Unselect and hover over nothing
        click(graph.edges[0])
        hover(None)
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Hover over the edge and the datasets should change back
        hover(graph.edges[0])
        assert link_widget.state.data1 is self.data1
        assert link_widget.state.data2 is self.data2

        # And check that clicking outside of nodes/edges deselects everything
        click(None)
        assert link_widget.state.data1 is None
        assert link_widget.state.data2 is None

        # Select a node, select another, then make sure that selecting a third
        # one will deselect the two original ones
        click(graph.nodes[0])
        click(graph.nodes[1])
        click(graph.nodes[2])
        assert link_widget.state.data1 is self.data3
        assert link_widget.state.data2 is None

        dialog.accept()

    def test_preexisting_links(self):

        # Check that things work properly if there are pre-existing links

        app = get_qapp()

        link1 = ComponentLink([self.data1.id['x']], self.data2.id['c'])

        def add(x, y):
            return x + y

        def double(x):
            return x * 2

        def halve(x):
            return x / 2

        link2 = ComponentLink([self.data2.id['a'], self.data2.id['b']], self.data3.id['j'], using=add)
        link3 = ComponentLink([self.data3.id['i']], self.data2.id['c'], using=double, inverse=halve)

        self.data_collection.add_link(link1)
        self.data_collection.add_link(link2)
        self.data_collection.add_link(link3)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        link_widget.state.data1 = self.data3

        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 6
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'a'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'b'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'j'

        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1]

        assert link_widget.listsel_current_link.count() == 2
        assert link_widget.link_details.text() == ''
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'i'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 3

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_to_id() is self.data2.id['c']
        assert links[0].get_using() is identity

        assert isinstance(links[1], ComponentLink)
        assert links[1].get_from_ids()[0] is self.data2.id['a']
        assert links[1].get_from_ids()[1] is self.data2.id['b']
        assert links[1].get_to_id() is self.data3.id['j']
        assert links[1].get_using() is add

        assert isinstance(links[2], ComponentLink)
        assert links[2].get_from_ids()[0] is self.data3.id['i']
        assert links[2].get_to_id() is self.data2.id['c']
        assert links[2].get_using() is double
        assert links[2].get_inverse() is halve

    def test_add_helper(self):

        app = get_qapp()

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        add_coordinate_link = get_action(link_widget, 'ICRS <-> Galactic')

        # Add a coordinate link
        add_coordinate_link.trigger()

        # Ensure that all events get processed
        app.processEvents()

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link ICRS and Galactic coordinates'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'a'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], ICRS_to_Galactic)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data1.id['y']
        assert links[0].cids2[0] is self.data2.id['a']
        assert links[0].cids2[1] is self.data2.id['b']

    def test_preexisting_helper(self):

        app = get_qapp()

        link1 = Galactic_to_FK5(cids1=[self.data1.id['x'], self.data1.id['y']],
                                cids2=[self.data2.id['c'], self.data2.id['b']])

        self.data_collection.add_link(link1)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.listsel_current_link.count() == 0

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 1
        assert link_widget.link_details.text() == 'Link Galactic and FK5 (J2000) Equatorial coordinates'
        assert non_empty_rows_count(get_link_io(link_widget)) == 7
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'y'
        assert get_link_io(link_widget).itemAtPosition(5, 1).widget().currentText() == 'c'
        assert get_link_io(link_widget).itemAtPosition(6, 1).widget().currentText() == 'b'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], Galactic_to_FK5)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data1.id['y']
        assert links[0].cids2[0] is self.data2.id['c']
        assert links[0].cids2[1] is self.data2.id['b']

    def test_cancel(self):

        # Make sure that changes aren't saved if dialog is cancelled
        # This is a bit more detailed test that checks that things update
        # correctly as we change various settings

        app = get_qapp()

        link1 = ComponentLink([self.data1.id['x']], self.data2.id['c'])

        self.data_collection.add_link(link1)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        link_widget.state.current_link.x = self.data1.id['y']

        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'y'

        add_identity_link = get_action(link_widget, 'identity')
        add_identity_link.trigger()

        assert link_widget.listsel_current_link.count() == 2

        dialog.reject()

        links = self.data_collection.external_links

        assert len(links) == 1

        assert isinstance(links[0], ComponentLink)
        assert links[0].get_from_ids()[0] is self.data1.id['x']
        assert links[0].get_to_id() is self.data2.id['c']

    def test_functional_link_collection(self):

        # Test that if we use a @link_helper in 'legacy' mode, i.e. with only
        # input labels, both datasets are available from the combos in the
        # link editor dialog. Also test the new-style @link_helper.

        app = get_qapp()

        def deg_arcsec(degree, arcsecond):
            return [ComponentLink([degree], arcsecond, using=lambda d: d * 3600),
                    ComponentLink([arcsecond], degree, using=lambda a: a / 3600)]

        # Old-style link helper

        helper1 = functional_link_collection(deg_arcsec, description='Legacy link',
                                             labels1=['deg', 'arcsec'], labels2=[])

        link1 = helper1(cids1=[self.data1.id['x'], self.data2.id['c']])

        self.data_collection.add_link(link1)

        # New-style link helper

        helper2 = functional_link_collection(deg_arcsec, description='New-style link',
                                             labels1=['deg'], labels2=['arcsec'])

        link2 = helper2(cids1=[self.data1.id['x']], cids2=[self.data2.id['c']])

        self.data_collection.add_link(link2)

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        assert link_widget.listsel_current_link.count() == 0

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.listsel_current_link.count() == 2

        assert link_widget.link_details.text() == 'Legacy link'
        assert non_empty_rows_count(get_link_io(link_widget)) == 4
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(2, 1).widget().currentText() == 'c'

        link_widget.state.current_link = type(link_widget.state).current_link.get_choices(link_widget.state)[1]

        assert link_widget.link_details.text() == 'New-style link'
        assert non_empty_rows_count(get_link_io(link_widget)) == 5
        assert get_link_io(link_widget).itemAtPosition(1, 1).widget().currentText() == 'x'
        assert get_link_io(link_widget).itemAtPosition(4, 1).widget().currentText() == 'c'

        dialog.accept()

        links = self.data_collection.external_links

        assert len(links) == 2

        assert isinstance(links[0], helper1)
        assert links[0].cids1[0] is self.data1.id['x']
        assert links[0].cids1[1] is self.data2.id['c']

        assert isinstance(links[1], helper2)
        assert links[1].cids1[0] is self.data1.id['x']
        assert links[1].cids2[0] is self.data2.id['c']

    def test_same_data(self):

        # Test that we can't set the same data twice

        app = get_qapp()

        dialog = LinkEditor(self.data_collection)
        dialog.show()
        link_widget = dialog.link_widget

        link_widget.state.data1 = self.data1
        link_widget.state.data2 = self.data2

        assert link_widget.state.data1 == self.data1
        assert link_widget.state.data2 == self.data2

        link_widget.state.data1 = self.data2

        assert link_widget.state.data1 == self.data2
        assert link_widget.state.data2 == self.data1

        link_widget.state.data2 = self.data2

        assert link_widget.state.data1 == self.data1
        assert link_widget.state.data2 == self.data2