Skip to content

Convert the "Taxonomy" app to 100% TypeScript #2025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = {
next: null,
previous: null,
import type { TaxonomyListData } from '../data/types';

const taxonomyListMock: TaxonomyListData = {
next: '',
previous: '',
count: 4,
numPages: 1,
currentPage: 1,
Expand All @@ -18,6 +20,11 @@ module.exports = {
visibleToAuthors: false,
canChangeTaxonomy: false,
canDeleteTaxonomy: false,
exportId: 'CA',
tagsCount: 0,
allOrgs: true,
canTagObject: true,
orgs: [],
},
{
id: -1,
Expand All @@ -30,6 +37,11 @@ module.exports = {
visibleToAuthors: true,
canChangeTaxonomy: false,
canDeleteTaxonomy: false,
exportId: 'L',
tagsCount: 15,
allOrgs: true,
canTagObject: true,
orgs: [],
},
{
id: 1,
Expand All @@ -42,6 +54,11 @@ module.exports = {
visibleToAuthors: true,
canChangeTaxonomy: true,
canDeleteTaxonomy: true,
exportId: 'T1',
tagsCount: 15,
allOrgs: false,
canTagObject: true,
orgs: ['org'],
},
{
id: 2,
Expand All @@ -54,6 +71,13 @@ module.exports = {
visibleToAuthors: true,
canChangeTaxonomy: true,
canDeleteTaxonomy: true,
exportId: 'T2',
tagsCount: 15,
allOrgs: false,
canTagObject: true,
orgs: ['org'],
},
],
};

export default taxonomyListMock;
2 changes: 1 addition & 1 deletion src/taxonomy/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const useImportTags = () => {
* @param taxonomyId The ID of the taxonomy whose tags we're updating.
* @param file The file that we want to import
*/
export const useImportPlan = (taxonomyId: number, file: File | null) => useQuery({
export const useImportPlan = (taxonomyId: number | undefined, file: File | null) => useQuery({
queryKey: taxonomyQueryKeys.importPlan(taxonomyId, file ? `${file.name}${file.lastModified}${file.size}` : ''),
queryFn: async (): Promise<string | null> => {
if (!taxonomyId || file === null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-check
import React, { useCallback, useState } from 'react';
import {
ActionRow,
Expand All @@ -8,12 +7,19 @@ import {
ModalDialog,
Icon,
} from '@openedx/paragon';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Warning } from '@openedx/paragon/icons';
import messages from './messages';

const DeleteDialog = ({
interface DeleteDialogProps {
taxonomyName: string;
tagsCount: number;
isOpen: boolean;
onClose: () => void;
onDelete: () => void;
}

const DeleteDialog: React.FC<DeleteDialogProps> = ({
taxonomyName,
tagsCount,
isOpen,
Expand All @@ -24,13 +30,13 @@ const DeleteDialog = ({
const [deleteButtonDisabled, setDeleteButtonDisabled] = useState(true);
const deleteLabel = intl.formatMessage(messages.deleteDialogConfirmDeleteLabel);

const handleInputChange = useCallback((event) => {
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value === deleteLabel) {
setDeleteButtonDisabled(false);
} else {
setDeleteButtonDisabled(true);
}
}, []);
}, [deleteLabel]);

const onClickDelete = React.useCallback(() => {
onClose();
Expand Down Expand Up @@ -90,12 +96,4 @@ const DeleteDialog = ({
);
};

DeleteDialog.propTypes = {
taxonomyName: PropTypes.string.isRequired,
tagsCount: PropTypes.number.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};

export default DeleteDialog;
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-check
import React, { useState } from 'react';
import {
ActionRow,
Expand All @@ -7,18 +6,25 @@ import {
Form,
ModalDialog,
} from '@openedx/paragon';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { getTaxonomyExportFile } from '../data/api';

const ExportModal = ({
interface ExportModalProps {
taxonomyId: number;
isOpen: boolean;
onClose: () => void;
}

type OutputFormat = 'csv' | 'json';

const ExportModal: React.FC<ExportModalProps> = ({
taxonomyId,
isOpen,
onClose,
}) => {
const intl = useIntl();
const [outputFormat, setOutputFormat] = useState(/** @type {'csv'|'json'} */('csv'));
const [outputFormat, setOutputFormat] = useState<OutputFormat>('csv');

const onClickExport = React.useCallback(() => {
onClose();
Expand Down Expand Up @@ -50,7 +56,7 @@ const ExportModal = ({
<Form.RadioSet
name="export-format"
value={outputFormat}
onChange={(e) => setOutputFormat(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setOutputFormat(e.target.value as OutputFormat)}
>
<Form.Radio
key={`export-csv-format-${taxonomyId}`}
Expand Down Expand Up @@ -86,10 +92,4 @@ const ExportModal = ({
);
};

ExportModal.propTypes = {
taxonomyId: PropTypes.number.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};

export default ExportModal;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import MockAdapter from 'axios-mock-adapter';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
Expand All @@ -11,17 +12,16 @@ import {
waitFor,
screen,
} from '@testing-library/react';
import PropTypes from 'prop-types';

import initializeStore from '../../store';
import { getTaxonomyExportFile } from '../data/api';
import { TaxonomyContext } from '../common/context';
import { ImportTagsWizard } from './ImportTagsWizard';

let store;
let store: any;

const queryClient = new QueryClient();
let axiosMock;
let axiosMock: MockAdapter;

jest.mock('../data/api', () => ({
...jest.requireActual('../data/api'),
Expand All @@ -33,7 +33,7 @@ const mockSetAlertError = jest.fn();
const context = {
toastMessage: null,
setToastMessage: mockSetToastMessage,
alertProps: null,
alertError: null,
setAlertError: mockSetAlertError,
};

Expand All @@ -46,7 +46,13 @@ const sampleTaxonomy = {
name: 'Test Taxonomy',
};

const RootWrapper = ({ onClose, reimport, taxonomy }) => (
interface RootWrapperProps {
onClose: () => void;
reimport: boolean;
taxonomy: typeof sampleTaxonomy | undefined;
}

const RootWrapper: React.FC<RootWrapperProps> = ({ onClose, reimport, taxonomy }) => (
<AppProvider store={store}>
<IntlProvider locale="en" messages={{}}>
<QueryClientProvider client={queryClient}>
Expand All @@ -58,15 +64,6 @@ const RootWrapper = ({ onClose, reimport, taxonomy }) => (
</AppProvider>
);

RootWrapper.propTypes = {
onClose: PropTypes.func.isRequired,
reimport: PropTypes.bool.isRequired,
taxonomy: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
}).isRequired,
};

describe('<ImportTagsWizard />', () => {
beforeEach(() => {
initializeMockApp({
Expand Down Expand Up @@ -140,7 +137,7 @@ describe('<ImportTagsWizard />', () => {
expect(getByTestId('dropzone')).toBeInTheDocument();
expect(importButton).toHaveAttribute('aria-disabled', 'true');

const makeJson = (filename) => new File(['{}'], filename, { type: 'application/json' });
const makeJson = (filename: string) => new File(['{}'], filename, { type: 'application/json' });

// Correct file type
axiosMock.onPut(planImportUrl).replyOnce(200, { plan: 'Import plan' });
Expand Down Expand Up @@ -248,7 +245,7 @@ describe('<ImportTagsWizard />', () => {
const onClose = jest.fn();
const {
findByTestId, getByRole, getByTestId, getByText, queryByTestId,
} = render(<RootWrapper taxonomy={null} onClose={onClose} />);
} = render(<RootWrapper taxonomy={undefined} onClose={onClose} reimport={false} />);

// Check that there is no export step
expect(await queryByTestId('export-step')).not.toBeInTheDocument();
Expand All @@ -269,7 +266,7 @@ describe('<ImportTagsWizard />', () => {
expect(getByTestId('dropzone')).toBeInTheDocument();
expect(continueButton).toHaveAttribute('aria-disabled', 'true');

const makeJson = (filename) => new File(['{}'], filename, { type: 'application/json' });
const makeJson = (filename: string) => new File(['{}'], filename, { type: 'application/json' });

// Correct file type
fireEvent.drop(getByTestId('dropzone'), { dataTransfer: { files: [makeJson('example1.json')], types: ['Files'] } });
Expand Down
Loading