From 35ba4b7979a7cfbb76176af6772da11f9729d519 Mon Sep 17 00:00:00 2001
From: Marius Friess <34072851+mariusfriess@users.noreply.github.com>
Date: Sun, 20 Aug 2023 22:49:37 +0200
Subject: [PATCH] Add unit tests for BrowserModule (#56)

* Add Browser tests

* fix lint error
---
 jest-all.json                       |   3 +-
 src/browser/browser.service.spec.ts | 124 ++++++++++++++++++++++++++++
 src/browser/browser.ts              | 116 +++++++++++++-------------
 3 files changed, 186 insertions(+), 57 deletions(-)
 create mode 100644 src/browser/browser.service.spec.ts

diff --git a/jest-all.json b/jest-all.json
index 369000d..24afbac 100644
--- a/jest-all.json
+++ b/jest-all.json
@@ -5,5 +5,6 @@
   "testRegex": ".(e2e-)?spec.ts$",
   "transform": {
     "^.+\\.(t|j)s$": "ts-jest"
-  }
+  },
+  "collectCoverageFrom": ["src/**/*.ts", "test/**/*.ts"]
 }
diff --git a/src/browser/browser.service.spec.ts b/src/browser/browser.service.spec.ts
new file mode 100644
index 0000000..fda7135
--- /dev/null
+++ b/src/browser/browser.service.spec.ts
@@ -0,0 +1,124 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { BrowserService } from './browser.service';
+import { Server } from 'socket.io';
+import { Channel } from '../channel/channel';
+
+describe('BrowserService', () => {
+  let service: BrowserService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      imports: [],
+      providers: [BrowserService],
+    }).compile();
+
+    service = module.get<BrowserService>(BrowserService);
+  });
+
+  afterEach(async () => {
+    await service.close();
+  });
+
+  /**
+   * Single test case: Verifies if the BrowserService is defined.
+   */
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('openWebsite', () => {
+    it('should create a browser context for a new channelId', async () => {
+      const channelId = 'test-channel-id';
+      const url = 'https://example.com';
+      const server = {
+        to: () => {
+          return {
+            emit: jest.fn(),
+          };
+        },
+      } as unknown as Server;
+
+      const peerId = await service.openWebsite(channelId, url, server);
+      expect(peerId).toBeDefined();
+      expect(service.getPeerId(channelId)).toEqual(peerId);
+    });
+  });
+
+  describe('browserInteraction', () => {
+    it('should execute the browser interaction functions', async () => {
+      const channelId = 'test-channel-id';
+      const url = 'https://example.com';
+      const server = {
+        to: () => {
+          return {
+            emit: jest.fn(),
+          };
+        },
+      } as unknown as Server;
+
+      const peerId = await service.openWebsite(channelId, url, server);
+      expect(peerId).toBeDefined();
+
+      await service.moveMouse(channelId, 0, 0);
+      await service.mouseDown(channelId);
+      await service.mouseUp(channelId);
+      await service.keyDown(channelId, 'a');
+      await service.keyUp(channelId, 'a');
+      await service.scroll(channelId, 0);
+      await service.navigateForward(channelId);
+      await service.closeBrowserContext(channelId);
+      expect(service.getPeerId(channelId)).toBeUndefined();
+    });
+  });
+
+  describe('getFromChannel', () => {
+    it('should return the browser context associated with a channel', async () => {
+      const channelId = 'test-channel-id';
+      const url = 'https://example.com';
+      const server = {
+        to: () => {
+          return {
+            emit: jest.fn(),
+          };
+        },
+      } as unknown as Server;
+
+      const peerId = await service.openWebsite(channelId, url, server);
+      expect(peerId).toBeDefined();
+
+      const channel = {
+        id: channelId,
+      } as Channel;
+
+      const browser = service.getFromChannel(channel);
+      expect(browser).toBeDefined();
+    });
+  });
+
+  describe('closeBrowserContext', () => {
+    it('should close the browser context associated with a channel', async () => {
+      const channelId = 'test-channel-id';
+      const url = 'https://example.com';
+      const server = {
+        to: () => {
+          return {
+            emit: jest.fn(),
+          };
+        },
+      } as unknown as Server;
+
+      const peerId = await service.openWebsite(channelId, url, server);
+      expect(peerId).toBeDefined();
+
+      const channel = {
+        id: channelId,
+      } as Channel;
+
+      const browser = service.getFromChannel(channel);
+      expect(browser).toBeDefined();
+      await service.closeBrowserContext(channelId);
+      const browser2 = service.getFromChannel(channel);
+      expect(browser2).toBeNull();
+    });
+  });
+});
diff --git a/src/browser/browser.ts b/src/browser/browser.ts
index 05a9702..199a4b5 100644
--- a/src/browser/browser.ts
+++ b/src/browser/browser.ts
@@ -21,6 +21,8 @@ export class Browser {
    *
    * @param browser - The Puppeteer browser instance.
    * @param url - The initial URL to open.
+   * @param server - The Socket.IO server instance.
+   * @param channelId - The ID of the channel.
    */
   constructor(
     private browser: PuppeteerBrowser,
@@ -38,7 +40,7 @@ export class Browser {
     LOGGER.debug(`Opening page with url: ${this.url}`);
     this.page = await this.browser.newPage();
 
-    await installMouseHelper(this.page);
+    await this.installMouseHelper(this.page);
 
     await this.page.goto(this.url);
     await this.page.setViewport({ width: 1920, height: 1080 });
@@ -65,6 +67,7 @@ export class Browser {
 
     await this.page.bringToFront();
 
+    /* istanbul ignore next */
     const id = await extensionPage.evaluate(async () => {
       // eslint-disable-next-line @typescript-eslint/ban-ts-comment
       // @ts-ignore
@@ -167,25 +170,25 @@ export class Browser {
       await this.browser.close();
     }
   }
-}
 
-/**
- * Helper function to install mouse tracking for puppeteer pages.
- *
- * @param page - The puppeteer page to install the mouse tracking on.
- */
-async function installMouseHelper(page) {
-  await page.evaluateOnNewDocument(() => {
-    // Install mouse helper only for top-level frame.
-    if (window !== window.parent) {
-      return;
-    }
-    window.addEventListener(
-      'DOMContentLoaded',
-      () => {
-        const box = document.createElement('puppeteer-mouse-pointer');
-        const styleElement = document.createElement('style');
-        styleElement.innerHTML = `
+  /**
+   * Helper function to install mouse tracking for puppeteer pages.
+   *
+   * @param page - The puppeteer page to install the mouse tracking on.
+   */
+  private async installMouseHelper(page) {
+    await page.evaluateOnNewDocument(() => {
+      // Install mouse helper only for top-level frame.
+      if (window !== window.parent) {
+        return;
+      }
+      // istanbul ignore next
+      window.addEventListener(
+        'DOMContentLoaded',
+        () => {
+          const box = document.createElement('puppeteer-mouse-pointer');
+          const styleElement = document.createElement('style');
+          styleElement.innerHTML = `
         puppeteer-mouse-pointer {
           pointer-events: none;
           position: absolute;
@@ -222,43 +225,44 @@ async function installMouseHelper(page) {
           border-color: rgba(0,255,0,0.9);
         }
       `;
-        document.head.appendChild(styleElement);
-        document.body.appendChild(box);
-        document.addEventListener(
-          'mousemove',
-          (event) => {
-            box.style.left = event.pageX + 'px';
-            box.style.top = event.pageY + 'px';
-            updateButtons(event.buttons);
-          },
-          true,
-        );
-        document.addEventListener(
-          'mousedown',
-          (event) => {
-            updateButtons(event.buttons);
-            box.classList.add('button-' + event.which);
-          },
-          true,
-        );
-        document.addEventListener(
-          'mouseup',
-          (event) => {
-            updateButtons(event.buttons);
-            box.classList.remove('button-' + event.which);
-          },
-          true,
-        );
+          document.head.appendChild(styleElement);
+          document.body.appendChild(box);
+          document.addEventListener(
+            'mousemove',
+            (event) => {
+              box.style.left = event.pageX + 'px';
+              box.style.top = event.pageY + 'px';
+              updateButtons(event.buttons);
+            },
+            true,
+          );
+          document.addEventListener(
+            'mousedown',
+            (event) => {
+              updateButtons(event.buttons);
+              box.classList.add('button-' + event.which);
+            },
+            true,
+          );
+          document.addEventListener(
+            'mouseup',
+            (event) => {
+              updateButtons(event.buttons);
+              box.classList.remove('button-' + event.which);
+            },
+            true,
+          );
 
-        function updateButtons(buttons) {
-          for (let i = 0; i < 5; i++) {
-            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-            // @ts-ignore
-            box.classList.toggle('button-' + i, buttons & (1 << i));
+          function updateButtons(buttons) {
+            for (let i = 0; i < 5; i++) {
+              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+              // @ts-ignore
+              box.classList.toggle('button-' + i, buttons & (1 << i));
+            }
           }
-        }
-      },
-      false,
-    );
-  });
+        },
+        false,
+      );
+    });
+  }
 }
-- 
GitLab