-
-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #411 from gaphor/fix-issue-with-copied-associations
Fix issue where copy/paste + delete of original association causes association ends to be removed
- Loading branch information
Showing
7 changed files
with
384 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
import pytest | ||
|
||
from gaphor import UML | ||
from gaphor.diagram.tests.fixtures import allow, connect, disconnect | ||
from gaphor.UML.classes.association import AssociationItem | ||
from gaphor.UML.classes.klass import ClassItem | ||
|
||
|
||
@pytest.fixture | ||
def create(diagram, element_factory): | ||
def _create(item_class, element_class=None): | ||
return diagram.create( | ||
item_class, | ||
subject=(element_factory.create(element_class) if element_class else None), | ||
) | ||
|
||
return _create | ||
|
||
|
||
@pytest.fixture | ||
def connected_association(create): | ||
asc = create(AssociationItem) | ||
c1 = create(ClassItem, UML.Class) | ||
c2 = create(ClassItem, UML.Class) | ||
|
||
connect(asc, asc.head, c1) | ||
assert asc.subject is None # no UML metaclass yet | ||
|
||
connect(asc, asc.tail, c2) | ||
assert asc.subject is not None | ||
|
||
return asc, c1, c2 | ||
|
||
|
||
@pytest.fixture | ||
def clone(create): | ||
def _clone(item): | ||
new = create(type(item)) | ||
new.subject = item.subject | ||
new.head_end.subject = item.head_end.subject | ||
new.tail_end.subject = item.tail_end.subject | ||
return new | ||
|
||
return _clone | ||
|
||
|
||
def get_connected(item, handle): | ||
cinfo = item.canvas.get_connection(handle) | ||
if cinfo: | ||
return cinfo.connected # type: ignore[no-any-return] # noqa: F723 | ||
return None | ||
|
||
|
||
def test_glue_to_class(connected_association): | ||
asc, c1, c2 = connected_association | ||
|
||
glued = allow(asc, asc.head, c1) | ||
assert glued | ||
|
||
connect(asc, asc.head, c1) | ||
|
||
glued = allow(asc, asc.tail, c2) | ||
assert glued | ||
|
||
|
||
def test_association_item_connect(connected_association, element_factory): | ||
asc, c1, c2 = connected_association | ||
|
||
# Diagram, Class *2, Property *2, Association, StyleSheet | ||
assert len(element_factory.lselect()) == 7 | ||
assert asc.head_end.subject is not None | ||
assert asc.tail_end.subject is not None | ||
|
||
|
||
def test_association_item_reconnect(connected_association, create): | ||
asc, c1, c2 = connected_association | ||
c3 = create(ClassItem, UML.Class) | ||
|
||
UML.model.set_navigability(asc.subject, asc.tail_end.subject, True) | ||
|
||
a = asc.subject | ||
|
||
connect(asc, asc.tail, c3) | ||
|
||
assert a is asc.subject | ||
ends = [p.type for p in asc.subject.memberEnd] | ||
assert c1.subject in ends | ||
assert c3.subject in ends | ||
assert c2.subject not in ends | ||
assert asc.tail_end.subject.navigability is True | ||
|
||
|
||
def test_disconnect_should_disconnect_model(connected_association): | ||
asc, c1, c2 = connected_association | ||
|
||
disconnect(asc, asc.head) | ||
disconnect(asc, asc.tail) | ||
assert c1 is not get_connected(asc, asc.head) | ||
assert c2 is not get_connected(asc, asc.tail) | ||
|
||
assert asc.subject | ||
assert len(asc.subject.memberEnd) == 2 | ||
assert asc.subject.memberEnd[0].type is None | ||
assert asc.subject.memberEnd[1].type is None | ||
|
||
|
||
def test_disconnect_of_second_association_should_leave_model_in_tact( | ||
connected_association, clone | ||
): | ||
asc, c1, c2 = connected_association | ||
new = clone(asc) | ||
|
||
disconnect(new, new.head) | ||
assert asc.subject.memberEnd[0].type is c1.subject | ||
assert asc.subject.memberEnd[1].type is c2.subject | ||
assert new.subject is asc.subject | ||
|
||
|
||
def test_disconnect_of_navigable_end_should_remove_owner_relationship( | ||
connected_association, | ||
): | ||
asc, c1, c2 = connected_association | ||
|
||
UML.model.set_navigability(asc.subject, asc.head_end.subject, True) | ||
|
||
assert asc.head_end.subject in c2.subject.ownedAttribute | ||
|
||
disconnect(asc, asc.head) | ||
|
||
assert asc.subject | ||
assert len(asc.subject.memberEnd) == 2 | ||
assert asc.subject.memberEnd[0].type is None | ||
assert asc.head_end.subject not in c2.subject.ownedAttribute | ||
assert asc.tail_end.subject not in c1.subject.ownedAttribute | ||
assert asc.head_end.subject.type is None | ||
assert asc.tail_end.subject.type is None | ||
|
||
|
||
def test_allow_reconnect_for_single_presentation(connected_association, create): | ||
asc, c1, c2 = connected_association | ||
c3 = create(ClassItem, UML.Class) | ||
|
||
assert allow(asc, asc.head, c3) | ||
|
||
|
||
def test_allow_reconnect_on_same_class_for_multiple_presentations( | ||
connected_association, clone, create | ||
): | ||
asc, c1, c2 = connected_association | ||
new = clone(asc) | ||
|
||
assert allow(new, new.head, c1) | ||
assert allow(new, new.tail, c2) | ||
|
||
|
||
def test_allow_reconnect_if_only_one_connected_presentations( | ||
connected_association, clone, create | ||
): | ||
asc, c1, c2 = connected_association | ||
clone(asc) | ||
|
||
c3 = create(ClassItem, UML.Class) | ||
|
||
assert allow(asc, asc.head, c3) | ||
|
||
|
||
def test_disallow_connect_if_already_connected_with_presentations( | ||
connected_association, clone, create | ||
): | ||
asc, c1, c2 = connected_association | ||
new = clone(asc) | ||
|
||
c3 = create(ClassItem, UML.Class) | ||
|
||
assert not allow(new, new.head, c3) | ||
|
||
|
||
def test_disallow_reconnect_if_multiple_connected_presentations( | ||
connected_association, clone, create | ||
): | ||
asc, c1, c2 = connected_association | ||
new = clone(asc) | ||
connect(new, new.head, c1) | ||
|
||
c3 = create(ClassItem, UML.Class) | ||
|
||
assert not allow(asc, asc.head, c3) | ||
|
||
|
||
def test_allow_both_ends_connected_to_the_same_class(create, clone): | ||
asc = create(AssociationItem) | ||
c1 = create(ClassItem, UML.Class) | ||
connect(asc, asc.head, c1) | ||
connect(asc, asc.tail, c1) | ||
|
||
new = clone(asc) | ||
c2 = create(ClassItem, UML.Class) | ||
assert allow(new, new.head, c1) | ||
assert allow(new, new.tail, c1) | ||
assert not allow(new, new.head, c2) | ||
assert not allow(new, new.tail, c2) |
Oops, something went wrong.