Skip to content

Commit

Permalink
Tighten TypeScript linting (#2684)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored Oct 11, 2023
1 parent 7037c5c commit 42cc273
Show file tree
Hide file tree
Showing 29 changed files with 116 additions and 94 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"piptools",
"Prenoun",
"Preverb",
"recaptcha",
"reportgenerator",
"sched",
"sillsdev",
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
},
"eslintConfig": {
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
Expand All @@ -142,8 +143,9 @@
],
"rules": {
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": "warn",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/switch-exhaustiveness-check": "warn",
"import/first": "warn",
"import/newline-after-import": "warn",
Expand Down Expand Up @@ -173,9 +175,8 @@
],
"no-undef": "off",
"prefer-const": "warn",
"react/display-name": "off",
"react/jsx-boolean-value": "warn",
"unused-imports/no-unused-imports": "error"
"unused-imports/no-unused-imports": "warn"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
Expand All @@ -189,6 +190,7 @@
"react",
"unused-imports"
],
"root": true,
"settings": {
"react": {
"version": "detect"
Expand Down
18 changes: 11 additions & 7 deletions src/components/App/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "jest-canvas-mock";
import { Provider } from "react-redux";
import renderer from "react-test-renderer";
import { act, create } from "react-test-renderer";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";

Expand All @@ -9,9 +9,13 @@ import "tests/reactI18nextMock";
import { defaultState } from "components/App/DefaultState";
import App from "components/App/component";

jest.mock("@matt-block/react-recaptcha-v2", () => () => (
<div id="mockRecaptcha">Recaptcha</div>
));
jest.mock(
"@matt-block/react-recaptcha-v2",
() =>
function MockRecaptcha() {
return <div id="mockRecaptcha">Recaptcha</div>;
}
);
jest.mock("components/AnnouncementBanner/AnnouncementBanner", () => "div");

const createMockStore = configureMockStore([thunk]);
Expand All @@ -23,9 +27,9 @@ global.innerHeight = 100;
global.analytics = { track: jest.fn() } as any;

describe("App", () => {
it("renders without crashing", () => {
renderer.act(() => {
renderer.create(
it("renders without crashing", async () => {
await act(async () => {
create(
<Provider store={mockStore}>
<App />
</Provider>
Expand Down
11 changes: 1 addition & 10 deletions src/components/AppBar/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,12 @@ export default function UserMenu(props: TabProps): ReactElement {
open={Boolean(anchorElement)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
>
<WrappedUserMenuList isAdmin={isAdmin} onSelect={handleClose} />
<UserMenuList isAdmin={isAdmin} onSelect={handleClose} />
</Menu>
</>
);
}

// <Menu> automatically applies a ref to its first child for anchoring. The
// following prevents a console warning: "Function components cannot be given refs.
// Attempts to access this ref will fail. Did you mean to use React.forwardRef()?"
const WrappedUserMenuList = React.forwardRef(
(props: React.ComponentProps<typeof UserMenuList>, ref) => (
<UserMenuList {...props} forwardedRef={ref} />
)
);

interface UserMenuListProps {
isAdmin: boolean;
onSelect: () => void;
Expand Down
10 changes: 4 additions & 6 deletions src/components/AppBar/tests/AppBarComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import renderer from "react-test-renderer";
import { act, create } from "react-test-renderer";
import configureMockStore from "redux-mock-store";

import "tests/reactI18nextMock";
Expand All @@ -18,8 +18,6 @@ jest.mock("backend", () => ({

const mockStore = configureMockStore()(defaultState);

let testRenderer: renderer.ReactTestRenderer;

function setMockFunctions() {
mockGetUser.mockResolvedValue(mockUser);
}
Expand All @@ -30,9 +28,9 @@ beforeAll(() => {
});

describe("AppBar", () => {
it("renders", () => {
renderer.act(() => {
testRenderer = renderer.create(
it("renders", async () => {
await act(async () => {
create(
<Provider store={mockStore}>
<MemoryRouter>
<AppBar />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface VernWithSuggestionsProps {
vernInput?: React.RefObject<HTMLInputElement>;
updateVernField: (newValue: string, openDialog?: boolean) => void;
onBlur: () => void;
onClose?: (e: React.ChangeEvent<{}>, reason: AutocompleteCloseReason) => void;
onClose?: (e: React.SyntheticEvent, reason: AutocompleteCloseReason) => void;
suggestedVerns?: string[];
handleEnter: () => void;
vernacularLang: WritingSystem;
Expand Down
50 changes: 27 additions & 23 deletions src/components/DataEntry/DataEntryTable/tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ReactElement } from "react";
import { Provider } from "react-redux";
import renderer from "react-test-renderer";
import {
ReactTestInstance,
ReactTestRenderer,
act,
create,
} from "react-test-renderer";
import configureMockStore from "redux-mock-store";

import "tests/reactI18nextMock";
Expand All @@ -17,7 +22,6 @@ import DataEntryTable, {
updateEntryGloss,
} from "components/DataEntry/DataEntryTable";
import NewEntry from "components/DataEntry/DataEntryTable/NewEntry";
import { RecentEntryProps } from "components/DataEntry/DataEntryTable/RecentEntry";
import { newProject } from "types/project";
import {
newSemanticDomain,
Expand Down Expand Up @@ -57,10 +61,10 @@ jest.mock("utilities/utilities");

jest.spyOn(window, "alert").mockImplementation(() => {});

let testRenderer: renderer.ReactTestRenderer;
let testHandle: renderer.ReactTestInstance;
let testRenderer: ReactTestRenderer;
let testHandle: ReactTestInstance;

function MockRecentEntry(props: RecentEntryProps): ReactElement {
function MockRecentEntry(): ReactElement {
return <div />;
}

Expand Down Expand Up @@ -97,8 +101,8 @@ beforeEach(() => {
});

const renderTable = async (): Promise<void> => {
await renderer.act(async () => {
testRenderer = renderer.create(
await act(async () => {
testRenderer = create(
<Provider store={mockStore}>
<DataEntryTable
semanticDomain={mockTreeNode}
Expand All @@ -119,7 +123,7 @@ const addRecentEntry = async (word?: Word): Promise<string> => {
}
mockCreateWord.mockResolvedValueOnce(word);
mockGetWord.mockResolvedValueOnce(word);
await renderer.act(async () => {
await act(async () => {
await testRenderer.root.findByType(NewEntry).props.addNewEntry();
});
return word.id;
Expand All @@ -140,7 +144,7 @@ describe("DataEntryTable", () => {
it("hides questions", async () => {
expect(mockHideQuestions).not.toBeCalled();
testHandle = testRenderer.root.findByProps({ id: exitButtonId });
await renderer.act(async () => await testHandle.props.onClick());
await act(async () => await testHandle.props.onClick());
expect(mockHideQuestions).toBeCalled();
});

Expand All @@ -149,26 +153,26 @@ describe("DataEntryTable", () => {
testHandle = testRenderer.root.findByType(NewEntry);
expect(testHandle).not.toBeNull;
// Set newVern but not newGloss.
await renderer.act(async () => testHandle.props.setNewVern("hasVern"));
await act(async () => testHandle.props.setNewVern("hasVern"));
testHandle = testRenderer.root.findByProps({ id: exitButtonId });
await renderer.act(async () => await testHandle.props.onClick());
await act(async () => await testHandle.props.onClick());
expect(mockCreateWord).toBeCalledTimes(1);
});

it("doesn't create word when new entry has no vernacular", async () => {
testHandle = testRenderer.root.findByType(NewEntry);
expect(testHandle).not.toBeNull;
// Set newGloss but not newVern.
await renderer.act(async () => testHandle.props.setNewGloss("hasGloss"));
await act(async () => testHandle.props.setNewGloss("hasGloss"));
testHandle = testRenderer.root.findByProps({ id: exitButtonId });
await renderer.act(async () => await testHandle.props.onClick());
await act(async () => await testHandle.props.onClick());
expect(mockCreateWord).not.toBeCalled();
});

it("opens the domain tree", async () => {
expect(mockOpenTree).not.toBeCalled();
testHandle = testRenderer.root.findByProps({ id: exitButtonId });
await renderer.act(async () => await testHandle.props.onClick());
await act(async () => await testHandle.props.onClick());
expect(mockOpenTree).toBeCalledTimes(1);
});
});
Expand Down Expand Up @@ -293,7 +297,7 @@ describe("DataEntryTable", () => {
mockGetFrontierWords.mockResolvedValue([word]);
await renderTable();
testHandle = testRenderer.root.findByType(NewEntry);
await renderer.act(async () => {
await act(async () => {
await testHandle.props.setNewGloss(firstGlossText(word.senses[0]));
await testHandle.props.updateWordWithNewGloss(word.id);
});
Expand All @@ -312,7 +316,7 @@ describe("DataEntryTable", () => {

testHandle = testRenderer.root.findByType(NewEntry);
const glossText = firstGlossText(word.senses[senseIndex]);
await renderer.act(async () => {
await act(async () => {
await testHandle.props.setNewGloss(glossText);
await testHandle.props.updateWordWithNewGloss(word.id);
});
Expand All @@ -330,7 +334,7 @@ describe("DataEntryTable", () => {
it("updates word in backend if gloss doesn't exist", async () => {
await renderTable();
testHandle = testRenderer.root.findByType(NewEntry);
await renderer.act(async () => {
await act(async () => {
await testHandle.props.setNewGloss("differentGloss");
await testHandle.props.updateWordWithNewGloss(mockMultiWord.id);
});
Expand All @@ -344,7 +348,7 @@ describe("DataEntryTable", () => {
it("checks for duplicate and, if so, updates it", async () => {
testHandle = testRenderer.root.findByType(NewEntry);
mockGetDuplicateId.mockResolvedValueOnce(true);
await renderer.act(async () => {
await act(async () => {
await testHandle.props.addNewEntry();
});
expect(mockUpdateDuplicate).toBeCalledTimes(1);
Expand All @@ -368,7 +372,7 @@ describe("DataEntryTable", () => {

// Verify that the number of recent entries increases by the correct amount
expect(testRenderer.root.findAllByType(MockRecentEntry)).toHaveLength(0);
await renderer.act(async () => {
await act(async () => {
await testRenderer.root.findByType(NewEntry).props.addNewEntry();
});
expect(testRenderer.root.findAllByType(MockRecentEntry)).toHaveLength(
Expand All @@ -383,14 +387,14 @@ describe("DataEntryTable", () => {
const vern = "vern";
const glossDef = "gloss";
const noteText = "note";
renderer.act(() => {
act(() => {
testHandle.props.setNewVern(vern);
testHandle.props.setNewGloss(glossDef);
testHandle.props.setNewNote(noteText);
});

// Trigger the function to add a new entry
await renderer.act(async () => {
await act(async () => {
try {
await testHandle.props.addNewEntry();
} catch {
Expand Down Expand Up @@ -426,7 +430,7 @@ describe("DataEntryTable", () => {
it("removes a recent entry", async () => {
await addRecentEntry();
const recentEntry = testRenderer.root.findByType(MockRecentEntry);
await renderer.act(async () => {
await act(async () => {
await recentEntry.props.removeEntry();
});
expect(testRenderer.root.findAllByType(MockRecentEntry)).toHaveLength(0);
Expand All @@ -449,7 +453,7 @@ describe("DataEntryTable", () => {

// Update the vernacular
const newVern = "not the vern generated in addRecentEntry";
await renderer.act(async () => {
await act(async () => {
await recentEntry.props.updateVern(newVern);
});

Expand Down
4 changes: 2 additions & 2 deletions src/components/Login/LoginPage/LoginComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
TextField,
Typography,
} from "@mui/material";
import React from "react";
import { Component } from "react";
import { withTranslation, WithTranslation } from "react-i18next";

import { BannerType } from "api/models";
Expand Down Expand Up @@ -57,7 +57,7 @@ interface LoginError {
}

/** The login page (also doubles as a logout page) */
export class Login extends React.Component<LoginProps, LoginState> {
export class Login extends Component<LoginProps, LoginState> {
constructor(props: LoginProps) {
super(props);
this.props.logout(); // Loading this page will reset the app, both store and localStorage
Expand Down
27 changes: 18 additions & 9 deletions src/components/Login/LoginPage/tests/LoginComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import renderer from "react-test-renderer";
import {
ReactTestInstance,
ReactTestRenderer,
act,
create,
} from "react-test-renderer";

import "tests/reactI18nextMock";

import Login from "components/Login/LoginPage/LoginComponent";

jest.mock("@matt-block/react-recaptcha-v2", () => () => (
<div id="mockRecaptcha">Recaptcha</div>
));
jest.mock(
"@matt-block/react-recaptcha-v2",
() =>
function MockRecaptcha() {
return <div id="mockRecaptcha">Recaptcha</div>;
}
);
jest.mock("backend", () => ({
getBannerText: () => Promise.resolve(""),
}));

jest.mock("browserRouter");
const LOGOUT = jest.fn();
let loginMaster: renderer.ReactTestRenderer;
let loginHandle: renderer.ReactTestInstance;
let loginMaster: ReactTestRenderer;
let loginHandle: ReactTestInstance;

const DATA = "stuff";
const MOCK_EVENT = { preventDefault: jest.fn(), target: { value: DATA } };

describe("Testing login component", () => {
beforeEach(() => {
renderer.act(() => {
loginMaster = renderer.create(<Login logout={LOGOUT} reset={LOGOUT} />);
beforeEach(async () => {
await act(async () => {
loginMaster = create(<Login logout={LOGOUT} reset={LOGOUT} />);
});
loginHandle = loginMaster.root.findByType(Login);
LOGOUT.mockClear();
Expand Down
Loading

0 comments on commit 42cc273

Please sign in to comment.