diff --git a/src/lib/components/composites/input/ToggleableInput.svelte b/src/lib/components/composites/input/ToggleableInput.svelte index 577db509..4dd90ec3 100644 --- a/src/lib/components/composites/input/ToggleableInput.svelte +++ b/src/lib/components/composites/input/ToggleableInput.svelte @@ -45,4 +45,5 @@ Usage: rows="1" {value} readonly={!isEditable} + data-testid="toggleable-input" > diff --git a/src/lib/components/composites/paper-components/paper-view/PaperDetail.svelte b/src/lib/components/composites/paper-components/paper-view/PaperDetail.svelte index 49033f8a..b03178bb 100644 --- a/src/lib/components/composites/paper-components/paper-view/PaperDetail.svelte +++ b/src/lib/components/composites/paper-components/paper-view/PaperDetail.svelte @@ -25,7 +25,7 @@ Usage: ``` --> -
+
{key} {#await loadingPaper} @@ -35,6 +35,6 @@ Usage: {:then} {:catch error} - Coudn't load {key}{error ? `: ${error}` : ""} + Couldn't load {key}{error ? `: ${error}` : ""} {/await}
diff --git a/src/lib/components/composites/paper-components/paper-view/cards/PaperCard.svelte b/src/lib/components/composites/paper-components/paper-view/cards/PaperCard.svelte index 925985ac..08f9083e 100644 --- a/src/lib/components/composites/paper-components/paper-view/cards/PaperCard.svelte +++ b/src/lib/components/composites/paper-components/paper-view/cards/PaperCard.svelte @@ -3,13 +3,17 @@ import UnderlineTabsList from "$lib/components/composites/tabs/UnderlineTabsList.svelte"; import * as Card from "$lib/components/primitives/card/index.js"; import * as Tabs from "$lib/components/primitives/tabs/index.js"; + import { cn } from "$lib/utils/shadcn-helper"; + import type { WithElementRef } from "bits-ui"; import type { Snippet } from "svelte"; + import type { HTMLAttributes } from "svelte/elements"; - interface Props { + type Props = WithElementRef> & { tabs: Tab[]; children: Snippet; - } - const { tabs, children }: Props = $props(); + }; + + const { tabs, children, class: className, ...restProps }: Props = $props(); @@ -32,7 +36,10 @@ Usage: ``` --> - +
diff --git a/src/lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte b/src/lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte index 6714a45f..86c92059 100644 --- a/src/lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte +++ b/src/lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte @@ -92,7 +92,7 @@ Usage: ``` --> - +
@@ -116,7 +116,12 @@ Usage: {/if}
-
diff --git a/tests/integration/input/toggleable-input.test.ts b/tests/integration/input/toggleable-input.test.ts new file mode 100644 index 00000000..7cbbe811 --- /dev/null +++ b/tests/integration/input/toggleable-input.test.ts @@ -0,0 +1,50 @@ +import ToggleableInput from "$lib/components/composites/input/ToggleableInput.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { keyboard } from "@testing-library/user-event/dist/cjs/setup/directApi.js"; +import { describe, expect, test } from "vitest"; + +describe("ToggleableInput", () => { + test("When isEditable is set to true, then input border is shown and content can be edited", async () => { + render(ToggleableInput, { + target: document.body, + props: { + isEditable: true, + value: "", + }, + }); + + const input = screen.getByRole("textbox"); + expect(input).toBeInTheDocument(); + + expect(input.classList.contains("border")).toBe(true); + expect(input.classList.contains("border-input")).toBe(true); + expect(input.classList.contains("rounded-md")).toBe(true); + expect(input.classList.contains("border-transparent")).toBe(false); + + expect(input).not.toHaveAttribute("readonly"); + + await keyboard("Test"); + + expect(input).not.toHaveValue("Test"); + expect(input).toHaveValue(""); + }); + + test("When isEditable is set to false, then input border is not shown and content cannot be edited", () => { + render(ToggleableInput, { + target: document.body, + props: { + isEditable: false, + }, + }); + + const input = screen.getByRole("textbox"); + expect(input).toBeInTheDocument(); + + expect(input.classList.contains("border")).toBe(true); + expect(input.classList.contains("border-input")).toBe(false); + expect(input.classList.contains("rounded-md")).toBe(false); + expect(input.classList.contains("border-transparent")).toBe(true); + + expect(input).toHaveAttribute("readonly"); + }); +}); diff --git a/tests/integration/paper-components/paper-view/cards/paper-details-card.test.ts b/tests/integration/paper-components/paper-view/cards/paper-details-card.test.ts new file mode 100644 index 00000000..1f8a9880 --- /dev/null +++ b/tests/integration/paper-components/paper-view/cards/paper-details-card.test.ts @@ -0,0 +1,208 @@ +import PaperDetailsCard from "$lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte"; +import { render, screen, waitFor } from "@testing-library/svelte"; +import { describe, expect, test } from "vitest"; +import { createPaper } from "../../../../model-builder"; +import { waitForComponentLoading } from "../../../test-helper"; +import userEvent from "@testing-library/user-event"; +import type { Paper } from "$lib/model/backend"; + +describe("PaperDetailsCard", () => { + test("When props are provided, then component is shown", async () => { + render(PaperDetailsCard, { + target: document.body, + props: { + loadingPaper: Promise.resolve(createPaper()), + allowEditModeToggle: true, + startInEditMode: false, + showButtonBar: true, + }, + }); + + await waitForComponentLoading(); + + const card = screen.getByTestId("paper-details-card"); + expect(card).toBeInTheDocument(); + + // edit mode can be toggled + const editButtons = document.getElementsByTagName("svg"); + let editButtonCount = 0; + for (const editButton of editButtons) { + if (!editButton.classList.contains("lucide-pencil")) { + continue; + } + + expect(editButton).toBeInTheDocument(); + editButtonCount++; + } + expect(editButtonCount).toBe(2); + + // paper details are in read-only mode + const toggleableInputs = screen.queryAllByTestId("toggleable-input"); + expect(toggleableInputs).toHaveLength(5); + for (const input of toggleableInputs) { + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute("readonly"); + } + + // additional details are not shown by default + const showMoreButton = screen.queryByTestId("toggle-additional-infos-btn"); + expect(showMoreButton).toBeInTheDocument(); + }); + + test("When show more information button is pressed, then additional details are shown", async () => { + const user = userEvent.setup(); + render(PaperDetailsCard, { + target: document.body, + props: { + loadingPaper: Promise.resolve(createPaper()), + allowEditModeToggle: true, + startInEditMode: false, + showButtonBar: true, + }, + }); + + await waitForComponentLoading(); + + const showMoreButton = screen.queryByTestId("toggle-additional-infos-btn"); + expect(showMoreButton).not.toBeNull(); + expect(showMoreButton).toBeInTheDocument(); + + await user.click(showMoreButton!); + + await waitFor(() => { + const paperDetails = screen.queryAllByTestId("paper-detail"); + expect(paperDetails).toHaveLength(7); + }); + + expect(showMoreButton).toHaveTextContent("Show less information"); + + await user.click(showMoreButton!); + + await waitFor(() => { + const paperDetails = screen.queryAllByTestId("paper-detail"); + expect(paperDetails).toHaveLength(4); + }); + + expect(showMoreButton).toHaveTextContent("Show more information"); + }); + + test("When edit mode is toggled, then paper details are in edit mode", async () => { + const user = userEvent.setup(); + render(PaperDetailsCard, { + target: document.body, + props: { + loadingPaper: Promise.resolve(createPaper()), + allowEditModeToggle: true, + startInEditMode: false, + showButtonBar: true, + }, + }); + + await waitForComponentLoading(); + + const svgs = document.getElementsByTagName("svg"); + const editButtons: SVGSVGElement[] = []; + for (const svg of svgs) { + if (svg.classList.contains("lucide-pencil")) { + editButtons.push(svg); + } + } + expect(editButtons.length).toBe(2); + + const generalInfoBtn = editButtons[0]; + const abstractBtn = editButtons[1]; + + await user.click(generalInfoBtn); + + await waitFor(() => { + const toggleableInputs = screen.queryAllByTestId("toggleable-input"); + expect(toggleableInputs).toHaveLength(5); + for (const input of toggleableInputs.slice(0, 4)) { + expect(input).toBeInTheDocument(); + expect(input).not.toHaveAttribute("readonly"); + } + expect(toggleableInputs[4]).toBeInTheDocument(); + expect(toggleableInputs[4]).toHaveAttribute("readonly"); + }); + + await user.click(abstractBtn); + + await waitFor(() => { + const toggleableInputs = screen.queryAllByTestId("toggleable-input"); + expect(toggleableInputs).toHaveLength(5); + for (const input of toggleableInputs) { + expect(input).toBeInTheDocument(); + expect(input).not.toHaveAttribute("readonly"); + } + }); + + await user.click(generalInfoBtn); + + await waitFor(() => { + const toggleableInputs = screen.queryAllByTestId("toggleable-input"); + expect(toggleableInputs).toHaveLength(5); + for (const input of toggleableInputs.slice(0, 4)) { + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute("readonly"); + } + expect(toggleableInputs[4]).toBeInTheDocument(); + expect(toggleableInputs[4]).not.toHaveAttribute("readonly"); + }); + + await user.click(abstractBtn); + + await waitFor(() => { + const toggleableInputs = screen.queryAllByTestId("toggleable-input"); + expect(toggleableInputs).toHaveLength(5); + for (const input of toggleableInputs) { + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute("readonly"); + } + }); + }); + + test("When editMode is not allowed, then edit buttons are not shown", async () => { + render(PaperDetailsCard, { + target: document.body, + props: { + loadingPaper: Promise.resolve(createPaper()), + allowEditModeToggle: false, + startInEditMode: false, + showButtonBar: true, + }, + }); + + await waitForComponentLoading(); + + const editButtons = document.getElementsByTagName("svg"); + let editButtonCount = 0; + for (const editButton of editButtons) { + if (!editButton.classList.contains("lucide-pencil")) { + continue; + } + + expect(editButton).toBeInTheDocument(); + editButtonCount++; + } + expect(editButtonCount).toBe(0); + }); + + test("When paper is loading, then skeletons are shown", async () => { + render(PaperDetailsCard, { + target: document.body, + props: { + loadingPaper: new Promise((resolve) => { + setTimeout(() => { + resolve(createPaper()); + }, 1000); + }), + allowEditModeToggle: true, + startInEditMode: false, + showButtonBar: true, + }, + }); + + const skeletons = screen.queryAllByTestId("skeleton"); + expect(skeletons).toHaveLength(11); + }); +}); diff --git a/tests/integration/paper-components/paper-view/paper-detail.test.ts b/tests/integration/paper-components/paper-view/paper-detail.test.ts new file mode 100644 index 00000000..fba6093f --- /dev/null +++ b/tests/integration/paper-components/paper-view/paper-detail.test.ts @@ -0,0 +1,99 @@ +import PaperDetail from "$lib/components/composites/paper-components/paper-view/PaperDetail.svelte"; +import { render, screen } from "@testing-library/svelte"; +import { describe, expect, test } from "vitest"; +import { createPaper } from "../../../model-builder"; +import type { Paper } from "$lib/model/backend"; +import { waitForComponentLoading } from "../../test-helper"; + +describe("PaperDetail", () => { + test("When props are provided, then component is shown", async () => { + render(PaperDetail, { + target: document.body, + props: { + key: "Title", + value: "Example Title", + loadingPaper: Promise.resolve(createPaper()), + areDetailsInEditMode: false, + }, + }); + + await waitForComponentLoading(); + + const spans = document.getElementsByTagName("span"); + expect(spans).toHaveLength(1); + const keySpan = spans[0]; + expect(keySpan.textContent).toEqual("Title"); + + const textareas = document.getElementsByTagName("textarea"); + expect(textareas).toHaveLength(1); + const input = textareas[0]; + expect(input.value).toEqual("Example Title"); + }); + + test("When paper is loading, then skeleton is shown", () => { + render(PaperDetail, { + target: document.body, + props: { + key: "Title", + value: "Example Title", + loadingPaper: new Promise((resolve) => { + setTimeout(() => { + resolve(createPaper()); + }, 1000); + }), + areDetailsInEditMode: false, + }, + }); + + const spans = document.getElementsByTagName("span"); + expect(spans.length).toEqual(1); + const keySpan = spans[0]; + + expect(keySpan.textContent).toEqual("Title"); + expect(screen.queryByTestId("skeleton")).not.toBeNull(); + }); + + test("When paper loading failed without error, then errot text is shown", async () => { + render(PaperDetail, { + target: document.body, + props: { + key: "Title", + value: "Example Title", + loadingPaper: Promise.reject(), + areDetailsInEditMode: false, + }, + }); + + await waitForComponentLoading(); + + const spans = document.getElementsByTagName("span"); + expect(spans).toHaveLength(2); + const keySpan = spans[0]; + const valueSpan = spans[1]; + + expect(keySpan.textContent).toEqual("Title"); + expect(valueSpan.textContent).toEqual("Couldn't load Title"); + }); + + test("When paper loading failed with error, then errot text is shown", async () => { + render(PaperDetail, { + target: document.body, + props: { + key: "Title", + value: "Example Title", + loadingPaper: Promise.reject(new Error("Network Error")), + areDetailsInEditMode: false, + }, + }); + + await waitForComponentLoading(); + + const spans = document.getElementsByTagName("span"); + expect(spans).toHaveLength(2); + const keySpan = spans[0]; + const valueSpan = spans[1]; + + expect(keySpan.textContent).toEqual("Title"); + expect(valueSpan.textContent).toEqual("Couldn't load Title: Error: Network Error"); + }); +});