diff --git a/cypress/component/CreateModal.cy.tsx b/cypress/component/CreateModal.cy.tsx index c45b1d6..5d6e2f5 100644 --- a/cypress/component/CreateModal.cy.tsx +++ b/cypress/component/CreateModal.cy.tsx @@ -85,6 +85,13 @@ describe('CreateModal', () => { }); describe('Successful creation', () => { + beforeEach(() => { + cy.intercept('GET', '/api/widget-layout/v1/', { + statusCode: 200, + body: { data: [mockDashboardResponse] }, + }).as('getDashboards'); + }); + it('calls import API and closes modal on success', () => { cy.intercept('POST', '/api/widget-layout/v1/import', { statusCode: 200, diff --git a/cypress/component/DashboardTable.cy.tsx b/cypress/component/DashboardTable.cy.tsx index 0b67201..0ab5d7e 100644 --- a/cypress/component/DashboardTable.cy.tsx +++ b/cypress/component/DashboardTable.cy.tsx @@ -215,7 +215,7 @@ describe('DashboardTable', () => { cy.intercept('GET', '/api/widget-layout/v1/', { statusCode: 200, - body: mockDashboards, + body: { data: mockDashboards }, }).as('getDashboards'); cy.mount( @@ -355,6 +355,11 @@ describe('DashboardTable', () => { body: '', }).as('deleteDashboard'); + cy.intercept('GET', '/api/widget-layout/v1/', { + statusCode: 200, + body: { data: mockDashboards }, + }).as('getDashboards'); + const refetchStub = cy.stub(); cy.mount( diff --git a/cypress/component/DuplicateModal.cy.tsx b/cypress/component/DuplicateModal.cy.tsx index 7e282c9..bd38d0d 100644 --- a/cypress/component/DuplicateModal.cy.tsx +++ b/cypress/component/DuplicateModal.cy.tsx @@ -1,8 +1,10 @@ import React from 'react'; import { DuplicateModal } from '../../src/Components/DuplicateModal/DuplicateModal'; import Portal from '@redhat-cloud-services/frontend-components-notifications/Portal'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { notificationsAtom, useRemoveNotification } from '../../src/state/notificationsAtom'; +import { dashboardsAtom } from '../../src/state/dashboardsAtom'; +import { DashboardTemplate } from '../../src/api/dashboard-templates'; const NotificationPortal = () => { const notifications = useAtomValue(notificationsAtom); @@ -10,6 +12,14 @@ const NotificationPortal = () => { return ; }; +const HydrateDashboards: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const set = useSetAtom(dashboardsAtom); + React.useEffect(() => { + set(mockDashboards as DashboardTemplate[]); + }, []); + return <>{children}; +}; + const mockDashboards = [ { id: 1, @@ -50,29 +60,29 @@ const mockCopyResponse = { describe('DuplicateModal', () => { it('renders modal with title when isOpen=true', () => { - cy.mount(); + cy.mount(); cy.contains('Duplicate existing dashboard').should('be.visible'); }); it('does not render modal content when isOpen=false', () => { - cy.mount(); + cy.mount(); cy.contains('Duplicate existing dashboard').should('not.exist'); }); it('shows "Duplicate dashboard" button disabled when form is empty', () => { - cy.mount(); + cy.mount(); cy.contains('button', 'Duplicate dashboard').should('be.visible').and('be.disabled'); }); it('Cancel button calls onClose', () => { const onClose = cy.stub().as('onClose'); - cy.mount(); + cy.mount(); cy.contains('button', 'Cancel').click(); cy.get('@onClose').should('have.been.calledOnce'); }); it('dashboard name input is present and can be typed into', () => { - cy.mount(); + cy.mount(); cy.get('#duplicate-dashboard-name') .should('be.visible') .type('My Duplicate') @@ -80,14 +90,14 @@ describe('DuplicateModal', () => { }); it('form labels are correct', () => { - cy.mount(); + cy.mount(); cy.contains('label', 'Select existing dashboard for duplication').should('be.visible'); cy.contains('label', 'New dashboard name').should('be.visible'); cy.contains('Set as homepage').should('be.visible'); }); it('checkbox is unchecked by default and can be toggled', () => { - cy.mount(); + cy.mount(); cy.get('#set-as-homepage').should('not.be.checked'); cy.get('#set-as-homepage').check(); cy.get('#set-as-homepage').should('be.checked'); @@ -96,7 +106,7 @@ describe('DuplicateModal', () => { }); it('dashboard select dropdown shows mocked dashboards', () => { - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').within(() => { cy.get('option').should('have.length', 3); // placeholder + 2 dashboards cy.contains('option', 'Dashboard One').should('exist'); @@ -105,32 +115,39 @@ describe('DuplicateModal', () => { }); it('enables "Duplicate dashboard" button when both dashboard is selected AND name is entered', () => { - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type('My Duplicate'); cy.contains('button', 'Duplicate dashboard').should('not.be.disabled'); }); it('keeps button disabled when only name is entered (no dashboard selected)', () => { - cy.mount(); + cy.mount(); cy.get('#duplicate-dashboard-name').type('My Duplicate'); cy.contains('button', 'Duplicate dashboard').should('be.disabled'); }); it('keeps button disabled when only dashboard is selected (no name)', () => { - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.contains('button', 'Duplicate dashboard').should('be.disabled'); }); it('keeps button disabled when name is whitespace only', () => { - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type(' '); cy.contains('button', 'Duplicate dashboard').should('be.disabled'); }); describe('Successful duplication', () => { + beforeEach(() => { + cy.intercept('GET', '/api/widget-layout/v1/', { + statusCode: 200, + body: { data: [mockCopyResponse] }, + }).as('getDashboards'); + }); + it('calls copy API, shows success notification, calls onSuccess and onClose', () => { cy.intercept('POST', '/api/widget-layout/v1/1/copy', { statusCode: 200, @@ -141,10 +158,10 @@ describe('DuplicateModal', () => { const onSuccess = cy.stub().as('onSuccess'); cy.mount( - <> + - - + + ); cy.get('select[aria-label="Select a dashboard"]').select('1'); @@ -172,10 +189,10 @@ describe('DuplicateModal', () => { const onClose = cy.stub().as('onClose'); cy.mount( - <> + - - + + ); cy.get('select[aria-label="Select a dashboard"]').select('1'); @@ -201,10 +218,10 @@ describe('DuplicateModal', () => { }).as('setDefault'); cy.mount( - <> + - - + + ); cy.get('select[aria-label="Select a dashboard"]').select('1'); @@ -226,7 +243,7 @@ describe('DuplicateModal', () => { delay: 1000, }).as('copyDashboard'); - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type('My Duplicate'); @@ -245,7 +262,7 @@ describe('DuplicateModal', () => { body: 'Internal Server Error', }).as('copyDashboard'); - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type('My Duplicate'); @@ -262,7 +279,7 @@ describe('DuplicateModal', () => { body: 'Bad Request', }).as('copyDashboard'); - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type('My Duplicate'); @@ -279,7 +296,7 @@ describe('DuplicateModal', () => { body: 'Internal Server Error', }).as('copyDashboard'); - cy.mount(); + cy.mount(); cy.get('select[aria-label="Select a dashboard"]').select('1'); cy.get('#duplicate-dashboard-name').type('My Duplicate'); diff --git a/cypress/component/KebabDropdown.cy.tsx b/cypress/component/KebabDropdown.cy.tsx index 0adc1f8..cee12b7 100644 --- a/cypress/component/KebabDropdown.cy.tsx +++ b/cypress/component/KebabDropdown.cy.tsx @@ -1,7 +1,39 @@ import React from 'react'; import { KebabDropdown } from '../../src/Components/Header/Header'; import { MemoryRouter } from 'react-router-dom'; +import { ScalprumContext, ScalprumState } from '@scalprum/react-core'; import { DashboardTemplate } from '../../src/api/dashboard-templates'; +import { useSetAtom } from 'jotai'; +import { dashboardsAtom } from '../../src/state/dashboardsAtom'; + +const scalprumValue = { + initialized: true, + config: {}, + pluginStore: {}, + api: { + chrome: { + auth: { + getUser: () => Promise.resolve({ + entitlements: {}, + identity: { + org_id: '123', + type: 'User', + user: { + username: 'test-user', + email: 'test@test.com', + first_name: 'Test', + last_name: 'User', + is_active: true, + is_internal: false, + is_org_admin: false, + locale: 'en_US', + }, + }, + }), + }, + }, + }, +} as unknown as ScalprumState; const mockDashboards: DashboardTemplate[] = [ { @@ -17,14 +49,26 @@ const mockDashboards: DashboardTemplate[] = [ }, ]; +const HydrateDashboards: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const set = useSetAtom(dashboardsAtom); + React.useEffect(() => { + set(mockDashboards); + }, []); + return <>{children}; +}; + describe('KebabDropdown', () => { beforeEach(() => { cy.mount( - -
- -
-
+ + + +
+ +
+
+
+
); }); diff --git a/src/Components/DashboardHub/DashboardTable/DashboardTable.tsx b/src/Components/DashboardHub/DashboardTable/DashboardTable.tsx index ab9cf30..095a71f 100644 --- a/src/Components/DashboardHub/DashboardTable/DashboardTable.tsx +++ b/src/Components/DashboardHub/DashboardTable/DashboardTable.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Table, Tbody, Td, Th, ThProps, Thead, Tr } from '@patternfly/react-table'; import { ActionsColumn } from '@patternfly/react-table'; -import { Button, Content, TooltipPosition } from '@patternfly/react-core'; +import { Button, Content, Tooltip, TooltipPosition } from '@patternfly/react-core'; import { Link } from 'react-router-dom'; import { DashboardTemplate } from '../../../api/dashboard-templates'; import { setDefaultDashboardAtom } from '../../../state/dashboardsAtom'; @@ -9,6 +9,7 @@ import { CodeIcon, CopyIcon, EditAltIcon, HomeIcon, TrashIcon, UsersIcon } from import { useExportDashboard } from '../../../hooks/useExportDashboard'; import { useDeleteDashboard } from '../../../hooks/useDeleteDashboard'; import { DeleteDashboardModal } from '../DeleteDashboardModal/DeleteDashboardModal'; +import { DuplicateModal } from '../../DuplicateModal/DuplicateModal'; import DateFormat from '@redhat-cloud-services/frontend-components/DateFormat'; import { useFlag } from '@unleash/proxy-client-react'; import { useAddNotification } from '../../../state/notificationsAtom'; @@ -27,15 +28,15 @@ interface DashboardTableProps { onRefetchDashboards: () => void; } -export const ButtonCopy: React.FunctionComponent = () => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - return