Program Listing for File NodeReferenceObserverTest.cxx

Return to documentation for file (Testing/Cxx/NodeReferenceObserverTest.cxx)

// LayerDM includes
#include "vtkMRMLLayerDMObjectEventObserver.h"
#include "vtkMRMLLayerDMNodeReferenceObserver.h"

// Slicer includes
#include <vtkMRMLMarkupsFiducialDisplayNode.h>
#include <vtkMRMLMarkupsFiducialNode.h>
#include <vtkMRMLScene.h>

// VTK includes
#include <vtkSmartPointer.h>

// CTK includes
#include "vtkSlicerLayerDMLogic.h"

#include <ctkTest.h>

namespace
{
struct Spy
{

  void OnRefModifiedCall(vtkMRMLNode* fromNode, vtkMRMLNode* toNode, const std::string& role, int eventType)
  {
    calls.emplace_back(std::make_tuple(fromNode, toNode, role, eventType));
    callCount++;
  }

  bool WasCalled() const { return callCount > 0; }

  void Clear()
  {
    calls.clear();
    callCount = 0;
  }

  using CallT = std::tuple<vtkMRMLNode*, vtkMRMLNode*, std::string, int>;
  CallT GetCall(int iCall) { return calls[iCall]; }

  std::vector<CallT> calls;
  std::vector<CallT> removedCalls;
  int callCount{};
};

struct Test
{
  Test(const vtkSmartPointer<vtkMRMLScene>& inScene = nullptr)
    : scene{ inScene ? inScene : vtkSmartPointer<vtkMRMLScene>::New() }
  {
    obs->SetReferenceModifiedCallBack([this](vtkMRMLNode* fromNode, vtkMRMLNode* toNode, const std::string& role, int eventType)
                                      { spy.OnRefModifiedCall(fromNode, toNode, role, eventType); });
    obs->SetScene(scene);

    scene->AddNode(markups);
    scene->AddNode(d1);
    scene->AddNode(d2);
    scene->AddNode(d3);
  }

  vtkSmartPointer<vtkMRMLScene> scene;
  vtkNew<vtkMRMLLayerDMNodeReferenceObserver> obs;
  Spy spy;
  vtkNew<vtkMRMLMarkupsFiducialNode> markups;
  vtkNew<vtkMRMLMarkupsFiducialDisplayNode> d1;
  vtkNew<vtkMRMLMarkupsFiducialDisplayNode> d2;
  vtkNew<vtkMRMLMarkupsFiducialDisplayNode> d3;
};
} // namespace

class NodeReferenceObserverTester : public QObject
{
  Q_OBJECT

private slots:
  void testOnNodesAddedToSceneDoesntTriggerRefAddedRemoved() const
  {
    Test test;
    QCOMPARE(test.markups->GetScene(), test.scene);
    QCOMPARE(test.d1->GetScene(), test.scene);
    QCOMPARE(test.d2->GetScene(), test.scene);
    QCOMPARE(test.spy.callCount, 0);
  }

  void testTriggersRefAddedOnRefAdded() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    QCOMPARE(test.spy.callCount, 1);

    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceAddedEvent);
  }

  void testTriggersRefRemovedOnRefRemoved() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.spy.Clear();

    test.markups->SetAndObserveDisplayNodeID("");
    QCOMPARE(test.spy.callCount, 1);

    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceRemovedEvent);
  }

  void testTriggersAddedWhenAddingAndNotReplacingExistingRef() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.markups->AddAndObserveDisplayNodeID(test.d2->GetID());
    QCOMPARE(test.spy.callCount, 2);

    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceAddedEvent);

    auto [fromNode2, toNode2, role2, eventType2] = test.spy.GetCall(1);
    QCOMPARE(fromNode2, test.markups);
    QCOMPARE(toNode2, test.d2);
    QCOMPARE(role2, "display");
    QCOMPARE(eventType2, vtkMRMLLayerDMNodeReferenceObserver::ReferenceAddedEvent);
  }

  void testTriggersRemovedAndAddedOnReferenceModified() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.markups->AddAndObserveDisplayNodeID(test.d2->GetID());
    test.spy.Clear();

    test.markups->SetAndObserveNthDisplayNodeID(0, test.d3->GetID());
    QCOMPARE(test.spy.callCount, 2);

    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceRemovedEvent);

    auto [fromNode3, toNode3, role3, eventType3] = test.spy.GetCall(1);
    QCOMPARE(fromNode3, test.markups);
    QCOMPARE(toNode3, test.d3);
    QCOMPARE(role3, "display");
    QCOMPARE(eventType3, vtkMRMLLayerDMNodeReferenceObserver::ReferenceAddedEvent);
  }

  void testTriggersRemovedOnToNodeRemovedFromScene() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.spy.Clear();

    test.scene->RemoveNode(test.d1);
    QCOMPARE(test.spy.callCount, 1);
    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceRemovedEvent);
  }

  void testTriggersRemovedOnFromNodeRemovedFromScene() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.spy.Clear();

    test.scene->RemoveNode(test.markups);
    QCOMPARE(test.spy.callCount, 1);
    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceRemovedEvent);
  }

  void testTriggersRemovedOnSceneClear() const
  {
    Test test;
    test.markups->SetAndObserveDisplayNodeID(test.d1->GetID());
    test.spy.Clear();

    test.scene->Clear();
    QCOMPARE(test.spy.callCount, 1);
    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, test.markups);
    QCOMPARE(toNode, test.d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceRemovedEvent);
  }

  void testTriggersAddedWhenSettingAlreadyExistingScene() const
  {
    auto scene = vtkSmartPointer<vtkMRMLScene>::New();
    vtkSlicerLayerDMLogic::RegisterNodeIfNeeded<vtkMRMLMarkupsFiducialDisplayNode>(scene);
    auto markups = vtkMRMLMarkupsFiducialNode::SafeDownCast(scene->AddNode(vtkNew<vtkMRMLMarkupsFiducialNode>{}));
    markups->CreateDefaultDisplayNodes();

    auto d1 = vtkMRMLMarkupsFiducialDisplayNode::SafeDownCast(markups->GetDisplayNode());
    QVERIFY(d1);

    Test test(scene);
    QCOMPARE(test.spy.callCount, 1);
    auto [fromNode, toNode, role, eventType] = test.spy.GetCall(0);
    QCOMPARE(fromNode, markups);
    QCOMPARE(toNode, d1);
    QCOMPARE(role, "display");
    QCOMPARE(eventType, vtkMRMLLayerDMNodeReferenceObserver::ReferenceAddedEvent);
  }

  void testNodesRemovedFromSceneAreCorrectlyRemovedFromObserver()
  {
    auto scene = vtkSmartPointer<vtkMRMLScene>::New();
    vtkSlicerLayerDMLogic::RegisterNodeIfNeeded<vtkMRMLMarkupsFiducialDisplayNode>(scene);

    auto markups = vtkMRMLMarkupsFiducialNode::SafeDownCast(scene->AddNode(vtkNew<vtkMRMLMarkupsFiducialNode>{}));
    markups->CreateDefaultDisplayNodes();

    // Verify that the number of nodes in the observer are consistent with the number of nodes present in the scene
    Test test(scene);
    int nNodes = test.obs->GetNumberOfNodes();
    QCOMPARE(scene->GetNumberOfNodes(), nNodes);

    int nRefTo = test.obs->GetReferenceToSize();
    QVERIFY(nRefTo <= nNodes);

    int nRefFrom = test.obs->GetReferenceFromSize();
    QVERIFY(test.obs->GetReferenceFromSize() <= nNodes);

    // Verify that the references from / to the created markups is consistent with the current display node
    const auto refTo = test.obs->GetNodeToReferences(markups);
    QVERIFY(refTo.find({ markups->GetDisplayNode(), "display" }) != refTo.end());

    const auto refFrom = test.obs->GetNodeFromReferences(markups->GetDisplayNode());
    QVERIFY(refFrom.find({ markups, "display" }) != refFrom.end());

    // Remove the markups and its display node from the scene
    scene->RemoveNode(markups->GetDisplayNode());
    scene->RemoveNode(markups);

    // Expect the number of nodes tracked to have been reduced by 2
    QCOMPARE(scene->GetNumberOfNodes(), nNodes - 2);
    QCOMPARE(test.obs->GetNumberOfNodes(), nNodes - 2);
  }
};

CTK_TEST_MAIN(NodeReferenceObserverTest)

#include "NodeReferenceObserverTest.moc"