/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import React from "react"; import { render, screen, act, fireEvent, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Router } from "react-router-dom"; import { createMemoryHistory } from "history"; import { DashboardState } from "../../models"; import ViewDashboardAdmin from "../ViewDashboardAdmin"; import * as hooks from "../../hooks"; import BackendService from "../../services/BackendService"; jest.mock("../../hooks"); jest.mock("../../services/BackendService"); const dummyDashboard = { id: "123", name: "AWS Dashboard", topicAreaId: "abc", topicAreaName: "Bananas", parentDashboardId: "123", description: "Some description", updatedAt: "2021-01-19T18:27:00Z", updatedBy: "johndoe", createdBy: "test user", state: DashboardState.Published, version: 1, widgets: [ { id: "0828b98e", dashboardId: "123", name: "Column chart: Average class size", widgetType: "Chart", order: 5, updatedAt: "2022-09-13T05:27:20.333Z", showTitle: true, section: "", content: { summary: "A column chart ...", fileName: "Average class size.csv", sortByDesc: false, title: "Column chart: Average class size", significantDigitLabels: false, dataLabels: false, s3Key: { raw: "672f377a-1def-4308-ab80-a2b15b0d91a9.csv", json: "9ac17541-9ba5-4fac-af5c-401f33893195.json", }, summaryBelow: false, horizontalScroll: false, showTotal: true, chartType: "ColumnChart", datasetId: "a8bec0ec-9fcb-4ef1-ba11-2e027cdb44a8", computePercentages: false, datasetType: "StaticDataset", columnsMetadata: [], }, }, { id: "ebf83d58-c226-4f82-aad6-7a18d32ac931", dashboardId: "a2eb7bec-a8fc-4055-9d49-669dd178e540", name: "Sections: Grouping related content items together ", widgetType: "Section", order: 6, updatedAt: "2022-09-13T05:27:20.333Z", showTitle: true, section: "", content: { summary: "A section ...", horizontally: false, title: "Sections: Grouping related content items together ", showWithTabs: true, widgetIds: ["3cd7a776-3240-4155-b479-60e49aaae1ce"], }, }, { id: "3cd7a776-3240-4155-b479-60e49aaae1ce", dashboardId: "a2eb7bec-a8fc-4055-9d49-669dd178e540", name: "Donut chart: Devices used to access service", widgetType: "Chart", order: 7, updatedAt: "2022-09-13T05:27:20.333Z", showTitle: true, section: "ebf83d58-c226-4f82-aad6-7a18d32ac931", content: { summary: "A donut chart...", fileName: "Devices used to access service.csv", sortByDesc: false, title: "Donut chart: Devices used to access service", significantDigitLabels: false, dataLabels: false, s3Key: { raw: "b1dc2e7f-aa52-472b-8fa5-d06a3830d764.csv", json: "29716b9c-6f30-4c80-a4a2-3f12bd5d6e71.json", }, summaryBelow: false, showTotal: false, chartType: "DonutChart", datasetId: "fde387fd-ea63-4565-88d3-b28c4474912b", computePercentages: false, datasetType: "StaticDataset", columnsMetadata: [ { numberType: "Percentage", hidden: false, columnName: "Usage", dataType: "Number", }, ], }, }, ], }; beforeEach(() => { // IntersectionObserver isn't available in test environment const mockIntersectionObserver = jest.fn(); mockIntersectionObserver.mockReturnValue({ observe: () => null, unobserve: () => null, disconnect: () => null, }); window.IntersectionObserver = mockIntersectionObserver; }); describe("general dashboard details", () => { beforeEach(() => { render(, { wrapper: MemoryRouter, }); }); test("renders dashboard title", () => { expect(screen.getByRole("heading", { name: "My AWS Dashboard" })).toBeInTheDocument(); }); test("renders dashboard topic area", () => { expect(screen.getByText("Bananas")).toBeInTheDocument(); }); test("renders dashboard description", () => { expect(screen.getByText("Some description")).toBeInTheDocument(); }); test("renders a back link to homepage", async () => { expect(screen.getByRole("link", { name: "Dashboards" })).toBeInTheDocument(); }); }); describe("dashboard in draft", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Draft const draft = { ...dummyDashboard, state: DashboardState.Draft }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: draft, }); render(, { wrapper: MemoryRouter, }); }); test("renders a close preview button", async () => { expect(screen.getByRole("button", { name: "Close Preview" })).toBeInTheDocument(); }); test("renders a publish button", async () => { expect(screen.getByRole("button", { name: "Publish" })).toBeInTheDocument(); }); test("renders an information alert", async () => { expect( screen.getByText( "Below is a preview of what the published dashboard will look like. " + "If ready to proceed, you can publish the dashboard to make it " + "available on the published site.", ), ).toBeInTheDocument(); }); test("renders the publish modal", async () => { await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Publish" })); }); expect(screen.getByRole("button", { name: "Publish dashboard" })).toBeInTheDocument(); await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Close" })); }); expect(screen.queryByText("Publish dashboard")).not.toBeInTheDocument(); }); }); describe("dashboard published", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Published state const published = { ...dummyDashboard, state: DashboardState.Published, }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); render(, { wrapper: MemoryRouter, }); }); test("renders an archive button", async () => { expect( screen.getByRole("button", { name: "Published dashboard actions" }), ).toBeInTheDocument(); }); test("renders an update button", async () => { expect(screen.getByRole("button", { name: "Update" })).toBeInTheDocument(); }); test("renders show version notes checkbox", async () => { const viewCheckbox = screen.getByText("Show version notes"); fireEvent.input(viewCheckbox, { target: { checked: true, }, }); expect(viewCheckbox).toBeInTheDocument(); }); }); describe("dashboard archived", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Archived state const archived = { ...dummyDashboard, state: DashboardState.Archived }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: archived, }); render(, { wrapper: MemoryRouter, }); }); test("renders a re-publish button", async () => { expect(screen.getByRole("button", { name: "Re-publish" })).toBeInTheDocument(); }); test("renders a view history button", async () => { expect( screen.getByRole("button", { name: "Archived dashboard actions" }), ).toBeInTheDocument(); }); test("renders the re-publish modal", async () => { await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Re-publish" })); }); const message = await screen.findByText( "Are you sure you want to re-publish this dashboard?", ); expect(message).toBeInTheDocument(); }); }); describe("dashboard in draft in mobile", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Draft const draft = { ...dummyDashboard, state: DashboardState.Draft }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: draft, }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 400, }); render(, { wrapper: MemoryRouter, }); }); test("renders a close preview button", async () => { expect(screen.getByRole("button", { name: "Close Preview" })).toBeInTheDocument(); }); test("renders a publish button", async () => { expect(screen.getByRole("button", { name: "Publish" })).toBeInTheDocument(); }); test("renders an information alert", async () => { expect( screen.getByText( "Below is a preview of what the published dashboard will look like. " + "If ready to proceed, you can publish the dashboard to make it " + "available on the published site.", ), ).toBeInTheDocument(); }); test("renders the publish modal", async () => { await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Publish" })); }); expect(screen.getByRole("button", { name: "Publish dashboard" })).toBeInTheDocument(); }); }); describe("dashboard published in mobile", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Published state const published = { ...dummyDashboard, state: DashboardState.Published, }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 400, }); render(, { wrapper: MemoryRouter, }); }); test("renders an archive button", async () => { expect( screen.getByRole("button", { name: "Published dashboard actions" }), ).toBeInTheDocument(); }); test("renders an update button", async () => { expect(screen.getByRole("button", { name: "Update" })).toBeInTheDocument(); }); test("renders show version notes checkbox", async () => { const viewCheckbox = screen.getByText("Show version notes"); fireEvent.input(viewCheckbox, { target: { checked: true, }, }); expect(viewCheckbox).toBeInTheDocument(); }); }); describe("dashboard archived in mobile", () => { beforeEach(() => { // Override the useDashboard mock to return a dashboard in Archived state const archived = { ...dummyDashboard, state: DashboardState.Archived }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: archived, }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 400, }); render(, { wrapper: MemoryRouter, }); }); test("renders a re-publish button", async () => { expect(screen.getByRole("button", { name: "Re-publish" })).toBeInTheDocument(); }); test("renders a view history button", async () => { expect( screen.getByRole("button", { name: "Archived dashboard actions" }), ).toBeInTheDocument(); }); test("renders the re-publish modal", async () => { await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Re-publish" })); }); const message = await screen.findByText( "Are you sure you want to re-publish this dashboard?", ); expect(message).toBeInTheDocument(); }); }); test("renders the update modal", async () => { BackendService.createDraft = jest.fn().mockReturnValue({ id: undefined, }); const published = { ...dummyDashboard, state: DashboardState.Published }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render(, { wrapper: MemoryRouter, }); fireEvent.click(screen.getByRole("button", { name: "Update" })); const createDrafButton = screen.getByRole("button", { name: "Create draft", }); expect(createDrafButton).toBeInTheDocument(); fireEvent.click(createDrafButton); expect(BackendService.createDraft).toHaveBeenCalled(); }); test("renders the update modal in mobile", async () => { const published = { ...dummyDashboard, state: DashboardState.Published }; (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 400, }); (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); render(, { wrapper: MemoryRouter, }); await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Update" })); }); expect(screen.getByRole("button", { name: "Create draft" })).toBeInTheDocument(); }); describe("Update Modal", () => { beforeEach(async () => { BackendService.createDraft = jest.fn().mockReturnValue({ id: undefined, }); const published = { ...dummyDashboard, state: DashboardState.Published, }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render(, { wrapper: MemoryRouter, }); fireEvent.click(screen.getByRole("button", { name: "Update" })); }); test("renders the modal", async () => { const createDraftButton = screen.getByRole("button", { name: "Create draft", }); expect(createDraftButton).toBeInTheDocument(); const closeButton = screen.getByRole("button", { name: "Close" }); expect(closeButton).toBeInTheDocument(); }); test("do click on 'Create draft' button submits the draft to the backend service", async () => { const createDraftButton = screen.getByRole("button", { name: "Create draft", }); fireEvent.click(createDraftButton); expect(BackendService.createDraft).toHaveBeenCalled(); }); test("do click on 'Close' do not submit the draft to the backend service", async () => { const closeButton = screen.getByRole("button", { name: "Close" }); fireEvent.click(closeButton); expect(BackendService.createDraft).toHaveBeenCalledTimes(0); }); }); describe("Archive Modal", () => { beforeEach(async () => { BackendService.archive = jest.fn().mockReturnValue({ id: undefined, }); const published = { ...dummyDashboard, state: DashboardState.Published, }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render(, { wrapper: MemoryRouter, }); fireEvent.click(screen.getByRole("button", { name: "Published dashboard actions" })); const menuItem = screen.getByLabelText("Archive published dashboard"); userEvent.click(menuItem); }); test("renders the modal", async () => { const archiveButton = screen.getByRole("button", { name: "Archive" }); expect(archiveButton).toBeInTheDocument(); const closeButton = screen.getByRole("button", { name: "Close" }); expect(closeButton).toBeInTheDocument(); }); test("do click on 'Archive' button submits the archive to the backend service", async () => { const copyButton = screen.getByRole("button", { name: "Archive" }); fireEvent.click(copyButton); expect(BackendService.archive).toHaveBeenCalled(); }); test("do click on 'Close' do not submit the archive to the backend service", async () => { const closeButton = screen.getByRole("button", { name: "Close" }); fireEvent.click(closeButton); expect(BackendService.archive).toHaveBeenCalledTimes(0); }); }); describe("Copy Modal", () => { beforeEach(async () => { BackendService.copyDashboard = jest.fn().mockReturnValue({ id: undefined, }); const published = { ...dummyDashboard, state: DashboardState.Published, }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render(, { wrapper: MemoryRouter, }); fireEvent.click(screen.getByRole("button", { name: "Published dashboard actions" })); const menuItem = screen.getByLabelText("Copy published dashboard"); userEvent.click(menuItem); }); test("renders the modal", async () => { const copyButton = screen.getByRole("button", { name: "Copy" }); expect(copyButton).toBeInTheDocument(); const closeButton = screen.getByRole("button", { name: "Close" }); expect(closeButton).toBeInTheDocument(); }); test("do click on 'Copy' button submits the copy to the backend service", async () => { const copyButton = screen.getByRole("button", { name: "Copy" }); fireEvent.click(copyButton); expect(BackendService.copyDashboard).toHaveBeenCalled(); }); test("do click on 'Close' do not submit the copy to the backend service", async () => { //Close Button inside Copy Modal const closeButton = screen.getByRole("button", { name: "Close" }); //do click on Close button fireEvent.click(closeButton); expect(BackendService.copyDashboard).toHaveBeenCalledTimes(0); }); }); describe("Re-publish Modal", () => { beforeEach(async () => { BackendService.publishDashboard = jest.fn().mockReturnValue({ id: undefined, }); const archived = { ...dummyDashboard, state: DashboardState.Archived }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: archived, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Archived", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render(, { wrapper: MemoryRouter, }); fireEvent.click(screen.getByRole("button", { name: "Re-publish" })); }); test("renders the modal", async () => { const dialog = screen.getByRole("dialog"); const rePublishButton = within(dialog).getByRole("button", { name: "Re-publish", }); expect(rePublishButton).toBeInTheDocument(); const closeButton = screen.getByRole("button", { name: "Close" }); expect(closeButton).toBeInTheDocument(); }); test("do click on 'Re-publish' button submits the publish action to the backend service", async () => { const dialog = screen.getByRole("dialog"); const rePublishButton = within(dialog).getByRole("button", { name: "Re-publish", }); fireEvent.click(rePublishButton); expect(BackendService.publishDashboard).toHaveBeenCalled(); }); test("do click on 'Close' do not submit the publish action to the backend service", async () => { const closeButton = screen.getByRole("button", { name: "Close" }); fireEvent.click(closeButton); expect(BackendService.publishDashboard).toHaveBeenCalledTimes(0); }); }); test("do click on 'View History' menu item navigates to dashboard history", async () => { const history = createMemoryHistory(); jest.spyOn(history, "push"); const published = { ...dummyDashboard, state: DashboardState.Published }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render( , ); //do click on the actions menu fireEvent.click(screen.getByRole("button", { name: "Published dashboard actions" })); // find the Copy menu item const copyMenuItem = screen.getByLabelText("View published dashboard's history"); expect(copyMenuItem).toBeInTheDocument(); //do click on the Copy menu item. userEvent.click(copyMenuItem); expect(history.push).toHaveBeenCalledWith(`/admin/dashboard/${dummyDashboard.id}/history`); }); test("when selecting a diferent version the ui navigates to selected version", async () => { const history = createMemoryHistory(); jest.spyOn(history, "push"); const published = { ...dummyDashboard, state: DashboardState.Published }; (hooks.useDashboard as any) = jest.fn().mockReturnValue({ loading: false, dashboard: published, }); (hooks.useDashboardVersions as any) = jest.fn().mockReturnValue({ loading: false, versions: [ { id: "af0dc34f", version: 1, state: "Inactive" }, { id: "40db73f9", version: 2, state: "Published", friendlyURL: "hello-world", }, ], }); (hooks.useWindowSize as any) = jest.fn().mockReturnValue({ width: 800, }); render( , ); const versionToSelect = { id: "af0dc34f", version: 1, state: "Inactive" }; userEvent.selectOptions(screen.getByRole("combobox"), [`${versionToSelect.version}`]); expect(history.push).toHaveBeenCalledWith(`/admin/dashboard/${versionToSelect.id}`); });