test(node-watcher): add unit tests to the node watcher adapter

Bug: twpowertools:226
Change-Id: I9571eb5e8300447f899924a8b6dfc01c1805e4a2
diff --git a/src/infrastructure/presentation/nodeWatcher/NodeWatcher.adapter.test.ts b/src/infrastructure/presentation/nodeWatcher/NodeWatcher.adapter.test.ts
new file mode 100644
index 0000000..82621f6
--- /dev/null
+++ b/src/infrastructure/presentation/nodeWatcher/NodeWatcher.adapter.test.ts
@@ -0,0 +1,202 @@
+import { beforeAll, describe, expect, it, jest } from '@jest/globals';
+import { waitFor } from '@testing-library/dom';
+import { NodeWatcherAdapter } from './NodeWatcher.adapter';
+import {
+  NodeMutationType,
+  NodeWatcherHandler,
+} from '../../../presentation/nodeWatcher/NodeWatcherHandler';
+
+describe('NodeWatcherAdapter', () => {
+  beforeAll(() => {
+    jest.resetAllMocks();
+  });
+
+  const createFakeHandler = () => {
+    return {
+      nodeFilter: jest.fn<NodeWatcherHandler['nodeFilter']>(),
+      onMutatedNode: jest.fn<NodeWatcherHandler['onMutatedNode']>(),
+    };
+  };
+
+  describe('Regarding start', () => {
+    it('should not throw an error when calling start', () => {
+      const sut = new NodeWatcherAdapter();
+
+      expect(() => sut.start()).not.toThrow();
+    });
+
+    it('should only call MutationObserver.prototype.observe 1 time after calling start 2 times', () => {
+      const sut = new NodeWatcherAdapter();
+
+      const observeSpy = jest.spyOn(MutationObserver.prototype, 'observe');
+
+      sut.start();
+      sut.start();
+
+      expect(observeSpy).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Regarding setHandler', () => {
+    it('should not throw an error when setting a handler', () => {
+      const sut = new NodeWatcherAdapter();
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      expect(() => sut.setHandler(key, handler)).not.toThrow();
+    });
+
+    it('should call onMutatedNode when watching the node for initial discovery and it already exists in the DOM when the handler is set', async () => {
+      const nodeTestId = 'test-node';
+
+      const key = 'handler';
+      const handler = {
+        ...createFakeHandler(),
+        initialDiscoverySelector: '[data-testid="test-node"]',
+      };
+      handler.nodeFilter.mockImplementation((nodeMutation) => {
+        return (
+          nodeMutation.node instanceof HTMLElement &&
+          nodeMutation.type === NodeMutationType.InitialDiscovery &&
+          nodeMutation.node.getAttribute('data-testid') === nodeTestId
+        );
+      });
+
+      const newNode = document.createElement('div');
+      newNode.setAttribute('data-testid', nodeTestId);
+      document.body.append(newNode);
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+
+      await waitFor(() => {
+        expect(handler.onMutatedNode).toHaveBeenCalledTimes(1);
+      });
+    });
+
+    it('should call onMutatedNode when watching for node creation and the watched node is created', async () => {
+      const nodeTestId = 'new-node';
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      handler.nodeFilter.mockImplementation((nodeMutation) => {
+        return (
+          nodeMutation.node instanceof HTMLElement &&
+          nodeMutation.type === NodeMutationType.NewNode &&
+          nodeMutation.node.getAttribute('data-testid') === nodeTestId
+        );
+      });
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+
+      const newNode = document.createElement('div');
+      newNode.setAttribute('data-testid', nodeTestId);
+      document.body.append(newNode);
+
+      await waitFor(() => {
+        expect(handler.onMutatedNode).toHaveBeenCalledTimes(1);
+      });
+    });
+
+    it('should call onMutatedNode when watching for node removal and the watched node is removed', async () => {
+      const nodeTestId = 'will-not-last-very-long';
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      handler.nodeFilter.mockImplementation((nodeMutation) => {
+        return (
+          nodeMutation.node instanceof HTMLElement &&
+          nodeMutation.type === NodeMutationType.RemovedNode &&
+          nodeMutation.node.getAttribute('data-testid') === nodeTestId
+        );
+      });
+
+      const node = document.createElement('div');
+      node.setAttribute('data-testid', nodeTestId);
+      document.body.append(node);
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+
+      node.remove();
+
+      await waitFor(() => {
+        expect(handler.onMutatedNode).toHaveBeenCalledTimes(1);
+      });
+    });
+
+    it('should not call nodeFilter when a mutation other than a node being added or removed is fired', async () => {
+      const nodeTestId = 'test-node';
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      handler.nodeFilter.mockImplementation((nodeMutation) => {
+        return (
+          nodeMutation.node instanceof HTMLElement &&
+          nodeMutation.type === NodeMutationType.RemovedNode &&
+          nodeMutation.node.getAttribute('data-testid') === nodeTestId
+        );
+      });
+
+      const node = document.createElement('div');
+      node.setAttribute('data-testid', nodeTestId);
+      document.body.append(node);
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+
+      node.setAttribute('data-someattribute', 'random-value');
+
+      await waitFor(() => {
+        expect(handler.nodeFilter).toHaveBeenCalledTimes(0);
+      });
+    });
+  });
+
+  describe('Regarding removeHandler', () => {
+    it('should not call nodeFilter nor onMutatedNode when a node changes but the handler has been removed', async () => {
+      const nodeTestId = 'test-node';
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      handler.nodeFilter.mockReturnValue(true);
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+      sut.removeHandler(key);
+
+      const node = document.createElement('div');
+      node.setAttribute('data-testid', nodeTestId);
+      document.body.append(node);
+
+      await waitFor(() => {
+        expect(handler.nodeFilter).toHaveBeenCalledTimes(0);
+        expect(handler.onMutatedNode).toHaveBeenCalledTimes(0);
+      });
+    });
+  });
+
+  describe('Regarding pause', () => {
+    it('should not call nodeFilter nor onMutatedNode when a node changes but the NodeWatcher has been paused', async () => {
+      const nodeTestId = 'test-node';
+
+      const key = 'handler';
+      const handler = createFakeHandler();
+      handler.nodeFilter.mockReturnValue(true);
+
+      const sut = new NodeWatcherAdapter();
+      sut.setHandler(key, handler);
+      sut.pause();
+
+      const node = document.createElement('div');
+      node.setAttribute('data-testid', nodeTestId);
+      document.body.append(node);
+
+      await waitFor(() => {
+        expect(handler.nodeFilter).toHaveBeenCalledTimes(0);
+        expect(handler.onMutatedNode).toHaveBeenCalledTimes(0);
+      });
+    });
+  });
+});