diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 30e0488f1..e2dd789f0 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -14,6 +14,17 @@ module.exports = { // dependencies to work in standalone mode. It may be overkill for most projects at // Nava which aren't image heavy. "@next/next/no-img-element": "off", + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["../"], + message: "Relative imports are not allowed.", + }, + ], + }, + ], }, // Additional lint rules. These get layered onto the top-level rules. overrides: [ diff --git a/frontend/.storybook/I18nStoryWrapper.tsx b/frontend/.storybook/I18nStoryWrapper.tsx index f05ca54a0..f988f2571 100644 --- a/frontend/.storybook/I18nStoryWrapper.tsx +++ b/frontend/.storybook/I18nStoryWrapper.tsx @@ -3,12 +3,11 @@ * @see https://storybook.js.org/docs/writing-stories/decorators */ import { StoryContext } from "@storybook/react"; +import { defaultLocale, formats, timeZone } from "src/i18n/config"; import { NextIntlClientProvider } from "next-intl"; import React from "react"; -import { defaultLocale, formats, timeZone } from "../src/i18n/config"; - const I18nStoryWrapper = ( Story: React.ComponentType, context: StoryContext, diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx index 9b135fadc..653b9358c 100644 --- a/frontend/.storybook/preview.tsx +++ b/frontend/.storybook/preview.tsx @@ -4,10 +4,11 @@ */ import { Loader, Preview } from "@storybook/react"; -import "../src/styles/styles.scss"; +import "src/styles/styles.scss"; + +import { defaultLocale, locales } from "src/i18n/config"; +import { getMessagesWithFallbacks } from "src/i18n/getMessagesWithFallbacks"; -import { defaultLocale, locales } from "../src/i18n/config"; -import { getMessagesWithFallbacks } from "../src/i18n/getMessagesWithFallbacks"; import I18nStoryWrapper from "./I18nStoryWrapper"; const parameters = { diff --git a/frontend/src/app/[locale]/opportunity/[id]/page.tsx b/frontend/src/app/[locale]/opportunity/[id]/page.tsx index ab1c3ac17..0cb334a98 100644 --- a/frontend/src/app/[locale]/opportunity/[id]/page.tsx +++ b/frontend/src/app/[locale]/opportunity/[id]/page.tsx @@ -1,4 +1,6 @@ import { Metadata } from "next"; +import OpportunityListingAPI from "src/app/api/OpportunityListingAPI"; +import NotFound from "src/app/not-found"; import { OPPORTUNITY_CRUMBS } from "src/constants/breadcrumbs"; import withFeatureFlag from "src/hoc/search/withFeatureFlag"; import { @@ -17,8 +19,6 @@ import OpportunityHistory from "src/components/opportunity/OpportunityHistory"; import OpportunityIntro from "src/components/opportunity/OpportunityIntro"; import OpportunityLink from "src/components/opportunity/OpportunityLink"; import OpportunityStatusWidget from "src/components/opportunity/OpportunityStatusWidget"; -import OpportunityListingAPI from "../../../api/OpportunityListingAPI"; -import NotFound from "../../../not-found"; export async function generateMetadata() { const t = await getTranslations({ locale: "en" }); diff --git a/frontend/src/app/api/SearchOpportunityAPI.ts b/frontend/src/app/api/SearchOpportunityAPI.ts index 07d0f30c7..54ec78654 100644 --- a/frontend/src/app/api/SearchOpportunityAPI.ts +++ b/frontend/src/app/api/SearchOpportunityAPI.ts @@ -1,6 +1,6 @@ import "server-only"; -import { QueryParamData } from "../../services/search/searchfetcher/SearchFetcher"; +import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher"; import { PaginationOrderBy, PaginationRequestBody, @@ -8,7 +8,8 @@ import { SearchFetcherActionType, SearchFilterRequestBody, SearchRequestBody, -} from "../../types/search/searchRequestTypes"; +} from "src/types/search/searchRequestTypes"; + import BaseApi from "./BaseApi"; export default class SearchOpportunityAPI extends BaseApi { diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts index f689992a9..4eb93dac5 100644 --- a/frontend/src/app/sitemap.ts +++ b/frontend/src/app/sitemap.ts @@ -1,6 +1,5 @@ import { MetadataRoute } from "next"; - -import { getNextRoutes } from "../utils/getRoutes"; +import { getNextRoutes } from "src/utils/getRoutes"; export default function sitemap(): MetadataRoute.Sitemap { const routes = getNextRoutes("./src/app"); diff --git a/frontend/src/components/content/FundingContent.tsx b/frontend/src/components/content/FundingContent.tsx index b72dc07be..6a95fc05b 100644 --- a/frontend/src/components/content/FundingContent.tsx +++ b/frontend/src/components/content/FundingContent.tsx @@ -3,7 +3,7 @@ import { nofoPdfs } from "src/constants/nofoPdfs"; import { useTranslations } from "next-intl"; import { Grid, GridContainer } from "@trussworks/react-uswds"; -import NofoImageLink from "../../components/NofoImageLink"; +import NofoImageLink from "src/components/NofoImageLink"; const FundingContent = () => { const t = useTranslations("Index"); diff --git a/frontend/src/components/content/IndexGoalContent.tsx b/frontend/src/components/content/IndexGoalContent.tsx index 04a829a70..080209da3 100644 --- a/frontend/src/components/content/IndexGoalContent.tsx +++ b/frontend/src/components/content/IndexGoalContent.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { Button, Grid } from "@trussworks/react-uswds"; import ContentLayout from "src/components/ContentLayout"; -import { USWDSIcon } from "../USWDSIcon"; +import { USWDSIcon } from "src/components/USWDSIcon"; const IndexGoalContent = () => { const t = useTranslations("Index"); diff --git a/frontend/src/hoc/search/withFeatureFlag.tsx b/frontend/src/hoc/search/withFeatureFlag.tsx index d59036cd8..6957ba8d2 100644 --- a/frontend/src/hoc/search/withFeatureFlag.tsx +++ b/frontend/src/hoc/search/withFeatureFlag.tsx @@ -1,10 +1,10 @@ +import { FeatureFlagsManager } from "src/services/FeatureFlagManager"; +import { ServerSideSearchParams } from "src/types/searchRequestURLTypes"; + import { cookies } from "next/headers"; import { notFound } from "next/navigation"; import React, { ComponentType } from "react"; -import { FeatureFlagsManager } from "../../services/FeatureFlagManager"; -import { ServerSideSearchParams } from "../../types/searchRequestURLTypes"; - type WithFeatureFlagProps = { searchParams: ServerSideSearchParams; }; diff --git a/frontend/src/hooks/useFeatureFlags.ts b/frontend/src/hooks/useFeatureFlags.ts index e37076138..35756b4bc 100644 --- a/frontend/src/hooks/useFeatureFlags.ts +++ b/frontend/src/hooks/useFeatureFlags.ts @@ -1,9 +1,8 @@ import Cookies from "js-cookie"; +import { FeatureFlagsManager } from "src/services/FeatureFlagManager"; import { useEffect, useState } from "react"; -import { FeatureFlagsManager } from "../services/FeatureFlagManager"; - /** * React hook for reading and managing feature flags in client-side code. * diff --git a/frontend/src/services/FeatureFlagManager.ts b/frontend/src/services/FeatureFlagManager.ts index 99fcf6db1..29f9fbcc8 100644 --- a/frontend/src/services/FeatureFlagManager.ts +++ b/frontend/src/services/FeatureFlagManager.ts @@ -4,12 +4,11 @@ import { CookiesStatic } from "js-cookie"; import { featureFlags } from "src/constants/featureFlags"; +import { ServerSideSearchParams } from "src/types/searchRequestURLTypes"; import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; import { NextRequest, NextResponse } from "next/server"; -import { ServerSideSearchParams } from "../types/searchRequestURLTypes"; - export type FeatureFlags = { [name: string]: boolean }; // Parity with unexported getServerSideProps context cookie type export type NextServerSideCookies = Partial<{ diff --git a/frontend/src/services/search/SearchFilterManager.ts b/frontend/src/services/search/SearchFilterManager.ts index 555f24f2f..3358eab5d 100644 --- a/frontend/src/services/search/SearchFilterManager.ts +++ b/frontend/src/services/search/SearchFilterManager.ts @@ -1,5 +1,6 @@ -import { FilterOption } from "../../components/search/SearchFilterAccordion/SearchFilterAccordion"; -import { QueryParamKey } from "../../types/search/searchResponseTypes"; +import { QueryParamKey } from "src/types/search/searchResponseTypes"; + +import { FilterOption } from "src/components/search/SearchFilterAccordion/SearchFilterAccordion"; type UpdateQueryParamsFunction = ( checkedSet: Set, diff --git a/frontend/src/services/search/searchfetcher/APISearchFetcher.ts b/frontend/src/services/search/searchfetcher/APISearchFetcher.ts index ee9397901..92c53db9b 100644 --- a/frontend/src/services/search/searchfetcher/APISearchFetcher.ts +++ b/frontend/src/services/search/searchfetcher/APISearchFetcher.ts @@ -1,7 +1,8 @@ import "server-only"; -import SearchOpportunityAPI from "../../../app/api/SearchOpportunityAPI"; -import { SearchAPIResponse } from "../../../types/search/searchResponseTypes"; +import SearchOpportunityAPI from "src/app/api/SearchOpportunityAPI"; +import { SearchAPIResponse } from "src/types/search/searchResponseTypes"; + import { QueryParamData, SearchFetcher } from "./SearchFetcher"; export class APISearchFetcher extends SearchFetcher { diff --git a/frontend/src/services/search/searchfetcher/MockSearchFetcher.ts b/frontend/src/services/search/searchfetcher/MockSearchFetcher.ts index 7d19be0b3..b72c2c826 100644 --- a/frontend/src/services/search/searchfetcher/MockSearchFetcher.ts +++ b/frontend/src/services/search/searchfetcher/MockSearchFetcher.ts @@ -1,7 +1,8 @@ import "server-only"; -import mockData from "../../../app/api/mock/APIMockResponse.json"; -import { SearchAPIResponse } from "../../../types/search/searchResponseTypes"; +import mockData from "src/app/api/mock/APIMockResponse.json"; +import { SearchAPIResponse } from "src/types/search/searchResponseTypes"; + import { SearchFetcher } from "./SearchFetcher"; export class MockSearchFetcher extends SearchFetcher { diff --git a/frontend/src/utils/opportunity/isSummary.ts b/frontend/src/utils/opportunity/isSummary.ts index b881d14f6..d72e79c65 100644 --- a/frontend/src/utils/opportunity/isSummary.ts +++ b/frontend/src/utils/opportunity/isSummary.ts @@ -1,4 +1,4 @@ -import { Summary } from "../../types/opportunity/opportunityResponseTypes"; +import { Summary } from "src/types/opportunity/opportunityResponseTypes"; export function isSummary(value: unknown): value is Summary { if (typeof value === "object" && value !== null) { diff --git a/frontend/src/utils/search/convertSearchParamsToProperTypes.ts b/frontend/src/utils/search/convertSearchParamsToProperTypes.ts index 3c4cbd6f3..a8789bcf4 100644 --- a/frontend/src/utils/search/convertSearchParamsToProperTypes.ts +++ b/frontend/src/utils/search/convertSearchParamsToProperTypes.ts @@ -1,6 +1,6 @@ -import { QueryParamData } from "../../services/search/searchfetcher/SearchFetcher"; -import { SearchFetcherActionType } from "../../types/search/searchRequestTypes"; -import { ServerSideSearchParams } from "../../types/searchRequestURLTypes"; +import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher"; +import { SearchFetcherActionType } from "src/types/search/searchRequestTypes"; +import { ServerSideSearchParams } from "src/types/searchRequestURLTypes"; // Search params (query string) coming from the request URL into the server // can be a string, string[], or undefined. diff --git a/frontend/stories/pages/search.stories.tsx b/frontend/stories/pages/search.stories.tsx index b3bfed661..eeeace677 100644 --- a/frontend/stories/pages/search.stories.tsx +++ b/frontend/stories/pages/search.stories.tsx @@ -1,6 +1,5 @@ import { Meta } from "@storybook/react"; - -import Search from "../../src/app/[locale]/search/page"; +import Search from "src/app/[locale]/search/page"; const meta: Meta = { title: "Pages/Search", diff --git a/frontend/tests/api/BaseApi.test.ts b/frontend/tests/api/BaseApi.test.ts index c2c46b242..05c71f91b 100644 --- a/frontend/tests/api/BaseApi.test.ts +++ b/frontend/tests/api/BaseApi.test.ts @@ -1,9 +1,8 @@ import "server-only"; +import BaseApi, { ApiMethod, JSONRequestBody } from "src/app/api/BaseApi"; import { NetworkError, UnauthorizedError } from "src/errors"; -import BaseApi, { ApiMethod, JSONRequestBody } from "../../src/app/api/BaseApi"; - // Define a concrete implementation of BaseApi for testing class TestApi extends BaseApi { get basePath(): string { diff --git a/frontend/tests/api/SearchOpportunityApi.test.ts b/frontend/tests/api/SearchOpportunityApi.test.ts index f2512b811..96247f5e6 100644 --- a/frontend/tests/api/SearchOpportunityApi.test.ts +++ b/frontend/tests/api/SearchOpportunityApi.test.ts @@ -1,6 +1,6 @@ -import SearchOpportunityAPI from "../../src/app/api/SearchOpportunityAPI"; -import { QueryParamData } from "../../src/services/search/searchfetcher/SearchFetcher"; -import { SearchRequestBody } from "../../src/types/search/searchRequestTypes"; +import SearchOpportunityAPI from "src/app/api/SearchOpportunityAPI"; +import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher"; +import { SearchRequestBody } from "src/types/search/searchRequestTypes"; // mockFetch should match the SearchAPIResponse type structure const mockFetch = ({ diff --git a/frontend/tests/components/Spinner.test.tsx b/frontend/tests/components/Spinner.test.tsx index 254ff5bfd..6fc9b959d 100644 --- a/frontend/tests/components/Spinner.test.tsx +++ b/frontend/tests/components/Spinner.test.tsx @@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react"; import React from "react"; -import Spinner from "../../src/components/Spinner"; +import Spinner from "src/components/Spinner"; describe("Spinner Component", () => { test("renders with correct attributes", () => { diff --git a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount.test.tsx b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount.test.tsx index e4f3546a8..f2be30368 100644 --- a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount.test.tsx +++ b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount.test.tsx @@ -5,7 +5,7 @@ import { render, screen } from "tests/react-utils"; import React from "react"; -import SectionLinkCount from "../../../../../src/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount"; +import SectionLinkCount from "src/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkCount"; describe("SectionLinkCount", () => { it("should not have basic accessibility issues", async () => { diff --git a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel.test.tsx b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel.test.tsx index 44d9885ed..10bfbf211 100644 --- a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel.test.tsx +++ b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel.test.tsx @@ -5,8 +5,8 @@ import { render, screen } from "tests/react-utils"; import React from "react"; -import { FilterOption } from "../../../../../src/components/search/SearchFilterAccordion/SearchFilterAccordion"; -import SectionLinkLabel from "../../../../../src/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel"; +import { FilterOption } from "src/components/search/SearchFilterAccordion/SearchFilterAccordion"; +import SectionLinkLabel from "src/components/search/SearchFilterAccordion/SearchFilterSection/SectionLinkLabel"; // Mock the Icon component from "@trussworks/react-uswds" jest.mock("@trussworks/react-uswds", () => ({ diff --git a/frontend/tests/services/FeatureFlagManager.test.ts b/frontend/tests/services/FeatureFlagManager.test.ts index 7ee40fc5d..d24395356 100644 --- a/frontend/tests/services/FeatureFlagManager.test.ts +++ b/frontend/tests/services/FeatureFlagManager.test.ts @@ -3,15 +3,14 @@ */ import Cookies from "js-cookie"; - -import { NextRequest, NextResponse } from "next/server"; - -import { FeatureFlagsManager } from "../../src/services/FeatureFlagManager"; -import { mockProcessEnv } from "../utils/commonTestUtils"; +import { FeatureFlagsManager } from "src/services/FeatureFlagManager"; +import { mockProcessEnv } from "tests/utils/commonTestUtils"; import { mockDefaultFeatureFlags, mockFeatureFlagsCookie, -} from "../utils/FeatureFlagTestUtils"; +} from "tests/utils/FeatureFlagTestUtils"; + +import { NextRequest, NextResponse } from "next/server"; describe("FeatureFlagsManager", () => { const COOKIE_VALUE = { feature1: true }; diff --git a/frontend/tests/utils/FeatureFlagTestUtils.ts b/frontend/tests/utils/FeatureFlagTestUtils.ts index fb09a7b74..1d7c277ce 100644 --- a/frontend/tests/utils/FeatureFlagTestUtils.ts +++ b/frontend/tests/utils/FeatureFlagTestUtils.ts @@ -12,7 +12,7 @@ import { FeatureFlags, FeatureFlagsManager, -} from "../../src/services/FeatureFlagManager"; +} from "src/services/FeatureFlagManager"; /** * Mock feature flags cookie in `window.document` so that we don't need to mock diff --git a/frontend/tests/utils/getRoutes.test.ts b/frontend/tests/utils/getRoutes.test.ts index 7ac733495..46a775413 100644 --- a/frontend/tests/utils/getRoutes.test.ts +++ b/frontend/tests/utils/getRoutes.test.ts @@ -1,9 +1,9 @@ -import { getNextRoutes, listPaths } from "../../src/utils/getRoutes"; +import { getNextRoutes, listPaths } from "src/utils/getRoutes"; /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -jest.mock("../../src/utils/getRoutes", () => { - const originalModule = jest.requireActual("../../src/utils/getRoutes"); +jest.mock("src/utils/getRoutes", () => { + const originalModule = jest.requireActual("src/utils/getRoutes"); return { ...originalModule, listPaths: jest.fn(), diff --git a/frontend/tests/utils/isSummary.test.ts b/frontend/tests/utils/isSummary.test.ts index a154c80a7..8f9178d64 100644 --- a/frontend/tests/utils/isSummary.test.ts +++ b/frontend/tests/utils/isSummary.test.ts @@ -1,5 +1,5 @@ -import { Summary } from "../../src/types/opportunity/opportunityResponseTypes"; -import { isSummary } from "../../src/utils/opportunity/isSummary"; +import { Summary } from "src/types/opportunity/opportunityResponseTypes"; +import { isSummary } from "src/utils/opportunity/isSummary"; describe("isSummary", () => { it("should return true for a valid Summary object", () => {