diff --git a/client/src/KeyframeTimeline/KeyframeTimeline.test.js b/client/src/KeyframeTimeline/KeyframeTimeline.test.js
new file mode 100644
index 0000000..cec6a23
--- /dev/null
+++ b/client/src/KeyframeTimeline/KeyframeTimeline.test.js
@@ -0,0 +1,53 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import '@testing-library/jest-dom';
+import KeyframeTimeline from "./index";
+
+jest.mock("./get-time-string", () => jest.fn((time) => `00:${String(time / 1000).padStart(2, '0')}`));
+
+
+describe("KeyframeTimeline", () => {
+ const mockOnChangeCurrentTime = jest.fn();
+
+ const setup = (props = {}) => {
+ const utils = render(
+
+ );
+ return { ...utils };
+ };
+
+ test("renders correctly", () => {
+ setup();
+ expect(screen.getAllByText("00:00")[0]).toBeInTheDocument();
+ expect(screen.getAllByText((content, element) => content.startsWith("00:0.25")).length).toBeGreaterThan(0);
+ expect(screen.getAllByText((content, element) => content.startsWith("00:0.75")).length).toBeGreaterThan(0);
+ });
+
+ test("displays keyframe markers", () => {
+ setup();
+ const keyframeMarker1 = screen.getByText("00:0.25");
+ const keyframeMarker2 = screen.getByText("00:0.75");
+
+ expect(keyframeMarker1).toBeInTheDocument();
+ expect(keyframeMarker2).toBeInTheDocument();
+ });
+
+ test("handles dragging the position cursor", () => {
+ setup();
+ const positionCursor = screen.getAllByText("00:00").find(element => element.style.cursor === "grab");
+
+ // Simulate mouse down and mouse move events
+ fireEvent.mouseDown(positionCursor, { clientX: 0 });
+ fireEvent.mouseMove(positionCursor, { clientX: 300 }); // Assuming the container width would be 600, so this moves to halfway
+ fireEvent.mouseUp(positionCursor, { clientX: 300 });
+
+ // The instant current time should now be updated
+ expect(mockOnChangeCurrentTime).toHaveBeenCalledWith(expect.any(Number));
+ });
+ });
\ No newline at end of file
diff --git a/client/src/KeyframesSelectorSidebarBox/KeyframesSelectorSidebarBox.test.js b/client/src/KeyframesSelectorSidebarBox/KeyframesSelectorSidebarBox.test.js
new file mode 100644
index 0000000..334fadd
--- /dev/null
+++ b/client/src/KeyframesSelectorSidebarBox/KeyframesSelectorSidebarBox.test.js
@@ -0,0 +1,82 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import '@testing-library/jest-dom';
+import KeyframesSelectorSidebarBox from "./index";
+import getTimeString from "../KeyframeTimeline/get-time-string";
+
+jest.mock("../KeyframeTimeline/get-time-string");
+jest.mock("react-i18next", () => ({
+ useTranslation: () => ({
+ t: (key) => key,
+ }),
+}));
+
+describe("KeyframesSelectorSidebarBox", () => {
+ const mockOnChangeVideoTime = jest.fn();
+ const mockOnDeleteKeyframe = jest.fn();
+
+ const setup = (props = {}) => {
+ const utils = render(
+
+ );
+ return { ...utils };
+ };
+
+ beforeEach(() => {
+ getTimeString.mockImplementation((time) => `00:${String(time / 10).padStart(2, '0')}`);
+ });
+
+ test("renders correctly", () => {
+ setup();
+
+ expect(screen.getByText(/00:10/)).toBeInTheDocument();
+ expect(screen.getByText(/00:30/)).toBeInTheDocument();
+ });
+
+ test("handles click to change video time", () => {
+ setup();
+
+ const keyframeRow = screen.getByText((content, element) => {
+ const hasText = (node) => node.textContent.includes("00:10");
+ const nodeHasText = hasText(element);
+ const childrenDontHaveText = Array.from(element.children).every(child => !hasText(child));
+ return nodeHasText && childrenDontHaveText;
+ }).closest("div");
+ fireEvent.click(keyframeRow);
+
+ expect(mockOnChangeVideoTime).toHaveBeenCalledWith(100);
+ });
+
+ test("handles click to delete keyframe", () => {
+ setup();
+
+ const deleteButton = screen.getAllByTestId("DeleteIcon")[0];
+ fireEvent.click(deleteButton);
+
+ expect(mockOnDeleteKeyframe).toHaveBeenCalledWith(100);
+ });
+
+ test("stops event propagation on delete", () => {
+ setup();
+
+ const keyframeRow = screen.getByText(/00:10/).closest(".keyframeRow");
+ expect(keyframeRow).toBeInTheDocument();
+
+ const deleteButton = keyframeRow.querySelector("[data-testid='DeleteIcon']");
+ expect(deleteButton).toBeInTheDocument();
+
+ const stopPropagationSpy = jest.spyOn(Event.prototype, "stopPropagation");
+ fireEvent.click(deleteButton);
+
+ expect(stopPropagationSpy).toHaveBeenCalled();
+
+ expect(mockOnChangeVideoTime).toHaveBeenCalled();
+ expect(mockOnDeleteKeyframe).toHaveBeenCalledWith(100);
+ });
+});
diff --git a/client/src/KeyframesSelectorSidebarBox/index.jsx b/client/src/KeyframesSelectorSidebarBox/index.jsx
index 2c5671c..f481d50 100644
--- a/client/src/KeyframesSelectorSidebarBox/index.jsx
+++ b/client/src/KeyframesSelectorSidebarBox/index.jsx
@@ -65,7 +65,7 @@ const KeyframesSelectorSidebarBox = ({
onChangeVideoTime(t)}
>