Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand All @@ -6,12 +7,13 @@ import {
OverlayTrigger,
Stack,
Tooltip,
IconButton,
} from '@openedx/paragon';
import { InfoOutline, Locked } from '@openedx/paragon/icons';
import { useContextId } from '../../../../data/hooks';

import { useContextId } from '@src/data/hooks';
import { useModel } from '@src/generic/model-store';
import messages from '../messages';
import { useModel } from '../../../../generic/model-store';

const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => {
const intl = useIntl();
Expand All @@ -20,24 +22,36 @@ const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => {
verifiedMode,
gradesFeatureIsFullyLocked,
} = useModel('progress', courseId);
const [showTooltip, setShowTooltip] = useState(false);

const handleKeyDown = (event) => {
if (event.key === 'Escape') {
setShowTooltip(false);
}
};

return (
<Stack gap={2} className="mb-3">
<Stack direction="horizontal" gap={2}>
<h3 className="h4 m-0">{intl.formatMessage(messages.gradeSummary)}</h3>
<OverlayTrigger
trigger="hover"
trigger="click"
placement="top"
show={showTooltip}
overlay={(
<Tooltip>
{intl.formatMessage(messages.gradeSummaryTooltipBody)}
</Tooltip>
)}
>
<Icon
<IconButton
onClick={() => setShowTooltip(!showTooltip)}
onBlur={() => setShowTooltip(false)}
onKeyDown={handleKeyDown}
alt={intl.formatMessage(messages.gradeSummaryTooltipAlt)}
src={InfoOutline}
iconAs={InfoOutline}
size="sm"
disabled={gradesFeatureIsFullyLocked}
/>
</OverlayTrigger>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import userEvent from '@testing-library/user-event';
import { useSelector } from 'react-redux';
import { IntlProvider } from 'react-intl';

import { useModel } from '@src/generic/model-store';
import {
initializeMockApp, render, screen, waitFor,
} from '@src/setupTest';
import GradeSummaryHeader from './GradeSummaryHeader';
import messages from '../messages';

jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));

jest.mock('@src/generic/model-store', () => ({
useModel: jest.fn(),
}));

jest.mock('@src/data/hooks', () => ({
useContextId: () => 'test-course-id',
}));

describe('GradeSummaryHeader', () => {
beforeAll(() => {
initializeMockApp();
});

beforeEach(() => {
useSelector.mockImplementation((selector) => selector({
courseHome: { courseId: 'test-course-id' },
}));
useModel.mockReturnValue({ gradesFeatureIsFullyLocked: false });
});

const renderComponent = (props = {}) => {
render(
<IntlProvider locale="en" messages={messages}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this extra IntlProvider?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, removed

<GradeSummaryHeader
allOfSomeAssignmentTypeIsLocked={false}
{...props}
/>
</IntlProvider>,
);
};

it('shows tooltip on icon button click', async () => {
renderComponent();

const iconButton = screen.getByRole('button', {
name: messages.gradeSummaryTooltipAlt.defaultMessage,
});

await userEvent.click(iconButton);

await waitFor(() => {
expect(screen.getByText(messages.gradeSummaryTooltipBody.defaultMessage)).toBeInTheDocument();
});
});

it('hides tooltip on click', async () => {
renderComponent();

const iconButton = screen.getByRole('button', {
name: messages.gradeSummaryTooltipAlt.defaultMessage,
});

await userEvent.click(iconButton);

await waitFor(() => {
expect(screen.getByText(messages.gradeSummaryTooltipBody.defaultMessage)).toBeVisible();
});

await userEvent.click(iconButton);

await waitFor(() => {
expect(screen.queryByText(messages.gradeSummaryTooltipBody.defaultMessage)).toBeNull();
});
});

it('hides tooltip on blur', async () => {
renderComponent();

const iconButton = screen.getByRole('button', {
name: messages.gradeSummaryTooltipAlt.defaultMessage,
});

await userEvent.hover(iconButton);
await userEvent.click(iconButton);

await waitFor(() => {
expect(screen.getByText(messages.gradeSummaryTooltipBody.defaultMessage)).toBeInTheDocument();
});

const blurTarget = document.createElement('button');
blurTarget.textContent = 'Outside';
document.body.appendChild(blurTarget);

await userEvent.click(blurTarget);

await waitFor(() => {
expect(screen.queryByText(messages.gradeSummaryTooltipBody.defaultMessage)).not.toBeInTheDocument();
});

document.body.removeChild(blurTarget);
});

it('hides tooltip when Escape is pressed (covers handleKeyDown)', async () => {
renderComponent();

const iconButton = screen.getByRole('button', {
name: messages.gradeSummaryTooltipAlt.defaultMessage,
});

await userEvent.hover(iconButton);
await userEvent.click(iconButton);

await waitFor(() => {
expect(screen.getByText(messages.gradeSummaryTooltipBody.defaultMessage)).toBeInTheDocument();
});

await userEvent.keyboard('{Escape}');

await waitFor(() => {
expect(screen.queryByText(messages.gradeSummaryTooltipBody.defaultMessage)).not.toBeInTheDocument();
});
});
});