Repository: cognitedata/reveal
Pull request #5134: test(React-components): Unit test for command controller
Author: login: nilscognite
Labels: name: auto-update
description: Makes bulldozer automatically update this PR when there are changes to the target branch
name: risk-review-ongoing
description: Risk review is in progress
name: waiting-for-team
description: Waiting for the submitter or reviewer of the PR to take an action
Created: 2025-05-12T15:29:34Z | Merged: 2025-05-27T08:59:45Z

#### Type of change
![Test](https://img.shields.io/badge/Type-Test-blueviolet) 

## Description :pencil:

This is a unit test only.

I have added some doc in the top of the `CommandsController `class.

**Coverage:**

![image](https://github.com/user-attachments/assets/fc231757-e726-4fe1-93dc-cc75b71f95e4)



Diff:
diff --git a/react-components/src/architecture/base/renderTarget/CommandsController.test.ts b/react-components/src/architecture/base/renderTarget/CommandsController.test.ts
new file mode 100644
index 00000000000..46a8a555644
--- /dev/null
+++ b/react-components/src/architecture/base/renderTarget/CommandsController.test.ts
@@ -0,0 +1,493 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.calledTimes).toBe(1);
+      expect(deactivateTester1.calledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.calledTimes).toBe(1);
+      expect(deactivateTester1.calledTimes).toBe(1);
+      expect(activateTester2.calledTimes).toBe(1);
+      expect(deactivateTester2.calledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.calledTimes).toBe(0);
+      expect(updateTester2.calledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.calledTimes).toBe(1);
+      expect(updateTester2.calledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.calledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.calledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWheel when wheel event', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onHover when hover event', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onHoverMock = vi.fn();
+      mockTool.onHover = onHoverMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onHoverMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correct function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {}
+class MockTool2 extends BaseTool {}
+
+class MockCommand extends BaseCommand {
+  protected override get shortCutKey(): string | undefined {
+    return 'A';
+  }
+
+  protected override get shortCutKeyOnAlt(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnCtrl(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnShift(): boolean {
+    return true;
+  }
+}
+
+class UpdateTester {
+  public calledTimes = 0;
+
+  public constructor(command: BaseCommand, change?: symbol) {
+    const listener = (_command: BaseCommand, inputChange?: symbol): void => {
+      if (change === inputChange) {
+        this.calledTimes++;
+      }
+    };
+    command.addEventListener(listener);
+  }
+}
+
+function createKeyboardEvent(key: string, keydown = true): KeyboardEvent {
+  return new KeyboardEvent(keydown ? 'keydown' : 'keyup', {
+    key,
+    ctrlKey: true,
+    shiftKey: true,
+    altKey: true
+  });
+}
+
+function createDeleteKeyEvent(): KeyboardEvent {
+  return new KeyboardEvent('keydown', { key: 'Delete' });
+}
+
+function createEscapeKeyEvent(): KeyboardEvent {
+  return new KeyboardEvent('keydown', { key: 'Escape' });
+}
+
+function createClickEvent(): PointerEvent {
+  return new PointerEvent('click', { button: MOUSE.LEFT });
+}
+
+function createDoubleClickEvent(): PointerEvent {
+  return new PointerEvent('dblclick', { button: MOUSE.LEFT });
+}
+
+function createMouseHoverEvent(): PointerEvent {
+  return new PointerEvent('pointermove');
+}
+
+function createMouseMoveEvent(isLeftButton: boolean): PointerEvent {
+  return new PointerEvent('pointermove', {
+    button: isLeftButton ? MOUSE.LEFT : MOUSE.RIGHT
+  });
+}
+
+function createMouseDownEvent(isLeftButton: boolean): PointerEvent {
+  return new PointerEvent('pointerdown', {
+    button: isLeftButton ? MOUSE.LEFT : MOUSE.RIGHT
+  });
+}
+
+function createMouseUpEvent(isLeftButton: boolean): PointerEvent {
+  return new PointerEvent('pointerup', {
+    button: isLeftButton ? MOUSE.LEFT : MOUSE.RIGHT
+  });
+}
diff --git a/react-components/src/architecture/base/renderTarget/CommandsController.ts b/react-components/src/architecture/base/renderTarget/CommandsController.ts
index d99d0244ab5..4464b5903ac 100644
--- a/react-components/src/architecture/base/renderTarget/CommandsController.ts
+++ b/react-components/src/architecture/base/renderTarget/CommandsController.ts
@@ -9,6 +9,19 @@ import { type BaseCommand } from '../commands/BaseCommand';
 import { type Class, isInstanceOf } from '../domainObjectsHelpers/Class';
 import { type Signal, signal } from '@cognite/signals';
 
+/**
+ * The main purpose of the command controller is to give the correct event to the correct command,
+ * and maintain a list of all available commands in the system for updating and other actions.
+ * Note that the tool is only a specific type command, where only one can be active at the time.
+ * The active tool will receive all mouse events. It also maintain the default tool, which is
+ * activated at startup or if the current active tool is disabled.
+ *
+ * It extends the `PointerEvents` class to handle pointer-based interactions.
+ * In addition the class also facilitates event handling for keyboard
+ * and some other pointer events, as well as managing event listeners for a DOM element.
+ *
+ * @extends PointerEvents
+ */
 export class CommandsController extends PointerEvents {
   // ==================================================
   // INSTANCE FIELDS


Reviews:
user: login: haakonflatval-cognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T09:11:35Z
user: login: haakonflatval-cognite
body: A few comments, but otherwise looks good :+1: 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:41:35Z
user: login: haakonflatval-cognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:48:21Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:51:37Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:27:12Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:27:42Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:29:12Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:37:16Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:37:39Z
user: login: nilscognite
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:16:05Z
user: login: haakonflatval-cognite
body: LGTM :+1: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T08:56:45Z
user: login: cemreyavuz
body: risk review ok
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-26T08:56:06Z

Review comments:
diff_hunk: @@ -9,6 +9,19 @@ import { type BaseCommand } from '../commands/BaseCommand';
 import { type Class, isInstanceOf } from '../domainObjectsHelpers/Class';
 import { type Signal, signal } from '@cognite/signals';
 
+/**
+ * The main purpose of the command controller is to give the correct event to the correct command,
+ * and maintain a list of all available commands in the system for updating and other actions.
+ * Note that the tool is only a specific type command, where only one can be active at the time.
+ * The active tool will receive all mouse events. It also maintain the default tool, which is
+ * activates at startup or if the current active tool is disabled.
path: react-components/src/architecture/base/renderTarget/CommandsController.ts
user: login: haakonflatval-cognite
body: ```suggestion
 * activated at startup or if the current active tool is disabled.
```
created_at: 2025-05-13T09:11:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {
+  // Mock any methods or properties as needed
+}
+class MockTool2 extends BaseTool {}
+
+class MockCommand extends BaseCommand {
+  protected override get shortCutKey(): string | undefined {
+    return 'A';
+  }
+
+  protected override get shortCutKeyOnAlt(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnCtrl(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnShift(): boolean {
+    return true;
+  }
+}
+
+export class UpdateTester {
+  public CalledTimes = 0;
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: ```suggestion
  public calledTimes = 0;
```
created_at: 2025-05-13T10:16:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {
+  // Mock any methods or properties as needed
+}
+class MockTool2 extends BaseTool {}
+
+class MockCommand extends BaseCommand {
+  protected override get shortCutKey(): string | undefined {
+    return 'A';
+  }
+
+  protected override get shortCutKeyOnAlt(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnCtrl(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnShift(): boolean {
+    return true;
+  }
+}
+
+export class UpdateTester {
+  public CalledTimes = 0;
+
+  // Set isCalled to true if the change is detected
+  public constructor(command: BaseCommand, change?: symbol) {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self = this;
+    function listener(_command: BaseCommand, inputChange?: symbol): void {
+      if (change === inputChange) {
+        self.CalledTimes++;
+      }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: I think you would get rid of this alias if you declare `listener` as `const listener = (...): void => { ... }`, if you still want to keep this class
created_at: 2025-05-13T10:22:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: Not sure if it would be better, but have you considered using `vi.fn()` here? Then these lines would become e.g. 
```
const mockToolListener1 = vi.fn();
const mockToolListener2 = vi.fn();
mockTool1.addEventListener(mockToolListener1);
mockTool2.addEventListener(mockToolListener2);
```
Then you could check e.g. `expect(mockToolListener1).toHaveBeenCalledWith(expect.anything(), CommandChanges.active);`, or `expect(mockToolListener1.mock.calls[0]).toEqual([expect.anything(), CommandChanges.active]);`
created_at: 2025-05-13T10:24:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: Just curious - since this is called "Recursive", do we intend that e.g. the `mockCommand` should be a child of the `mockTool`?
created_at: 2025-05-13T10:26:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: ```suggestion
    test('Should call onWheel when WheelEvent', () => {
```
created_at: 2025-05-13T10:34:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: This seems to have the same name as the test above - should it be called something else?
created_at: 2025-05-13T10:36:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: This mock function may also be misnamed, from what I can see
created_at: 2025-05-13T10:36:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: ```suggestion
    test('Should call correct function on the correct tool on hover event', async () => {
```
The same goes for the rest of the tests in this section
created_at: 2025-05-13T10:37:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: Is it intentional that the first one is registered on `activeTool` and the second on `defaultTool`?
created_at: 2025-05-13T10:39:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: (Seems like it's a recurring pattern in the other tests in this section, but I must admit I still don't understand it completely :sweat_smile: )
created_at: 2025-05-13T10:40:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {
+  // Mock any methods or properties as needed
+}
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: Do we need this comment?
created_at: 2025-05-13T10:40:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: haakonflatval-cognite
body: Ok, think I got it :+1: 
created_at: 2025-05-13T10:48:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Commands are Recursive in general. So that particular function goes recursive. In this case it is not. A tool can not have children
created_at: 2025-05-13T10:51:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Fixed
created_at: 2025-05-13T11:27:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {
+  // Mock any methods or properties as needed
+}
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Fixed
created_at: 2025-05-13T11:27:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Fixed
created_at: 2025-05-13T11:29:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
+        await controller.onPointerDown(createMouseDownEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse drag', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDrag = mock;
+        } else {
+          defaultTool.onRightPointerDrag = mock;
+        }
+        await controller.onPointerDrag(createMouseMoveEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    test('Should call correction function on the correct tool on mouse up', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerUp = mock;
+        } else {
+          defaultTool.onRightPointerUp = mock;
+        }
+        await controller.onPointerUp(createMouseUpEvent(isLeftButton), isLeftButton);
+        expect(mock).toHaveBeenCalledTimes(1);
+      }
+    });
+  });
+
+  function createMockTool1(): MockTool1 {
+    const tool = new MockTool1();
+    tool.attach(renderTarget);
+    return tool;
+  }
+  function createMockTool2(): MockTool2 {
+    const tool = new MockTool2();
+    tool.attach(renderTarget);
+    return tool;
+  }
+});
+
+class MockTool1 extends BaseTool {
+  // Mock any methods or properties as needed
+}
+class MockTool2 extends BaseTool {}
+
+class MockCommand extends BaseCommand {
+  protected override get shortCutKey(): string | undefined {
+    return 'A';
+  }
+
+  protected override get shortCutKeyOnAlt(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnCtrl(): boolean {
+    return true;
+  }
+
+  protected override get shortCutKeyOnShift(): boolean {
+    return true;
+  }
+}
+
+export class UpdateTester {
+  public CalledTimes = 0;
+
+  // Set isCalled to true if the change is detected
+  public constructor(command: BaseCommand, change?: symbol) {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self = this;
+    function listener(_command: BaseCommand, inputChange?: symbol): void {
+      if (change === inputChange) {
+        self.CalledTimes++;
+      }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Fixed
created_at: 2025-05-13T11:37:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: This will give more verbose code!
created_at: 2025-05-13T11:37:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,498 @@
+/*!
+ * Copyright 2025 Cognite AS
+ */
+import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
+
+import { CommandsController } from './CommandsController';
+import { BaseTool } from '../commands/BaseTool';
+import { type RevealRenderTarget } from './RevealRenderTarget';
+import { createRenderTargetMock } from '../../../../tests/tests-utilities/fixtures/renderTarget';
+import { BaseCommand } from '../commands/BaseCommand';
+import { CommandChanges } from '../domainObjectsHelpers/CommandChanges';
+import { MOUSE } from 'three';
+
+describe(CommandsController.name, () => {
+  let renderTarget: RevealRenderTarget;
+  let controller: CommandsController;
+  let domElement: HTMLElement;
+
+  beforeEach(() => {
+    renderTarget = createRenderTargetMock();
+    domElement = document.createElement('div');
+    controller = new CommandsController(domElement);
+    controller.addEventListeners();
+  });
+
+  afterEach(() => {
+    controller.removeEventListeners();
+    controller.dispose();
+  });
+
+  test('Should be enabled', () => {
+    expect(controller.isEnabled).toBe(true);
+  });
+
+  describe('Test active, default and previous tool tool', () => {
+    test('Should set default tool', () => {
+      const mockTool = createMockTool1();
+      controller.setDefaultTool(undefined);
+      expect(controller.defaultTool).toBeUndefined();
+      controller.setDefaultTool(mockTool);
+      expect(controller.defaultTool).toBe(mockTool);
+    });
+
+    test('Should set default tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.setDefaultTool(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.activateDefaultTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.isDefaultToolActive).toBe(true);
+    });
+
+    test('Should set previous active tool as active', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+      controller.setActiveTool(mockTool1);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+      controller.setPreviousTool();
+      expect(controller.activeTool).toBe(mockTool1);
+    });
+
+    test('Should set active tool and check if it is updating', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      const activateTester1 = new UpdateTester(mockTool1, CommandChanges.active);
+      const deactivateTester1 = new UpdateTester(mockTool1, CommandChanges.deactive);
+      const activateTester2 = new UpdateTester(mockTool2, CommandChanges.active);
+      const deactivateTester2 = new UpdateTester(mockTool2, CommandChanges.deactive);
+
+      controller.setActiveTool(mockTool1);
+      expect(controller.activeTool).toBe(mockTool1);
+      expect(controller.activeToolSignal()).toBe(mockTool1);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(0);
+
+      controller.setActiveTool(mockTool2);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool2);
+
+      expect(activateTester1.CalledTimes).toBe(1);
+      expect(deactivateTester1.CalledTimes).toBe(1);
+      expect(activateTester2.CalledTimes).toBe(1);
+      expect(deactivateTester2.CalledTimes).toBe(0);
+    });
+
+    test('Should not changed active tool when undefined or the same tool is given', () => {
+      const mockTool = createMockTool1();
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(undefined);
+      expect(controller.activeTool).toBe(mockTool);
+
+      controller.setActiveTool(mockTool);
+      expect(controller.activeTool).toBe(mockTool);
+    });
+
+    test('Should activate by tool type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      controller.setActiveToolByType(MockTool1);
+      expect(controller.activeTool).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      for (let i = 0; i < 2; i++) {
+        controller.setActiveToolByType(MockTool1);
+        expect(controller.activeTool).toBe(mockTool1);
+
+        controller.setActiveToolByType(MockTool2);
+        expect(controller.activeTool).toBe(mockTool2);
+      }
+    });
+  });
+
+  describe('Test getters', () => {
+    test('Should get tool by type', () => {
+      const mockTool1 = createMockTool1();
+      const mockTool2 = createMockTool2();
+
+      expect(controller.getToolByType(MockTool1)).toBeUndefined();
+      expect(controller.getToolByType(MockTool2)).toBeUndefined();
+
+      controller.add(mockTool1);
+      controller.add(mockTool2);
+
+      expect(controller.getToolByType(MockTool1)).toBe(mockTool1);
+      expect(controller.getToolByType(MockTool2)).toBe(mockTool2);
+    });
+
+    test('Should get command by type', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBeUndefined();
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getCommandByTypeRecursive(MockTool1)).toBe(mockTool);
+      expect(controller.getCommandByTypeRecursive(MockCommand)).toBe(mockCommand);
+    });
+
+    test('Should get the existing and equal command', () => {
+      const mockTool = createMockTool1();
+      const mockCommand = new MockCommand();
+
+      expect(controller.getEqual(new MockCommand())).toBeUndefined();
+      expect(controller.getEqual(new MockTool1())).toBeUndefined();
+
+      controller.add(mockTool);
+      controller.add(mockCommand);
+
+      expect(controller.getEqual(new MockCommand())).toBe(mockCommand);
+      expect(controller.getEqual(new MockTool1())).toBe(mockTool);
+    });
+  });
+
+  describe('Test shortcuts', () => {
+    test('Should invoke on correct shortcut and not invoke when incorrect shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      for (const correct of [true, false]) {
+        const invokeMock = vi.fn();
+        mockCommand.invoke = invokeMock;
+
+        const event = createKeyboardEvent(correct ? 'A' : 'B');
+        expect(invokeMock).toHaveBeenCalledTimes(0);
+        controller.onKey(event, true); // Key down
+
+        const expectedTimes = correct ? 1 : 0;
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+        controller.onKey(event, false); // key up
+        expect(invokeMock).toHaveBeenCalledTimes(expectedTimes);
+      }
+    });
+
+    test('Should call onKey on the active tool when any shortcut is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onKeyMock = vi.fn();
+      mockTool.onKey = onKeyMock;
+
+      const event = createKeyboardEvent('A');
+      controller.onKey(event, true);
+      expect(onKeyMock).toHaveBeenCalledTimes(1);
+      controller.onKey(event, false);
+      expect(onKeyMock).toHaveBeenCalledTimes(2);
+    });
+
+    test('Should call onDeleteKey on the active tool when Delete key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onDeleteKeyMock = vi.fn();
+      mockTool.onDeleteKey = onDeleteKeyMock;
+
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createDeleteKeyEvent(), true);
+      expect(onDeleteKeyMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onEscapeKey on the active tool when Escape key is pressed', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onEscapeKeyMock = vi.fn();
+      mockTool.onEscapeKey = onEscapeKeyMock;
+
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(0);
+      controller.onKey(createEscapeKeyEvent(), true);
+      expect(onEscapeKeyMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test update', () => {
+    test('Should all commands when the command controller updates', () => {
+      const mockCommand = new MockCommand();
+      const mockTool = createMockTool1();
+      controller.add(mockCommand);
+      controller.add(mockTool);
+
+      const updateTester1 = new UpdateTester(mockCommand);
+      const updateTester2 = new UpdateTester(mockTool);
+
+      expect(updateTester1.CalledTimes).toBe(0);
+      expect(updateTester2.CalledTimes).toBe(0);
+      controller.update();
+      expect(updateTester1.CalledTimes).toBe(1);
+      expect(updateTester2.CalledTimes).toBe(1);
+    });
+
+    test('Should not update wnen disposed', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const updateTester = new UpdateTester(mockCommand);
+
+      expect(updateTester.CalledTimes).toBe(0);
+      controller.dispose();
+      controller.update();
+      expect(updateTester.CalledTimes).toBe(0);
+    });
+  });
+
+  describe('Test some event with dispatch the event on the dom element', () => {
+    test('Should invoke on correct shortcut', () => {
+      const mockCommand = new MockCommand();
+      controller.add(mockCommand);
+
+      const invokeMock = vi.fn();
+      mockCommand.invoke = invokeMock;
+
+      domElement.dispatchEvent(createKeyboardEvent('A'));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+      domElement.dispatchEvent(createKeyboardEvent('A', false));
+      expect(invokeMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should invoke on correct shortcut', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onFocusChangedMock = vi.fn();
+      mockTool.onFocusChanged = onFocusChangedMock;
+
+      domElement.dispatchEvent(new FocusEvent('focus'));
+      domElement.dispatchEvent(new FocusEvent('blur'));
+
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(1, true);
+      expect(onFocusChangedMock).toHaveBeenNthCalledWith(2, false);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onWheel = onWheelMock;
+
+      domElement.dispatchEvent(new WheelEvent('wheel'));
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call onWhen when WheelEvent', () => {
+      const mockTool = createMockTool1();
+      controller.setActiveTool(mockTool);
+
+      const onWheelMock = vi.fn();
+      mockTool.onHover = onWheelMock;
+
+      domElement.dispatchEvent(createMouseHoverEvent());
+      expect(onWheelMock).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Test some event with the event function at the controller itself', () => {
+    test('Should call correction function on the correct tool on hover event', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onHoverByDebounce = mock;
+      controller.onHover(createMouseHoverEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onClick = mock;
+      await controller.onClick(createClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on double click', async () => {
+      const activeTool = createMockTool1();
+      controller.setActiveTool(activeTool);
+
+      const mock = vi.fn();
+      activeTool.onDoubleClick = mock;
+      await controller.onDoubleClick(createDoubleClickEvent());
+      expect(mock).toHaveBeenCalledTimes(1);
+    });
+
+    test('Should call correction function on the correct tool on mouse down', async () => {
+      const defaultTool = createMockTool2();
+      const activeTool = createMockTool1();
+      controller.setDefaultTool(defaultTool);
+      controller.setActiveTool(activeTool);
+
+      for (const isLeftButton of [false, true]) {
+        const mock = vi.fn();
+        if (isLeftButton) {
+          activeTool.onLeftPointerDown = mock;
+        } else {
+          defaultTool.onRightPointerDown = mock;
+        }
path: react-components/src/architecture/base/renderTarget/CommandsController.test.ts
user: login: nilscognite
body: Left mouse events goes to the active tool
Right mouse events goes to the default tool, which is normally the navigation.
This ensure max flexibility
created_at: 2025-05-13T12:16:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-12T15:38:08Z
body: ## [Codecov](https://app.codecov.io/gh/cognitedata/reveal/pull/5134?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 58.08%. Comparing base [(`09f2b2e`)](https://app.codecov.io/gh/cognitedata/reveal/commit/09f2b2e544779a6ceebff269b5e6e6d6d438ec5b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata) to head [(`9e1a15b`)](https://app.codecov.io/gh/cognitedata/reveal/commit/9e1a15b401ca7ba91c0f40ee27075a69a67b8717?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5134      +/-   ##
==========================================
+ Coverage   57.92%   58.08%   +0.15%     
==========================================
  Files        1126     1126              
  Lines       81725    81717       -8     
  Branches     6368     6428      +60     
==========================================
+ Hits        47337    47463     +126     
+ Misses      34052    33918     -134     
  Partials      336      336              
```

| [Files with missing lines](https://app.codecov.io/gh/cognitedata/reveal/pull/5134?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata) | Coverage Δ | |
|---|---|---|
| [...chitecture/base/renderTarget/CommandsController.ts](https://app.codecov.io/gh/cognitedata/reveal/pull/5134?src=pr&el=tree&filepath=react-components%2Fsrc%2Farchitecture%2Fbase%2FrenderTarget%2FCommandsController.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata#diff-cmVhY3QtY29tcG9uZW50cy9zcmMvYXJjaGl0ZWN0dXJlL2Jhc2UvcmVuZGVyVGFyZ2V0L0NvbW1hbmRzQ29udHJvbGxlci50cw==) | `97.24% <ø> (+57.33%)` | :arrow_up: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/cognitedata/reveal/pull/5134/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cognitedata)

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: nilscognite
created_at: 2025-05-13T11:39:34Z
body: Here is changed according to Review 7a10dff904a923372c691fef34aaf9617292bacf
author_association: CONTRIBUTOR