diff --git a/frontend/src/components/Login.js b/frontend/src/components/Login.js index 4640a419..ba362b88 100644 --- a/frontend/src/components/Login.js +++ b/frontend/src/components/Login.js @@ -57,23 +57,27 @@ const Login = () => { return ( - {loading === false && Login} + {loading === false && Login to platform} {errors === true && ( Cannot log in with provided credentials )} {loading === false && (
- + setUsername(e.target.value)} + required /> - + + {" "} + {/* Added htmlFor value */} { return ( - {loading === false && Sign up} + {loading === false && Sign up for an account} {errors === true && ( Cannot sign up with provided credentials )} - + { /> { required /> - + { ).toBeInTheDocument(), ); }); + +// test("loads and displays fetched cases", async () => { +// render(); + +// await waitFor(() => +// expect(screen.getByText("Test case 1")).toBeInTheDocument(), +// ); +// expect(screen.getByText("Test case 2")).toBeInTheDocument(); +// }); + +// test("navigates to the correct path when a case is selected", async () => { +// render(); + +// await waitFor(() => +// expect(screen.getByText("Test case 1")).toBeInTheDocument(), +// ); + +// fireEvent.click(screen.getByPlaceholderText("Select or create a case")); +// fireEvent.click(screen.getByText("Test case 1")); + +// expect(mockedUsedNavigate).toHaveBeenCalledWith("/case/1"); +// }); + +// test("contains 'Create new case' option", async () => { +// render(); + +// await waitFor(() => +// expect(screen.getByText("Test case 1")).toBeInTheDocument(), +// ); + +// fireEvent.click(screen.getByPlaceholderText("Select or create a case")); + +// expect(screen.getByText("Create new case")).toBeInTheDocument(); +// }); diff --git a/frontend/src/components/tests/CreateGroup.test.js b/frontend/src/components/tests/CreateGroup.test.js index 82cef4b4..479a00df 100644 --- a/frontend/src/components/tests/CreateGroup.test.js +++ b/frontend/src/components/tests/CreateGroup.test.js @@ -4,6 +4,7 @@ import "regenerator-runtime/runtime"; import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; +import { fireEvent } from "@testing-library/react"; import React from "react"; import CreateGroup from "../CreateGroup.js"; @@ -19,3 +20,9 @@ test("renders group creator layer", () => { const button = screen.getByText("Create group"); expect(button).toBeInTheDocument(); }); + +global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve({}), + }), +); diff --git a/frontend/src/components/tests/Groups.test.js b/frontend/src/components/tests/Groups.test.js index 95c63cd5..0b1469c1 100644 --- a/frontend/src/components/tests/Groups.test.js +++ b/frontend/src/components/tests/Groups.test.js @@ -2,7 +2,7 @@ * @jest-environment jsdom */ import "regenerator-runtime/runtime"; -import { render, screen } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; import React from "react"; import Groups from "../Groups.js"; @@ -38,3 +38,43 @@ test("renders groups layer", () => { const text = screen.getByText("Groups you own"); expect(text).toBeInTheDocument(); }); + +test("renders owner groups", async () => { + render(); + const groupNames = await screen.findAllByText("Group 1"); + expect(groupNames[0]).toBeInTheDocument(); +}); + +test("renders member groups", async () => { + render(); + const memberGroupNames = await screen.findAllByText("Group 1"); + expect(memberGroupNames[0]).toBeInTheDocument(); +}); + +test("renders group creation button", () => { + render(); + const createButton = screen.getByRole("button", { + name: /create group/i, + }); + expect(createButton).toBeInTheDocument(); +}); + +test("renders manage members button for owned groups", async () => { + render(); + const manageButton = await screen.findByRole("button", { + name: /manage members/i, + }); + expect(manageButton).toBeInTheDocument(); +}); + +test("renders delete button for owned groups", async () => { + render(); + const deleteButton = await screen.findByRole("button", { name: /delete/i }); + expect(deleteButton).toBeInTheDocument(); +}); + +test("renders 'Groups you are member of' section", () => { + render(); + const text = screen.getByText("Groups you are member of"); + expect(text).toBeInTheDocument(); +}); diff --git a/frontend/src/components/tests/ItemCreator.test.js b/frontend/src/components/tests/ItemCreator.test.js index 057e6228..f2da8daa 100644 --- a/frontend/src/components/tests/ItemCreator.test.js +++ b/frontend/src/components/tests/ItemCreator.test.js @@ -1,7 +1,7 @@ /** * @jest-environment jsdom */ -import { render, screen } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; import React from "react"; import ItemCreator from "../ItemCreator.js"; @@ -11,3 +11,45 @@ test("renders item creator layer", () => { const textElement = screen.getByText("Create a new TopLevelNormativeGoal"); expect(textElement).toBeInTheDocument(); }); + +test("renders input fields correctly", () => { + render(); + const nameInput = screen.getByPlaceholderText("Name"); + const sdescInput = screen.getByPlaceholderText("Short description"); + const ldescInput = screen.getByPlaceholderText("Long description"); + const keywordsInput = screen.getByPlaceholderText( + "Keywords (comma-separated)", + ); + + expect(nameInput).toBeInTheDocument(); + expect(sdescInput).toBeInTheDocument(); + expect(ldescInput).toBeInTheDocument(); + expect(keywordsInput).toBeInTheDocument(); +}); + +test("updates input fields on change", () => { + render(); + const nameInput = screen.getByPlaceholderText("Name"); + const sdescInput = screen.getByPlaceholderText("Short description"); + + fireEvent.change(nameInput, { target: { value: "Updated name" } }); + fireEvent.change(sdescInput, { + target: { value: "Updated short description" }, + }); + + expect(nameInput.value).toBe("Updated name"); + expect(sdescInput.value).toBe("Updated short description"); +}); + +test("renders Evidence specific property", () => { + render(); + const urlInput = screen.getByPlaceholderText("www.some-evidence.com"); + fireEvent.change(urlInput, { target: { value: "https://updated.url" } }); + expect(urlInput.value).toBe("https://updated.url"); +}); + +test("renders submit button", () => { + render(); + const submitButton = screen.getByText("Submit"); + expect(submitButton).toBeInTheDocument(); +}); diff --git a/frontend/src/components/tests/ItemEditor.test.js b/frontend/src/components/tests/ItemEditor.test.js index b071d591..b0065035 100644 --- a/frontend/src/components/tests/ItemEditor.test.js +++ b/frontend/src/components/tests/ItemEditor.test.js @@ -1,7 +1,7 @@ /** * @jest-environment jsdom */ -import { render, screen, waitFor } from "@testing-library/react"; +import { render, screen, waitFor, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; import "regenerator-runtime/runtime"; import React from "react"; @@ -20,9 +20,40 @@ global.fetch = jest.fn(() => }), ); +beforeEach(() => { + jest.clearAllMocks(); +}); + test("renders item editor layer", async () => { render(); await waitFor(() => expect(screen.getByDisplayValue("Test goal")).toBeInTheDocument(), ); }); + +test("updates item properties correctly", async () => { + render(); + + await waitFor(() => screen.getByDisplayValue("Test goal")); + const nameInput = screen.getByDisplayValue("Test goal"); + const shortDescInput = screen.getByDisplayValue("short"); + const longDescInput = screen.getByDisplayValue("long"); + const keywordsInput = screen.getByDisplayValue("key"); + + fireEvent.change(nameInput, { target: { value: "Updated name" } }); + fireEvent.change(shortDescInput, { target: { value: "Updated short" } }); + fireEvent.change(longDescInput, { target: { value: "Updated long" } }); + fireEvent.change(keywordsInput, { target: { value: "Updated key" } }); + + expect(nameInput.value).toBe("Updated name"); + expect(shortDescInput.value).toBe("Updated short"); + expect(longDescInput.value).toBe("Updated long"); + expect(keywordsInput.value).toBe("Updated key"); +}); + +test("renders delete item button", async () => { + render(); + await waitFor(() => screen.getByText("Delete item")); + const deleteButton = screen.getByText("Delete item"); + expect(deleteButton).toBeInTheDocument(); +}); diff --git a/frontend/src/components/tests/ItemViewer.test.js b/frontend/src/components/tests/ItemViewer.test.js index 86be16db..ce06ac7d 100644 --- a/frontend/src/components/tests/ItemViewer.test.js +++ b/frontend/src/components/tests/ItemViewer.test.js @@ -6,7 +6,6 @@ import "@testing-library/jest-dom"; import "regenerator-runtime/runtime"; import { act } from "react-dom/test-utils"; import React from "react"; -import "@testing-library/jest-dom"; import ItemViewer from "../ItemViewer.js"; global.fetch = jest.fn(() => @@ -18,13 +17,64 @@ global.fetch = jest.fn(() => short_description: "Test short", long_description: "Test long", keywords: "Test keywords", + claim_type: "PropertyClaim test type", + URL: "https://test.url", }), }), ); -test("renders item viewer layer", () => { - act(() => { +beforeEach(() => { + jest.clearAllMocks(); +}); + +test("renders item viewer layer", async () => { + await act(async () => { render(); }); expect(screen.getByText("Name")).toBeInTheDocument(); }); + +test("renders item properties correctly", async () => { + await act(async () => { + render(); + }); + + const goalText = await screen.findByText("Test goal 1"); + expect(goalText).toBeInTheDocument(); + + const shortDescText = await screen.findByText("Test short"); + expect(shortDescText).toBeInTheDocument(); + + const longDescText = await screen.findByText("Test long"); + expect(longDescText).toBeInTheDocument(); + + const keywordText = await screen.findByText("Test keywords"); + expect(keywordText).toBeInTheDocument(); +}); + +test("renders PropertyClaim specific property", async () => { + await act(async () => { + render(); + }); + + const claimTypeText = await screen.findByText("PropertyClaim test type"); + expect(claimTypeText).toBeInTheDocument(); +}); + +test("renders Evidence specific property", async () => { + await act(async () => { + render(); + }); + + const urlText = await screen.findByText("https://test.url"); + expect(urlText).toBeInTheDocument(); +}); + +test("renders edit button in edit mode", async () => { + await act(async () => { + render(); + }); + + const editButton = screen.getByText("Edit"); + expect(editButton).toBeInTheDocument(); +}); diff --git a/frontend/src/components/tests/Login.test.js b/frontend/src/components/tests/Login.test.js index 08cf9830..cb5e0b67 100644 --- a/frontend/src/components/tests/Login.test.js +++ b/frontend/src/components/tests/Login.test.js @@ -1,14 +1,84 @@ /** * @jest-environment jsdom */ -import "regenerator-runtime/runtime"; -import { render, screen } from "@testing-library/react"; -import "@testing-library/jest-dom"; +import { render, screen, fireEvent } from "@testing-library/react"; import React from "react"; +import "@testing-library/jest-dom"; import Login from "../Login.js"; -test("renders login component", () => { +// Mock fetch and localStorage methods +global.fetch = jest.fn(() => + Promise.resolve({ json: () => Promise.resolve({}) }), +); + +Object.defineProperty(window, "localStorage", { + value: { + getItem: jest.fn(), + setItem: jest.fn(), + clear: jest.fn(), + }, + writable: true, +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +test("renders login component and checks elements", () => { + render(); + + // Check for registration text + const registrationText = screen.getByText("Not already registered?"); + expect(registrationText).toBeInTheDocument(); + + // Check for login heading + const loginHeading = screen.getByText("Login"); + expect(loginHeading).toBeInTheDocument(); + + // Check for input fields + const usernameInput = screen.getByLabelText("User name"); + expect(usernameInput).toBeInTheDocument(); + + const passwordInput = screen.getByLabelText("Password"); + expect(passwordInput).toBeInTheDocument(); + + // Check for login button + const loginButton = screen.getByText("Login"); + expect(loginButton).toBeInTheDocument(); + + // Check for signup button + const signupButton = screen.getByText("Sign-up"); + expect(signupButton).toBeInTheDocument(); +}); + +test("inputs are changeable", () => { + render(); + + const usernameInput = screen.getByLabelText("User name"); + const passwordInput = screen.getByLabelText("Password"); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "testpassword" } }); + + expect(usernameInput.value).toBe("testuser"); + expect(passwordInput.value).toBe("testpassword"); +}); + +test("on form submit, fetch is called", () => { + render(); + + const usernameInput = screen.getByLabelText("User name"); + const passwordInput = screen.getByLabelText("Password"); + const loginButton = screen.getByText("Login"); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "testpassword" } }); + fireEvent.click(loginButton); + + expect(fetch).toHaveBeenCalled(); +}); + +test("when user is already logged in, page redirects", () => { + global.localStorage.getItem = jest.fn(() => "test_token"); render(); - const text = screen.getByText("Not already registered?"); - expect(text).toBeInTheDocument(); }); diff --git a/frontend/src/components/tests/Mermaid.test.js b/frontend/src/components/tests/Mermaid.test.js index 5f25ef2b..a518d7e3 100644 --- a/frontend/src/components/tests/Mermaid.test.js +++ b/frontend/src/components/tests/Mermaid.test.js @@ -14,3 +14,60 @@ test("renders chart", async () => { expect(screen.getByText("Syntax error in graph")).toBeInTheDocument(), ); }); + +// jest.mock("mermaid", () => ({ +// initialize: jest.fn(), +// contentLoaded: jest.fn(), +// })); + +// test("renders chart markdown", () => { +// render(); +// expect(screen.getByText("graph TB; A[TestGoal];")).toBeInTheDocument(); +// }); + +// test("mermaid is initialized with correct parameters", () => { +// render(); +// expect(mermaid.initialize).toHaveBeenCalledWith({ +// theme: "base", +// logLevel: 1, +// securityLevel: "loose", +// flowchart: { +// useMaxWidth: true, +// htmlLabels: true, +// curve: "linear", +// }, +// themeVariables: { +// primaryColor: "#ffffff", +// nodeBorder: "#000000", +// defaultLinkColor: "#004990", +// fontFamily: "arial", +// }, +// }); +// }); + +// test("window callback is set", () => { +// const mockFunc = jest.fn(); +// render( +// , +// ); +// expect(window.callback).toEqual(mockFunc); +// }); + +// test("mermaid contentLoaded is called", () => { +// render(); +// expect(mermaid.contentLoaded).toHaveBeenCalled(); +// }); + +// test("SVG max-height is set to 100%", () => { +// const mockGetElementsByClassName = jest.spyOn(document, 'getElementsByClassName'); +// const mockStyle = {}; // This will store any styles set on the mock SVG +// mockGetElementsByClassName.mockReturnValue([{ +// childNodes: [{ style: mockStyle }], +// }]); + +// render(); + +// expect(mockStyle["max-height"]).toBe("100%"); + +// mockGetElementsByClassName.mockRestore(); +// }); diff --git a/frontend/src/components/tests/MermaidSyntax.test.js b/frontend/src/components/tests/MermaidSyntax.test.js index be986aa9..3eb82d5e 100644 --- a/frontend/src/components/tests/MermaidSyntax.test.js +++ b/frontend/src/components/tests/MermaidSyntax.test.js @@ -5,7 +5,16 @@ import { render, screen, waitFor } from "@testing-library/react"; import "regenerator-runtime/runtime"; import React from "react"; import "@testing-library/jest-dom"; -import { jsonToMermaid, sanitizeForMermaid } from "../utils.js"; +import { + getBaseURL, + removeArrayElement, + highlightNode, + removeHighlight, + splitCommaSeparatedString, + joinCommaSeparatedString, + jsonToMermaid, + sanitizeForMermaid, +} from "../utils.js"; test("Simple JSON translation", () => { let input = { @@ -54,3 +63,31 @@ test("jsonToMermaid sanitizes goal name", () => { let output = jsonToMermaid(input); expect(output.includes("test goal")); }); + +test("removeArrayElement removes correct element", () => { + const arr = [1, 2, 3, 4]; + removeArrayElement(arr, 3); + expect(arr).toEqual([1, 2, 4]); +}); + +test("highlightNode appends highlight class", () => { + const markdown = "test markdown"; + const highlighted = highlightNode(markdown, "Goal", 1); + expect(highlighted.endsWith("class Goal_1 classHighlighted;\n")).toBe(true); +}); + +test("removeHighlight removes the highlight class", () => { + const highlighted = "test markdown\nclass Goal_1 classHighlighted;\n"; + const result = removeHighlight(highlighted); + expect(result).toBe("test markdown"); +}); + +test("splitCommaSeparatedString splits correctly", () => { + const str = "a, b, c,"; + expect(splitCommaSeparatedString(str)).toEqual(["a", "b", "c"]); +}); + +test("joinCommaSeparatedString joins correctly", () => { + const arr = ["a", "b", "c"]; + expect(joinCommaSeparatedString(arr)).toBe("a,b,c"); +}); diff --git a/frontend/src/components/tests/ParentSelector.test.js b/frontend/src/components/tests/ParentSelector.test.js index 7307b15f..7fd79248 100644 --- a/frontend/src/components/tests/ParentSelector.test.js +++ b/frontend/src/components/tests/ParentSelector.test.js @@ -2,10 +2,11 @@ * @jest-environment jsdom */ import "regenerator-runtime/runtime"; -import { render, screen } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; import React from "react"; import ParentSelector from "../ParentSelector.js"; +global.window.scrollTo = jest.fn(); global.fetch = jest.fn(() => Promise.resolve({ @@ -15,13 +16,51 @@ global.fetch = jest.fn(() => id: 1, name: "PropertyClaim 1", }, + { + id: 2, + name: "PropertyClaim 2", + }, ]), }), ); +import { cleanup } from "@testing-library/react"; + +afterEach(cleanup); + test("renders parent selector layer", () => { localStorage.setItem("token", "dummy"); render(); const dropdown = screen.getByPlaceholderText("Choose a parent"); expect(dropdown).toBeInTheDocument(); }); + +test("renders dropdown with 'Choose a potential parent' placeholder for potential prop", () => { + render(); + const dropdown = screen.getByPlaceholderText("Choose a potential parent"); + expect(dropdown).toBeInTheDocument(); +}); + +test("renders options based on API response", async () => { + render(); + const dropdown = screen.getByPlaceholderText("Choose a parent"); + fireEvent.click(dropdown); + + const option1 = await screen.findByText("PropertyClaim 1"); + const option2 = await screen.findByText("PropertyClaim 2"); + expect(option1).toBeInTheDocument(); + expect(option2).toBeInTheDocument(); +}); + +// TODO: Get this test to work +// test("onChange updates selected value correctly", () => { +// const setValueMock = jest.fn(); +// render(); +// const dropdown = screen.getByPlaceholderText("Choose a parent"); +// fireEvent.click(dropdown); + +// const option = screen.findByText("PropertyClaim 1");; +// fireEvent.click(option); + +// expect(setValueMock).toHaveBeenCalledWith({ id: 1, name: "PropertyClaim 1" }); +// }); diff --git a/frontend/src/components/tests/Signup.test.js b/frontend/src/components/tests/Signup.test.js index 0b5815a4..f1b3871f 100644 --- a/frontend/src/components/tests/Signup.test.js +++ b/frontend/src/components/tests/Signup.test.js @@ -6,9 +6,51 @@ import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; import React from "react"; import Signup from "../Signup.js"; +import userEvent from "@testing-library/user-event"; test("renders signup component", () => { render(); - const text = screen.getByText("At least 8 characters"); - expect(text).toBeInTheDocument(); + const userNameField = screen.getByLabelText("User name"); + const passwordField = screen.getByLabelText("Password"); + const confirmPasswordField = screen.getByLabelText("Confirm password"); + expect(userNameField).toBeInTheDocument(); + expect(passwordField).toBeInTheDocument(); + expect(confirmPasswordField).toBeInTheDocument(); +}); + +test("renders signup button", () => { + render(); + const signupButton = screen.getByRole("button", { name: /sign up/i }); + expect(signupButton).toBeInTheDocument(); +}); + +test("renders password requirements", () => { + render(); + const passwordInfo = screen.getByText("At least 8 characters"); + expect(passwordInfo).toBeInTheDocument(); +}); + +test("renders error message on failed signup", async () => { + // Mock the fetch call to return an error + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + username: ["This username is already taken."], + // ... add other errors as necessary + }), + status: 400, // typically, errors like these would return a 400 status + }), + ); + + render(); + + const submitButton = screen.getByText("Sign up"); + userEvent.click(submitButton); + + // Since network requests are asynchronous, we need to wait for the error message to appear + const errorMessage = await screen.findByText( + "Cannot sign up with provided credentials", + ); + expect(errorMessage).toBeInTheDocument(); }); diff --git a/frontend/src/components/utils.js b/frontend/src/components/utils.js index 4d18df4f..5f6f2566 100644 --- a/frontend/src/components/utils.js +++ b/frontend/src/components/utils.js @@ -149,7 +149,11 @@ function removeHighlight(inputMarkdown) { // remove last line of markdown if it contains highlight let lines = inputMarkdown.split("\n"); let numLines = lines.length; - if (lines[numLines - 2].includes("classHighlighted")) { + if ( + numLines >= 2 && + lines[numLines - 2] && + lines[numLines - 2].includes("classHighlighted") + ) { lines.splice(numLines - 2, numLines - 1); inputMarkdown = lines.join("\n"); }