Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
.username .form-control::placeholder {
font-size: var(--pgn-typography-form-input-font-size-sm);
}

.info-tooltip .tooltip-inner {
max-width: none;
}
8 changes: 1 addition & 7 deletions src/components/ActionCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('ActionCard', () => {
title: 'Test Card Title',
description: 'This is a test card description',
buttonLabel: 'Click Me',
onButtonClick: jest.fn(),
};

beforeEach(() => {
Expand Down Expand Up @@ -63,11 +64,4 @@ describe('ActionCard', () => {
expect(screen.getByText('Custom Button 2')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Click Me' })).not.toBeInTheDocument();
});

it('should handle missing onButtonClick prop gracefully', async () => {
render(<ActionCard {...defaultProps} />);
const button = screen.getByRole('button', { name: 'Click Me' });
await user.click(button);
expect(button).toBeInTheDocument();
});
});
16 changes: 8 additions & 8 deletions src/components/ActionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, Card } from '@openedx/paragon';

interface ActionCardProps {
buttonLabel: string,
export interface ActionCardProps {
buttonLabel?: string,
customAction?: React.ReactNode,
description: string,
hasBorderBottom?: boolean,
Expand All @@ -20,23 +20,23 @@ const ActionCard = ({
onButtonClick,
}: ActionCardProps) => {
return (
<Card className={`bg-light-200 py-2 border-gray-500 rounded-0 shadow-none ${hasBorderBottom ? 'border-bottom' : ''}`} orientation="horizontal">
<Card className={`bg-light-200 pb-2 mt-2 border-gray-500 rounded-0 shadow-none ${hasBorderBottom ? 'border-bottom' : ''}`} orientation="horizontal">
<Card.Body className="flex-grow-1">
<Card.Section>
<h4 className="mb-2">{title}</h4>
<p className="text-muted mb-0">{description}</p>
<Card.Section className="pl-0">
<h4 className="text-primary-700 mb-2">{title}</h4>
<p className="text-primary-500 mb-0">{description}</p>
</Card.Section>
</Card.Body>
<Card.Footer className="d-flex align-items-center justify-content-end">
{customAction ?? (
{customAction ?? (buttonLabel && onButtonClick && (
<Button
onClick={onButtonClick}
disabled={isLoading}
variant="primary"
>
{buttonLabel}
</Button>
)}
))}
</Card.Footer>
</Card>
);
Expand Down
24 changes: 15 additions & 9 deletions src/components/PendingTasks.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { screen } from '@testing-library/react';
import { PendingTasks } from './PendingTasks';
import { PendingTasks } from '@src/components/PendingTasks';
import messages from '@src/components/messages';
import { usePendingTasks } from '@src/data/apiHook';
import { renderWithQueryClient } from '@src/testUtils';

Expand All @@ -12,14 +13,14 @@ describe('PendingTasks', () => {

beforeEach(() => {
jest.clearAllMocks();
});

it('should render the collapsible pending tasks section', () => {
mockUsePendingTasks.mockReturnValue({
data: undefined,
isPending: false,
isLoading: false,
} as any);
});

it('should render the collapsible pending tasks section', () => {
renderWithQueryClient(<PendingTasks />);

expect(screen.getByText('Pending Tasks')).toBeInTheDocument();
Expand All @@ -37,9 +38,9 @@ describe('PendingTasks', () => {
const toggleButton = screen.getByRole('button');
await toggleButton.click();

expect(screen.queryByText('No tasks currently running.')).not.toBeInTheDocument();
expect(screen.queryByText(messages.noTasksMessage.defaultMessage)).not.toBeInTheDocument();
expect(screen.queryByRole('table')).not.toBeInTheDocument();
expect(screen.queryByText('Task Type')).not.toBeInTheDocument();
expect(screen.queryByText(messages.taskTypeColumnName.defaultMessage)).not.toBeInTheDocument();

const skeletons = container.querySelectorAll('.react-loading-skeleton');
expect(skeletons).toHaveLength(3);
Expand All @@ -56,7 +57,7 @@ describe('PendingTasks', () => {
const toggleButton = screen.getByRole('button');
await toggleButton.click();

expect(screen.getByText('No tasks currently running.')).toBeInTheDocument();
expect(screen.getByText(messages.noTasksMessage.defaultMessage)).toBeInTheDocument();
});

it('should render data table with tasks when data is available', async () => {
Expand Down Expand Up @@ -85,13 +86,18 @@ describe('PendingTasks', () => {
const toggleButton = screen.getByRole('button');
await toggleButton.click();

expect(screen.getByText('Task Type')).toBeInTheDocument();
expect(screen.getByText('Task ID')).toBeInTheDocument();
expect(screen.getByText(messages.taskTypeColumnName.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.taskIdColumnName.defaultMessage)).toBeInTheDocument();
expect(screen.getByText('grade_course')).toBeInTheDocument();
expect(screen.getByText('12345')).toBeInTheDocument();
});

it('should fetch tasks on component mount', async () => {
mockUsePendingTasks.mockReturnValue({
data: undefined,
isPending: false,
isLoading: false,
} as any);
renderWithQueryClient(<PendingTasks />);
expect(mockUsePendingTasks).toHaveBeenCalledTimes(1);
});
Expand Down
7 changes: 6 additions & 1 deletion src/components/PendingTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { PendingTask, TableCellValue } from '@src/types';

interface PendingTasksProps {
isPolling?: boolean,
isOpen?: boolean,
onToggle?: () => void,
}

const PendingTasks = ({ isPolling = false }: PendingTasksProps) => {
const PendingTasks = ({ isPolling = false, isOpen = false, onToggle }: PendingTasksProps) => {
const intl = useIntl();
const { courseId = '' } = useParams();
const { data: tasks, isLoading } = usePendingTasks(courseId, { enablePolling: isPolling });
Expand Down Expand Up @@ -48,10 +50,13 @@ const PendingTasks = ({ isPolling = false }: PendingTasksProps) => {
);
};

const collapsibleProps = onToggle ? { open: isOpen, onToggle } : {};

return (
<Collapsible.Advanced
className="mt-4 pt-4 border-top"
styling="basic"
{...collapsibleProps}
>
<Collapsible.Trigger
className="collapsible-trigger d-flex border-0 align-items-center text-decoration-none"
Expand Down
4 changes: 2 additions & 2 deletions src/components/SpecifyLearnerField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('SpecifyLearnerField', () => {
(useCourseInfo as jest.Mock).mockReturnValue({ data: { permissions: { admin: true, dataResearcher: false } } });
(useLearner as jest.Mock).mockReturnValue({
data: mockLearnerData,
refetch: jest.fn(),
refetch: jest.fn().mockResolvedValue({ data: mockLearnerData }),
error: null,
});
});
Expand Down Expand Up @@ -76,7 +76,7 @@ describe('SpecifyLearnerField', () => {
(useCourseInfo as jest.Mock).mockReturnValue({ data: { permissions: { admin: true, dataResearcher: false } } });
(useLearner as jest.Mock).mockReturnValue({
data: {},
refetch: jest.fn(),
refetch: jest.fn().mockResolvedValue({ data: {} }),
error: { isAxiosError: true, response: { status: 404 } },
});
});
Expand Down
31 changes: 24 additions & 7 deletions src/components/SpecifyLearnerField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, ChangeEvent } from 'react';
import { useState, ChangeEvent, useImperativeHandle, forwardRef } from 'react';
import { isAxiosError } from 'axios';
import { useParams } from 'react-router-dom';
import { Avatar, Button, FormControl, FormGroup, FormLabel, useToggle } from '@openedx/paragon';
Expand All @@ -14,19 +14,30 @@ interface SpecifyLearnerFieldProps {
onClickSelect: (emailOrUsername: string) => void,
}

const SpecifyLearnerField = ({ learner, onClickSelect }: SpecifyLearnerFieldProps) => {
interface SpecifyLearnerFieldRef {
reset: () => void,
}

const SpecifyLearnerField = forwardRef<SpecifyLearnerFieldRef, SpecifyLearnerFieldProps>(({ learner, onClickSelect }, ref) => {
const intl = useIntl();
const { courseId = '' } = useParams<{ courseId: string }>();
const [identifier, setIdentifier] = useState('');
const [showLearner, enableShowLearner, disableShowLearner] = useToggle(false);
const { data: courseInfo } = useCourseInfo(courseId);
const permissions = courseInfo?.permissions || { admin: false, dataResearcher: false };
const { inputValue, handleChange } = useDebouncedFilter({
const { inputValue, handleChange, resetFilter } = useDebouncedFilter({
filterValue: identifier,
setFilter: setIdentifier,
});
const { data = { email: '', fullName: '', username: '' }, refetch, error } = useLearner(courseId, inputValue);

useImperativeHandle(ref, () => ({
reset: () => {
resetFilter();
disableShowLearner();
}
}));

const selectedLearner = learner || data;

const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
Expand All @@ -39,9 +50,13 @@ const SpecifyLearnerField = ({ learner, onClickSelect }: SpecifyLearnerFieldProp

const handleClickSelect = () => {
if (inputValue) {
onClickSelect(inputValue);
refetch();
enableShowLearner();
refetch().then((result) => {
// Need to pass empty value if learner is not valid to clear out any previously selected learner
// We could have other conditions/fields depending on valid learner
const formValue = !result.error ? inputValue : '';
onClickSelect(formValue);
enableShowLearner();
});
}
};

Expand Down Expand Up @@ -86,6 +101,8 @@ const SpecifyLearnerField = ({ learner, onClickSelect }: SpecifyLearnerFieldProp
)}
</FormGroup>
);
};
});

SpecifyLearnerField.displayName = 'SpecifyLearnerField';

export default SpecifyLearnerField;
5 changes: 0 additions & 5 deletions src/components/SpecifyProblem.tsx

This file was deleted.

Loading
Loading