Revision control
++ // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest/QTest>
++ #include <Qt3DCore/qentity.h>
++ #include <Qt3DCore/private/qentity_p.h>
++ #include <Qt3DCore/qcomponent.h>
++ #include <QtCore/qscopedpointer.h>
++
++ using namespace Qt3DCore;
++
++ class tst_Entity : public QObject
++ {
++ Q_OBJECT
++ public:
++ tst_Entity() : QObject()
++ {
++ qRegisterMetaType<Qt3DCore::QNode*>();
++ }
++ ~tst_Entity() {}
++
++ private slots:
++ void constructionDestruction();
++
++ void addComponentSingleParentSingleAggregation();
++ void addComponentSingleParentSeveralAggregations();
++ void addComponentsSeveralParentsSingleAggregations();
++ void addComponentsSeveralParentsSeveralAggregations();
++
++ void retrieveSingleComponent();
++
++ void removeComponentSingleParentSingleAggregation();
++ void removeComponentSingleParentSeveralAggregations();
++ void removeComponentsSeveralParentsSingleAggreation();
++ void removeComponentsSeveralParentsSeveralAggregations();
++
++ void addSeveralTimesSameComponent();
++ void removeSeveralTimesSameComponent();
++
++ void checkComponentBookkeeping();
++ };
++
++ class MyQComponent : public Qt3DCore::QComponent
++ {
++ Q_OBJECT
++ public:
++ explicit MyQComponent(Qt3DCore::QNode *parent = nullptr)
++ : QComponent(parent)
++ {}
++ };
++
++ class MyQ2Component : public Qt3DCore::QComponent
++ {
++ Q_OBJECT
++ public:
++ explicit MyQ2Component(Qt3DCore::QNode *parent = 0)
++ : QComponent(parent)
++ {}
++ };
++
++ class MyEntity : public Qt3DCore::QEntity
++ {
++ public:
++ explicit MyEntity(Qt3DCore::QNode *parent = nullptr)
++ : QEntity(parent)
++ {}
++ };
++
++ void tst_Entity::constructionDestruction()
++ {
++ // GIVEN
++ QEntity *entity = nullptr;
++ // WHEN
++ entity = new QEntity;
++ // THEN
++ QVERIFY(entity != nullptr);
++
++ delete entity;
++
++ // GIVEN
++ QScopedPointer<QEntity> entity2(new QEntity);
++ // WHEN
++ entity2.reset(nullptr);
++ // THEN
++ // this should not crash
++ }
++
++ void tst_Entity::addComponentSingleParentSingleAggregation()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity(new QEntity());
++ MyQComponent *comp = new MyQComponent(entity.data());
++ QCoreApplication::processEvents();
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 0);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 0);
++
++ // WHEN
++ entity->addComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 1);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 1);
++ }
++
++ void tst_Entity::addComponentSingleParentSeveralAggregations()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity1.data());
++ QCoreApplication::processEvents();
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity1.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 3);
++ QCOMPARE(entity2->children().size(), 0);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++
++ // WHEN
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++ entity1->addComponent(comp3);
++
++ entity2->addComponent(comp1);
++ entity2->addComponent(comp2);
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity1.data());
++
++ QCOMPARE(entity1->components().size(), 3);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 3);
++ QCOMPARE(entity2->children().size(), 0);
++
++ QCOMPARE(comp1->entities().size(), 2);
++ QCOMPARE(comp2->entities().size(), 2);
++ QCOMPARE(comp3->entities().size(), 2);
++ }
++
++ void tst_Entity::addComponentsSeveralParentsSingleAggregations()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity2.data());
++ QCoreApplication::processEvents();
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++
++ // WHEN
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 2);
++ QCOMPARE(entity2->components().size(), 1);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 1);
++ QCOMPARE(comp2->entities().size(), 1);
++ QCOMPARE(comp3->entities().size(), 1);
++ }
++
++ void tst_Entity::addComponentsSeveralParentsSeveralAggregations()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity2.data());
++ QCoreApplication::processEvents();
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++
++ // WHEN
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++ entity1->addComponent(comp3);
++
++ entity2->addComponent(comp1);
++ entity2->addComponent(comp2);
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 3);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 2);
++ QCOMPARE(comp2->entities().size(), 2);
++ QCOMPARE(comp3->entities().size(), 2);
++ }
++
++ void tst_Entity::removeComponentSingleParentSingleAggregation()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity(new QEntity());
++ MyQComponent *comp = new MyQComponent(entity.data());
++ QCoreApplication::processEvents();
++ entity->addComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 1);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 1);
++
++ // WHEN
++ entity->removeComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 0);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 0);
++ }
++
++ void tst_Entity::removeComponentSingleParentSeveralAggregations()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity1.data());
++ QCoreApplication::processEvents();
++
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++ entity1->addComponent(comp3);
++
++ entity2->addComponent(comp1);
++ entity2->addComponent(comp2);
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity1.data());
++
++ QCOMPARE(entity1->components().size(), 3);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 3);
++ QCOMPARE(entity2->children().size(), 0);
++
++ QCOMPARE(comp1->entities().size(), 2);
++ QCOMPARE(comp2->entities().size(), 2);
++ QCOMPARE(comp3->entities().size(), 2);
++
++ // WHEN
++ entity1->removeComponent(comp1);
++ entity1->removeComponent(comp2);
++ entity1->removeComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity1.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 3);
++ QCOMPARE(entity2->children().size(), 0);
++
++ QCOMPARE(comp1->entities().size(), 1);
++ QCOMPARE(comp2->entities().size(), 1);
++ QCOMPARE(comp3->entities().size(), 1);
++
++ // WHEN
++ entity2->removeComponent(comp1);
++ entity2->removeComponent(comp2);
++ entity2->removeComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity1.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 3);
++ QCOMPARE(entity2->children().size(), 0);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++ }
++
++ void tst_Entity::removeComponentsSeveralParentsSingleAggreation()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity2.data());
++ QCoreApplication::processEvents();
++
++ // WHEN
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 2);
++ QCOMPARE(entity2->components().size(), 1);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 1);
++ QCOMPARE(comp2->entities().size(), 1);
++ QCOMPARE(comp3->entities().size(), 1);
++
++ // WHEN
++ entity1->removeComponent(comp1);
++ entity1->removeComponent(comp2);
++ entity2->removeComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++ }
++
++ void tst_Entity::removeComponentsSeveralParentsSeveralAggregations()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++ QScopedPointer<Qt3DCore::QEntity> entity2(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ MyQComponent *comp3 = new MyQComponent(entity2.data());
++ QCoreApplication::processEvents();
++
++ // WHEN
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++ entity1->addComponent(comp3);
++
++ entity2->addComponent(comp1);
++ entity2->addComponent(comp2);
++ entity2->addComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 3);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 2);
++ QCOMPARE(comp2->entities().size(), 2);
++ QCOMPARE(comp3->entities().size(), 2);
++
++ // WHEN
++ entity1->removeComponent(comp1);
++ entity1->removeComponent(comp2);
++ entity1->removeComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 3);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 1);
++ QCOMPARE(comp2->entities().size(), 1);
++ QCOMPARE(comp3->entities().size(), 1);
++
++ // WHEN
++ entity2->removeComponent(comp1);
++ entity2->removeComponent(comp2);
++ entity2->removeComponent(comp3);
++
++ // THEN
++ QVERIFY(comp1->parent() == entity1.data());
++ QVERIFY(comp2->parent() == entity1.data());
++ QVERIFY(comp3->parent() == entity2.data());
++
++ QCOMPARE(entity1->components().size(), 0);
++ QCOMPARE(entity2->components().size(), 0);
++
++ QCOMPARE(entity1->children().size(), 2);
++ QCOMPARE(entity2->children().size(), 1);
++
++ QCOMPARE(comp1->entities().size(), 0);
++ QCOMPARE(comp2->entities().size(), 0);
++ QCOMPARE(comp3->entities().size(), 0);
++ }
++
++ void tst_Entity::retrieveSingleComponent()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity1(new QEntity());
++
++ MyQComponent *comp1 = new MyQComponent(entity1.data());
++ MyQComponent *comp2 = new MyQComponent(entity1.data());
++ QCoreApplication::processEvents();
++ entity1->addComponent(comp1);
++ entity1->addComponent(comp2);
++
++ // WHEN
++ const QList<MyQComponent *> myQComponentsInEntity = entity1->componentsOfType<MyQComponent>();
++ const QList<MyQ2Component *> myQ2ComponentsInEntity = entity1->componentsOfType<MyQ2Component>();
++
++ // THEN
++ QVERIFY(myQComponentsInEntity.size() == 2);
++ QVERIFY(myQComponentsInEntity[0] == comp1);
++ QVERIFY(myQComponentsInEntity[1] == comp2);
++
++ QVERIFY(myQ2ComponentsInEntity.size() == 0);
++ }
++
++ void tst_Entity::addSeveralTimesSameComponent()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity(new QEntity());
++ MyQComponent *comp = new MyQComponent(entity.data());
++ QCoreApplication::processEvents();
++ entity->addComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 1);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 1);
++
++ // WHEN
++ entity->addComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 1);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 1);
++ }
++
++ void tst_Entity::removeSeveralTimesSameComponent()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> entity(new QEntity());
++ MyQComponent *comp = new MyQComponent(entity.data());
++ QCoreApplication::processEvents();
++ entity->addComponent(comp);
++ entity->removeComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 0);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 0);
++
++ // WHEN
++ entity->removeComponent(comp);
++
++ // THEN
++ QVERIFY(comp->parent() == entity.data());
++ QCOMPARE(entity->components().size(), 0);
++ QCOMPARE(entity->children().size(), 1);
++ QCOMPARE(comp->entities().size(), 0);
++ }
++
++ void tst_Entity::checkComponentBookkeeping()
++ {
++ // GIVEN
++ QScopedPointer<Qt3DCore::QEntity> rootEntity(new Qt3DCore::QEntity);
++ {
++ // WHEN
++ QScopedPointer<Qt3DCore::QComponent> comp(new MyQComponent(rootEntity.data()));
++ rootEntity->addComponent(comp.data());
++
++ // THEN
++ QCOMPARE(comp->parent(), rootEntity.data());
++ QCOMPARE(rootEntity->components().size(), 1);
++ }
++ // THEN (Should not crash and comp should be automatically removed)
++ QVERIFY(rootEntity->components().empty());
++
++ {
++ // WHEN
++ QScopedPointer<Qt3DCore::QEntity> someOtherEntity(new Qt3DCore::QEntity);
++ QScopedPointer<Qt3DCore::QComponent> comp(new MyQComponent(someOtherEntity.data()));
++ rootEntity->addComponent(comp.data());
++
++ // THEN
++ QCOMPARE(comp->parent(), someOtherEntity.data());
++ QCOMPARE(rootEntity->components().size(), 1);
++
++ // WHEN
++ int sigCount = 0;
++ QObject *sigSender = comp.data();
++ connect(comp.data(), &QComponent::removedFromEntity, this, [&sigCount, sigSender](QEntity *) {
++ QComponent *c = qobject_cast<QComponent *>(sigSender);
++ if (sigSender && c)
++ sigCount++; // test the sender is still a QComponent when signal is emitted
++ });
++
++ comp.reset();
++ rootEntity.reset();
++
++ // THEN (Should not crash when the comp is destroyed (tests for failed removal of destruction helper)
++ QCOMPARE(sigCount, 1);
++ }
++ }
++
++ Qt3DCore::QNodeId parentEntityId(Qt3DCore::QEntity *entity)
++ {
++ Qt3DCore::QEntityPrivate *d = static_cast<Qt3DCore::QEntityPrivate*>(Qt3DCore::QNodePrivate::get(entity));
++ return d->parentEntityId();
++ }
++
++ QTEST_MAIN(tst_Entity)
++
++ #include "tst_qentity.moc"