diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfd95fb5d..356da7c17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,42 @@ env: CACHE_NUMBER: 1 jobs: + unit-test-frontend: + name: Frontend linting / unit tests + # Defines the type of runner the job runs on + runs-on: ubuntu-latest + steps: + - name: Checkout to the repository + uses: actions/checkout@v3 + - name: Set up NodeJS environment + uses: actions/setup-node@v3 + with: + node-version: '14' + # Consider this as an add on to optimize the execution of actions + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install package dependencies + run: npm install + working-directory: ./frontend2 + - name: Check linting and formatting + # Custom script for checking the linting and formatting being in place + run: npm run lint + working-directory: ./frontend2 + # Run test cases and this could ensure minimum coverage as well if set + - name: Execute test cases + run: npm run test + working-directory: ./frontend2 lint: name: Linter (pre-commit) runs-on: ubuntu-latest @@ -40,8 +76,8 @@ jobs: run: | pre-commit install pre-commit run -a - unit-test: - name: Unit tests (django) + unit-test-backend: + name: Backend unit tests runs-on: ubuntu-latest permissions: contents: read diff --git a/frontend2/.eslintrc.js b/frontend2/.eslintrc.js index b6bef5f20..4a8b759d8 100644 --- a/frontend2/.eslintrc.js +++ b/frontend2/.eslintrc.js @@ -10,6 +10,7 @@ module.exports = { "plugin:react/recommended", "eslint:recommended", "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", "prettier", ], ignorePatterns: [ @@ -28,6 +29,7 @@ module.exports = { plugins: ["react"], rules: { indent: ["error", 2], + semi: "error", // require semicolons ending statements }, settings: { react: { diff --git a/frontend2/src/components/EpisodeLayout.tsx b/frontend2/src/components/EpisodeLayout.tsx index 41b9ba710..0086f13fe 100644 --- a/frontend2/src/components/EpisodeLayout.tsx +++ b/frontend2/src/components/EpisodeLayout.tsx @@ -14,7 +14,7 @@ const EpisodeLayout: React.FC = () => { } return (
- +
diff --git a/frontend2/src/components/sidebar/SidebarItem.tsx b/frontend2/src/components/sidebar/SidebarItem.tsx index 85c4e01f3..80d5d9ff2 100644 --- a/frontend2/src/components/sidebar/SidebarItem.tsx +++ b/frontend2/src/components/sidebar/SidebarItem.tsx @@ -7,13 +7,8 @@ interface SidebarItemProps { linkTo: string; } -const SidebarItem: React.FC = ({ - icon, - text, - linkTo, -}) => { - const baseStyle = - "text-base flex items-center gap-3 "; +const SidebarItem: React.FC = ({ icon, text, linkTo }) => { + const baseStyle = "text-base flex items-center gap-3 "; const colorVariants = { gray: "text-gray-800 hover:text-gray-400", color: "text-teal", diff --git a/frontend2/src/components/sidebar/SidebarSection.tsx b/frontend2/src/components/sidebar/SidebarSection.tsx index a99a91d64..cf3e61642 100644 --- a/frontend2/src/components/sidebar/SidebarSection.tsx +++ b/frontend2/src/components/sidebar/SidebarSection.tsx @@ -1,17 +1,19 @@ import React from "react"; interface SidebarSectionProps { - children?: React.ReactNode, + children?: React.ReactNode; title?: string; } const SidebarSection: React.FC = ({ children, title }) => { return (
- {title !== undefined &&

{title}

} -
- {children} -
+ {title !== undefined && ( +

+ {title} +

+ )} +
{children}
); }; diff --git a/frontend2/src/components/sidebar/__test__/Sidebar.test.tsx b/frontend2/src/components/sidebar/__test__/Sidebar.test.tsx index 99ebe9f69..487590334 100644 --- a/frontend2/src/components/sidebar/__test__/Sidebar.test.tsx +++ b/frontend2/src/components/sidebar/__test__/Sidebar.test.tsx @@ -5,23 +5,43 @@ import { DEFAULT_EPISODE } from "../../../utils/constants"; import { EpisodeContext } from "../../../contexts/EpisodeContext"; import { MemoryRouter } from "react-router-dom"; -test('UI: should link to default episode', () => { - render(); - const linkElement = screen.getByText('Resources').closest('a')?.getAttribute('href'); - expect(linkElement).toEqual(expect.stringContaining(`/${DEFAULT_EPISODE}/resources`)); +test("UI: should link to default episode", () => { + render( + + + + ); + const linkElement = screen + .getByText("Resources") + .closest("a") + ?.getAttribute("href"); + expect(linkElement).toEqual( + expect.stringContaining(`/${DEFAULT_EPISODE}/resources`) + ); }); -test('UI: should collapse sidebar', () => { - render(); - expect(screen.queryByText('Home')).toBeNull(); +test("UI: should collapse sidebar", () => { + render( + + + + ); + expect(screen.queryByText("Home")).toBeNull(); }); -test('UI: should link to episode in surrounding context', () => { - render( - undefined }}> - - +test("UI: should link to episode in surrounding context", () => { + render( + + undefined }} + > + + + ); - const linkElement = screen.getByText('Resources').closest('a')?.getAttribute('href'); + const linkElement = screen + .getByText("Resources") + .closest("a") + ?.getAttribute("href"); expect(linkElement).toEqual(expect.stringContaining(`/something/resources`)); }); diff --git a/frontend2/src/components/sidebar/index.tsx b/frontend2/src/components/sidebar/index.tsx index be668c857..ae25e3862 100644 --- a/frontend2/src/components/sidebar/index.tsx +++ b/frontend2/src/components/sidebar/index.tsx @@ -2,9 +2,15 @@ import React, { useContext } from "react"; import SidebarSection from "./SidebarSection"; import SidebarItem from "./SidebarItem"; import { - ClipboardDocumentIcon, HomeIcon, MapIcon, - TrophyIcon, ChartBarIcon, ClockIcon, - UserGroupIcon, ArrowUpTrayIcon, PlayCircleIcon, + ClipboardDocumentIcon, + HomeIcon, + MapIcon, + TrophyIcon, + ChartBarIcon, + ClockIcon, + UserGroupIcon, + ArrowUpTrayIcon, + PlayCircleIcon, } from "@heroicons/react/24/outline"; import { EpisodeContext } from "../../contexts/EpisodeContext"; @@ -12,72 +18,66 @@ interface SidebarProps { collapsed?: boolean; } -const Sidebar: React.FC = ({ - collapsed -}) => { +const Sidebar: React.FC = ({ collapsed }) => { collapsed = collapsed ?? false; const { episodeId } = useContext(EpisodeContext); const linkBase = `/${episodeId}/`; - return ( - collapsed ? null : ( -
- - } - text="Home" - linkTo={`${linkBase}home`} - /> - } - text="Quick Start" - linkTo={`${linkBase}quickstart`} - /> - } - text="Resources" - linkTo={`${linkBase}resources`} - /> - - - } - text="Tournaments" - linkTo={`${linkBase}tournaments`} - /> - } - text="Rankings" - linkTo={`${linkBase}rankings`} - /> - } - text="Queue" - linkTo={`${linkBase}queue`} - /> - - - } - text="My Team" - linkTo={`${linkBase}team`} - /> - } - text="Submissions" - linkTo={`${linkBase}submission`} - /> - } - text="Scrimmaging" - linkTo={`${linkBase}scrimmaging`} - /> - -
- ) + return collapsed ? null : ( +
+ + } + text="Home" + linkTo={`${linkBase}home`} + /> + } + text="Quick Start" + linkTo={`${linkBase}quickstart`} + /> + } + text="Resources" + linkTo={`${linkBase}resources`} + /> + + + } + text="Tournaments" + linkTo={`${linkBase}tournaments`} + /> + } + text="Rankings" + linkTo={`${linkBase}rankings`} + /> + } + text="Queue" + linkTo={`${linkBase}queue`} + /> + + + } + text="My Team" + linkTo={`${linkBase}team`} + /> + } + text="Submissions" + linkTo={`${linkBase}submission`} + /> + } + text="Scrimmaging" + linkTo={`${linkBase}scrimmaging`} + /> + +
); - - }; export default Sidebar; diff --git a/frontend2/src/index.tsx b/frontend2/src/index.tsx index c71eaec78..cfd778490 100644 --- a/frontend2/src/index.tsx +++ b/frontend2/src/index.tsx @@ -1,6 +1,5 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App";