Skip to content
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

Enable TypeScript support in this repo #1459

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ validate:
make validate-no-uncommitted-package-lock-changes
npm run i18n_extract
npm run lint -- --max-warnings 0
npm run types
npm run test
npm run build

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"postinstall": "patch-package",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage --passWithNoTests"
"test": "fedx-scripts jest --coverage --passWithNoTests",
"types": "tsc --noEmit"
},
"author": "edX",
"license": "AGPL-3.0",
Expand Down
11 changes: 6 additions & 5 deletions src/constants.js → src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const DECODE_ROUTES = {
],
REDIRECT_HOME: 'home/:courseId',
REDIRECT_SURVEY: 'survey/:courseId',
};
} as const satisfies Readonly<{ [k: string]: string | readonly string[] }>;

export const ROUTES = {
UNSUBSCRIBE: '/goal-unsubscribe/:token',
Expand All @@ -25,15 +25,15 @@ export const ROUTES = {
DASHBOARD: 'dashboard',
ENTERPRISE_LEARNER_DASHBOARD: 'enterprise-learner-dashboard',
CONSENT: 'consent',
};
} as const satisfies Readonly<{ [k: string]: string }>;

export const REDIRECT_MODES = {
DASHBOARD_REDIRECT: 'dashboard-redirect',
ENTERPRISE_LEARNER_DASHBOARD_REDIRECT: 'enterprise-learner-dashboard-redirect',
CONSENT_REDIRECT: 'consent-redirect',
HOME_REDIRECT: 'home-redirect',
SURVEY_REDIRECT: 'survey-redirect',
};
} as const satisfies Readonly<{ [k: string]: string }>;

export const VERIFIED_MODES = [
'professional',
Expand All @@ -44,14 +44,15 @@ export const VERIFIED_MODES = [
'executive-education',
'paid-executive-education',
'paid-bootcamp',
];
] as const satisfies readonly string[];

export const WIDGETS = {
DISCUSSIONS: 'DISCUSSIONS',
NOTIFICATIONS: 'NOTIFICATIONS',
};
} as const satisfies Readonly<{ [k: string]: string }>;
Comment on lines 49 to +52
Copy link
Member

Choose a reason for hiding this comment

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

export const WIDGETS: Readonly<{ [k: string]: string }> = {
  DISCUSSIONS: 'DISCUSSIONS',
  NOTIFICATIONS: 'NOTIFICATIONS',
};

@bradenmacdonald I just experimented with this and I found this to be more precise, what do you say?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That seems less precise to me? If you do that, the type actually just becomes this, which is basically "any object" - not very useful:

Screenshot

Whereas using the satisfies operator, we get the exactly correct type as a constant:

Screenshot

Copy link
Member

Choose a reason for hiding this comment

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

@bradenmacdonald That makes more sense I was looking it from the point of view of just reading it, inferencing it will be more helpful for sure in the way you did it.


export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
export type StatusValue = typeof LOADING | typeof LOADED | typeof FAILED | typeof DENIED;
10 changes: 6 additions & 4 deletions src/course-home/data/slice.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';

export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
import {
LOADING,
LOADED,
FAILED,
DENIED,
} from '@src/constants';

const slice = createSlice({
name: 'course-home',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
} from '@openedx/paragon';
import PropTypes from 'prop-types';
import truncate from 'truncate-html';
import { FAILED, LOADED, LOADING } from '@src/constants';
import { useModel } from '../../../generic/model-store';
import fetchCourseRecommendations from './data/thunks';
import { FAILED, LOADED, LOADING } from './data/slice';
import CatalogSuggestion from './CatalogSuggestion';
import PageLoading from '../../../generic/PageLoading';
import { logClick } from './utils';
Expand Down
9 changes: 5 additions & 4 deletions src/courseware/course/course-exit/data/slice.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';

export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
import {
LOADING,
LOADED,
FAILED,
} from '@src/constants';

const slice = createSlice({
courseId: null,
Expand Down
2 changes: 1 addition & 1 deletion src/courseware/course/new-sidebar/common/SidebarBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ SidebarBase.propTypes = {
ariaLabel: PropTypes.string.isRequired,
sidebarId: PropTypes.string.isRequired,
className: PropTypes.string,
children: PropTypes.element.isRequired,
children: PropTypes.node.isRequired,
showTitleBar: PropTypes.bool,
width: PropTypes.string,
allowFullHeight: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const NotificationsWidget = () => {

// After three seconds, update notificationSeen (to hide red dot)
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
Copy link
Member

@farhaanbukhsh farhaanbukhsh Oct 3, 2024

Choose a reason for hiding this comment

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

Are we running the linter for TS and TSX? I can't find the command being updated and changes in eslintrc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, you are right. Fixed with dc39854. Changing .eslintrc shouldn't be needed since it inherits from frontend-build which already configures eslint to support typescript.

setTimeout(onNotificationSeen, 3000);
sendTrackEvent('edx.ui.course.upgrade.new_sidebar.notifications', notificationTrayEventProperties);
}, []);
Expand Down
2 changes: 1 addition & 1 deletion src/courseware/course/sequence/Sequence.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-use-before-define */
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import {
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { useSelector } from 'react-redux';

import { LOADED } from '@src/constants';
import { GetCourseExitNavigation } from '../../course-exit';
import UnitButton from './UnitButton';
import SequenceNavigationTabs from './SequenceNavigationTabs';
import { useSequenceNavigationMetadata } from './hooks';
import { useModel } from '../../../../generic/model-store';
import { LOADED } from '../../../data/slice';

import messages from './messages';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@openedx/paragon/icons';

import { useModel } from '@src/generic/model-store';
import { LOADING, LOADED } from '@src/course-home/data/slice';
import { LOADING, LOADED } from '@src/constants';
import PageLoading from '@src/generic/PageLoading';
import {
getSequenceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const NotificationTray = ({ intl }) => {
};
// After three seconds, update notificationSeen (to hide red dot)
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
setTimeout(onNotificationSeen, 3000);
sendTrackEvent('edx.ui.course.upgrade.old_sidebar.notifications', notificationTrayEventProperties);
}, []);
Expand Down
2 changes: 1 addition & 1 deletion src/courseware/data/redux.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';

import { FAILED, LOADING } from '@src/constants';
import * as thunks from './thunks';
import { FAILED, LOADING } from './slice';

import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils';

Expand Down
2 changes: 1 addition & 1 deletion src/courseware/data/selectors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LOADED } from './slice';
import { LOADED } from '@src/constants';

export function sequenceIdsSelector(state) {
if (state.courseware.courseStatus !== LOADED) {
Expand Down
10 changes: 6 additions & 4 deletions src/courseware/data/slice.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';

export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
import {
LOADING,
LOADED,
FAILED,
DENIED,
} from '@src/constants';

const slice = createSlice({
name: 'courseware',
Expand Down
41 changes: 41 additions & 0 deletions src/frontend-platform.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// frontend-platform currently doesn't provide types... do it ourselves for i18n module at least.
// We can remove this in the future when we migrate to frontend-shell, or when frontend-platform gets types
// (whichever comes first).

declare module '@edx/frontend-platform/i18n' {
// eslint-disable-next-line import/no-extraneous-dependencies
import { injectIntl as _injectIntl } from 'react-intl';
/** @deprecated Use useIntl() hook instead. */
export const injectIntl: typeof _injectIntl;
/** @deprecated Use useIntl() hook instead. */
export const intlShape: any;

// eslint-disable-next-line import/no-extraneous-dependencies
export {
createIntl,
FormattedDate,
FormattedTime,
FormattedRelativeTime,
FormattedNumber,
FormattedPlural,
FormattedMessage,
defineMessages,
IntlProvider,
useIntl,
} from 'react-intl';

// Other exports from the i18n module:
export const configure: any;
export const getPrimaryLanguageSubtag: (code: string) => string;
export const getLocale: (locale?: string) => string;
export const getMessages: any;
export const isRtl: (locale?: string) => boolean;
export const handleRtl: any;
export const mergeMessages: any;
export const LOCALE_CHANGED: any;
export const LOCALE_TOPIC: any;
export const getCountryList: any;
export const getCountryMessages: any;
export const getLanguageList: any;
export const getLanguageMessages: any;
}
2 changes: 1 addition & 1 deletion src/generic/CourseAccessErrorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useParams, Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import FooterSlot from '@openedx/frontend-slot-footer';
import { LOADED, LOADING } from '@src/constants';
import useActiveEnterpriseAlert from '../alerts/active-enteprise-alert';
import { AlertList } from './user-messages';
import { fetchDiscussionTab } from '../course-home/data/thunks';
import { LOADED, LOADING } from '../course-home/data/slice';
import PageLoading from './PageLoading';
import messages from '../tab-page/messages';

Expand Down
2 changes: 1 addition & 1 deletion src/preferences-unsubscribe/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { logError } from '@edx/frontend-platform/logging';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';

import { LOADED, LOADING, FAILED } from '../constants';
import { LOADED, LOADING, FAILED } from '@src/constants';
import PageLoading from '../generic/PageLoading';
import { unsubscribeNotificationPreferences } from './data/api';
import messages from './messages';
Expand Down
4 changes: 2 additions & 2 deletions src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ export async function initializeTestStore(options = {}, overrideStore = true) {

logUnhandledRequests(axiosMock);

// eslint-disable-next-line no-unused-expressions
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
!options.excludeFetchCourse && await executeThunk(fetchCourse(courseMetadata.id), store.dispatch);

// eslint-disable-next-line no-unused-expressions
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
!options.excludeFetchOutlineSidebar && await executeThunk(
getCourseOutlineStructure(courseMetadata.id),
store.dispatch,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/streak-celebration/StreakCelebrationModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const StreakModal = ({
}) => {
const { org, celebrations, username } = useModel('courseHomeMeta', courseId);
const factoid = getRandomFactoid(intl, streakLengthToCelebrate);
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [randomFactoid, setRandomFactoid] = useState(factoid); // Don't change factoid on re-render

// Open edX Folks: if you create a voucher with this code, the MFE will notice and show the discount
Expand Down
16 changes: 11 additions & 5 deletions src/utils.js → src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Helper, that is used to forcibly finalize all promises
// in thunk before running matcher against state.
/**
* Helper, that is used to forcibly finalize all promises
* in thunk before running matcher against state.
*
* TODO: move this to setupTest or testUtils - it's only used in tests.
*/
export const executeThunk = async (thunk, dispatch, getState) => {
await thunk(dispatch, getState);
await new Promise(setImmediate);
};

// Utility function for appending the browser timezone to the url
// Can be used on the backend when the user timezone is not set in the user account
export const appendBrowserTimezoneToUrl = (url) => {
/**
* Utility function for appending the browser timezone to the url
* Can be used on the backend when the user timezone is not set in the user account
*/
export const appendBrowserTimezoneToUrl = (url: string) => {
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const urlObject = new URL(url);
if (browserTimezone) {
Expand Down
13 changes: 13 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "@edx/typescript-config",
"compilerOptions": {
"outDir": "dist",
"baseUrl": "./src",
"paths": {
"*": ["*"],
"@src/*": ["*"]
}
},
"include": ["*.js", ".eslintrc.js", "src/**/*", "plugins/**/*"],
"exclude": ["dist", "node_modules"]
}