diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 23bb122..4935fdf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,6 +24,8 @@ jobs:
run: make validate-no-uncommitted-package-lock-changes
- name: Lint
run: npm run lint
+ - name: Type check
+ run: npm run types
- name: Test
run: npm run test
- name: Build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c6df0cc..499db8c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,6 +25,8 @@ jobs:
run: make validate-no-uncommitted-package-lock-changes
- name: Lint
run: npm run lint
+ - name: Type check
+ run: npm run types
- name: Test
run: npm run test
- name: i18n_extract
diff --git a/package.json b/package.json
index b15299f..16a0ef3 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
- "test": "fedx-scripts jest --coverage"
+ "test": "fedx-scripts jest --coverage",
+ "types": "tsc --noEmit"
},
"files": [
"/dist"
diff --git a/src/desktop-header/DesktopHeader.jsx b/src/desktop-header/DesktopHeader.jsx
index 4b5e493..9982950 100644
--- a/src/desktop-header/DesktopHeader.jsx
+++ b/src/desktop-header/DesktopHeader.jsx
@@ -22,7 +22,7 @@ import messages from '../Header.messages';
import { CaretIcon } from '../Icons';
class DesktopHeader extends React.Component {
- constructor(props) { // eslint-disable-line no-useless-constructor
+ constructor(props) { // eslint-disable-line @typescript-eslint/no-useless-constructor
super(props);
}
diff --git a/src/frontend-platform.d.ts b/src/frontend-platform.d.ts
new file mode 100644
index 0000000..d9b2f7f
--- /dev/null
+++ b/src/frontend-platform.d.ts
@@ -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;
+}
diff --git a/src/mobile-header/MobileHeader.jsx b/src/mobile-header/MobileHeader.jsx
index 7a04ec7..70d808e 100644
--- a/src/mobile-header/MobileHeader.jsx
+++ b/src/mobile-header/MobileHeader.jsx
@@ -21,7 +21,7 @@ import messages from '../Header.messages';
import { MenuIcon } from '../Icons';
class MobileHeader extends React.Component {
- constructor(props) { // eslint-disable-line no-useless-constructor
+ constructor(props) { // eslint-disable-line @typescript-eslint/no-useless-constructor
super(props);
}
diff --git a/src/studio-header/BrandNav.test.jsx b/src/studio-header/BrandNav.test.tsx
similarity index 93%
rename from src/studio-header/BrandNav.test.jsx
rename to src/studio-header/BrandNav.test.tsx
index 7ea2d3e..5c9be68 100644
--- a/src/studio-header/BrandNav.test.jsx
+++ b/src/studio-header/BrandNav.test.tsx
@@ -34,7 +34,7 @@ describe('BrandNav Component', () => {
it('displays a link that navigates to studioBaseUrl', () => {
render();
- const link = screen.getByRole('link');
+ const link = screen.getByRole('link') as HTMLAnchorElement;
expect(link.href).toBe(studioBaseUrl);
});
});
diff --git a/src/studio-header/BrandNav.jsx b/src/studio-header/BrandNav.tsx
similarity index 100%
rename from src/studio-header/BrandNav.jsx
rename to src/studio-header/BrandNav.tsx
diff --git a/src/studio-header/CourseLockUp.test.jsx b/src/studio-header/CourseLockUp.test.tsx
similarity index 88%
rename from src/studio-header/CourseLockUp.test.jsx
rename to src/studio-header/CourseLockUp.test.tsx
index 5dfc48f..d5a48d6 100644
--- a/src/studio-header/CourseLockUp.test.jsx
+++ b/src/studio-header/CourseLockUp.test.tsx
@@ -16,7 +16,7 @@ const mockProps = {
const RootWrapper = (props) => (
-
+
@@ -52,7 +52,8 @@ describe('CourseLockUp Component', () => {
it('navigates to an absolute URL when clicked', () => {
render();
- const link = screen.getByTestId('course-lock-up-block');
+ // FIXME: don't use testId - https://testing-library.com/docs/queries/about#priority
+ const link = screen.getByTestId('course-lock-up-block') as HTMLAnchorElement;
expect(link.href).toBe(mockProps.outlineLink);
});
});
diff --git a/src/studio-header/CourseLockUp.jsx b/src/studio-header/CourseLockUp.tsx
similarity index 100%
rename from src/studio-header/CourseLockUp.jsx
rename to src/studio-header/CourseLockUp.tsx
diff --git a/src/studio-header/HeaderBody.test.jsx b/src/studio-header/HeaderBody.test.tsx
similarity index 98%
rename from src/studio-header/HeaderBody.test.jsx
rename to src/studio-header/HeaderBody.test.tsx
index 6e5a5e6..3223f60 100644
--- a/src/studio-header/HeaderBody.test.jsx
+++ b/src/studio-header/HeaderBody.test.tsx
@@ -35,7 +35,7 @@ const defaultProps = {
const RootWrapper = (props) => (
-
+
diff --git a/src/studio-header/HeaderBody.jsx b/src/studio-header/HeaderBody.tsx
similarity index 99%
rename from src/studio-header/HeaderBody.jsx
rename to src/studio-header/HeaderBody.tsx
index 3ed7403..598aec3 100644
--- a/src/studio-header/HeaderBody.jsx
+++ b/src/studio-header/HeaderBody.tsx
@@ -135,6 +135,7 @@ const HeaderBody = ({
logoutUrl,
authenticatedUserAvatar,
isAdmin,
+ isMobile,
}}
/>
diff --git a/src/studio-header/MobileHeader.jsx b/src/studio-header/MobileHeader.tsx
similarity index 95%
rename from src/studio-header/MobileHeader.jsx
rename to src/studio-header/MobileHeader.tsx
index 44d6d21..bec1d8b 100644
--- a/src/studio-header/MobileHeader.jsx
+++ b/src/studio-header/MobileHeader.tsx
@@ -13,6 +13,7 @@ const MobileHeader = ({
return (
<>
+ {/* @ts-expect-error The type of 'props' is any until we convert from propTypes to TypeScript interface/types */}
{
+}: React.ComponentProps) => {
const appContextValue = useMemo(() => ({
authenticatedUser: currentUser,
config: {
@@ -55,7 +55,7 @@ const RootWrapper = ({
);
};
-const props = {
+const props: React.ComponentProps = {
number: '123',
org: 'Ed',
title: 'test',
@@ -74,6 +74,10 @@ const props = {
outlineLink: 'tEsTLInK',
searchButtonAction: null,
isNewHomePage: true,
+ // These default values shouldn't be needed but typescript is confused by propTypes; can remove after converting
+ // from propTypes to TypeScript:
+ containerProps: {},
+ isHiddenMainMenu: false,
};
describe('Header', () => {
diff --git a/src/studio-header/StudioHeader.jsx b/src/studio-header/StudioHeader.tsx
similarity index 96%
rename from src/studio-header/StudioHeader.jsx
rename to src/studio-header/StudioHeader.tsx
index 6ad0823..735cfa5 100644
--- a/src/studio-header/StudioHeader.jsx
+++ b/src/studio-header/StudioHeader.tsx
@@ -19,6 +19,7 @@ const StudioHeader = ({
number, org, title, containerProps, isHiddenMainMenu, mainMenuDropdowns,
outlineLink, searchButtonAction, isNewHomePage,
}) => {
+ // @ts-expect-error - frontend-platform doesn't yet have type information :/
const { authenticatedUser, config } = useContext(AppContext);
const props = {
logo: config.LOGO_URL,
diff --git a/src/studio-header/UserMenu.jsx b/src/studio-header/UserMenu.tsx
similarity index 100%
rename from src/studio-header/UserMenu.jsx
rename to src/studio-header/UserMenu.tsx
diff --git a/src/studio-header/index.js b/src/studio-header/index.ts
similarity index 100%
rename from src/studio-header/index.js
rename to src/studio-header/index.ts
diff --git a/src/studio-header/messages.js b/src/studio-header/messages.ts
similarity index 100%
rename from src/studio-header/messages.js
rename to src/studio-header/messages.ts
diff --git a/src/studio-header/utils.js b/src/studio-header/utils.ts
similarity index 100%
rename from src/studio-header/utils.js
rename to src/studio-header/utils.ts
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..d01a9c8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "@edx/typescript-config",
+ "compilerOptions": {
+ "noEmit": true,
+ "baseUrl": "./src",
+ "paths": {
+ "*": ["*"]
+ }
+ },
+ "include": ["*.js", ".eslintrc.js", "src/**/*", "plugins/**/*"],
+ "exclude": ["dist", "node_modules"]
+}