From d86f413c409b8ad7cc09e999d391faee264fa293 Mon Sep 17 00:00:00 2001 From: Kyle Holmberg Date: Mon, 23 Mar 2026 17:57:12 +0700 Subject: [PATCH 1/3] replace faker --- .../Form/__tests__/MultiStepForm.test.tsx | 66 +++++++++---------- package.json | 2 +- pnpm-lock.yaml | 17 ++--- test-utils/mockGenerators/mockUser.ts | 10 +-- types/index.d.ts | 1 - 5 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 types/index.d.ts diff --git a/components/Form/__tests__/MultiStepForm.test.tsx b/components/Form/__tests__/MultiStepForm.test.tsx index 3f18c4ef7..62085c49e 100644 --- a/components/Form/__tests__/MultiStepForm.test.tsx +++ b/components/Form/__tests__/MultiStepForm.test.tsx @@ -2,7 +2,7 @@ import { vi } from 'vitest'; import type { ReactElement } from 'react'; import { Component } from 'react'; -import faker from 'faker'; +import { faker } from '@faker-js/faker'; import get from 'lodash/get'; import { fireEvent, render, waitFor, getByTestId } from '@testing-library/react'; import { Field } from 'formik'; @@ -206,8 +206,8 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); expect(await findByTestId('ultimateAnswer')).not.toBeNull(); @@ -219,15 +219,15 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); typeIntoInput(await findByLabelText(/ultimate/gim), 'ultimateAnswer', '42'); await submitForm({ container }); - typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.random.number()); - typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.name.firstName()); + typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.number.int()); + typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.person.firstName()); await submitForm({ container, isFinalStep: true }); await waitFor(() => { @@ -241,8 +241,8 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); await waitFor(() => { @@ -256,8 +256,8 @@ describe('MultiStepForm', () => { expect(onEachStepSubmit).toHaveBeenCalledTimes(2); }); - typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.random.number()); - typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.name.firstName()); + typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.number.int()); + typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.person.firstName()); await submitForm({ container, isFinalStep: true }); await waitFor(() => { @@ -272,15 +272,15 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); typeIntoInput(await findByLabelText(/ultimate/gim), 'ultimateAnswer', '42'); await submitForm({ container }); - typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.random.number()); - typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.name.firstName()); + typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.number.int()); + typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.person.firstName()); await submitForm({ container, isFinalStep: true }); const alert = await findByRole('alert'); @@ -298,15 +298,15 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); typeIntoInput(await findByLabelText(/ultimate/gim), 'ultimateAnswer', '42'); await submitForm({ container }); - typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.random.number()); - typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.name.firstName()); + typeIntoInput(await findByLabelText(/number/gim), 'favoriteNumber', faker.number.int()); + typeIntoInput(await findByLabelText(/person/gim), 'favoritePerson', faker.person.firstName()); await submitForm({ container, isFinalStep: true }); const alert = await findByRole('alert'); @@ -325,15 +325,15 @@ describe('MultiStepForm', () => { expect(queryByRole('alert')).toBeNull(); - typeIntoInput(await findByLabelText(/first name/i), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/i), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/i), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/i), 'lastName', faker.person.lastName()); await submitForm({ container }); typeIntoInput(await findByLabelText(/ultimate/i), 'ultimateAnswer', '42'); await submitForm({ container }); - typeIntoInput(await findByLabelText(/number/i), 'favoriteNumber', faker.random.number()); - typeIntoInput(await findByLabelText(/person/i), 'favoritePerson', faker.name.firstName()); + typeIntoInput(await findByLabelText(/number/i), 'favoriteNumber', faker.number.int()); + typeIntoInput(await findByLabelText(/person/i), 'favoritePerson', faker.person.firstName()); await submitForm({ container, isFinalStep: true }); const alert = await findByRole('alert'); @@ -352,8 +352,8 @@ describe('MultiStepForm', () => { , ); - const firstNameValue = faker.name.firstName(); - const lastNameValue = faker.name.lastName(); + const firstNameValue = faker.person.firstName(); + const lastNameValue = faker.person.lastName(); typeIntoInput(await findByLabelText(/first name/gim), 'firstName', firstNameValue); typeIntoInput(await findByLabelText(/last name/gim), 'lastName', lastNameValue); @@ -386,8 +386,8 @@ describe('MultiStepForm', () => { , ); - const firstNameValue = faker.name.firstName(); - const lastNameValue = faker.name.lastName(); + const firstNameValue = faker.person.firstName(); + const lastNameValue = faker.person.lastName(); typeIntoInput(await findByLabelText(/first name/gim), 'firstName', firstNameValue); typeIntoInput(await findByLabelText(/last name/gim), 'lastName', lastNameValue); @@ -418,8 +418,8 @@ describe('MultiStepForm', () => { it('should call custom step handler after submitting', async () => { const { container, findByLabelText } = render(); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); await waitFor(() => { @@ -438,8 +438,8 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); await waitFor(() => { @@ -463,8 +463,8 @@ describe('MultiStepForm', () => { , ); - typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.name.firstName()); - typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.name.lastName()); + typeIntoInput(await findByLabelText(/first name/gim), 'firstName', faker.person.firstName()); + typeIntoInput(await findByLabelText(/last name/gim), 'lastName', faker.person.lastName()); await submitForm({ container }); await waitFor(() => { diff --git a/package.json b/package.json index 7e701084c..c9f970421 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "yup": "^1.7.1" }, "devDependencies": { + "@faker-js/faker": "^10.3.0", "@operation_code/eslint-plugin-custom-rules": "^1.0.1", "@playwright/test": "^1.56.1", "@storybook/addon-docs": "^10.3.1", @@ -102,7 +103,6 @@ "eslint-plugin-vitest": "^0.3.22", "eslint-plugin-vitest-globals": "^1.5.0", "express": "^4.18.1", - "faker": "^5.5.3", "file-loader": "^6.2.0", "husky": "^9.1.7", "identity-obj-proxy": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3d3394b5..2a0af9eb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: specifier: ^1.7.1 version: 1.7.1 devDependencies: + '@faker-js/faker': + specifier: ^10.3.0 + version: 10.3.0 '@operation_code/eslint-plugin-custom-rules': specifier: ^1.0.1 version: 1.0.1(lodash@4.17.21) @@ -210,9 +213,6 @@ importers: express: specifier: ^4.18.1 version: 4.21.2 - faker: - specifier: ^5.5.3 - version: 5.5.3 file-loader: specifier: ^6.2.0 version: 6.2.0(webpack@5.102.1(esbuild@0.25.12)) @@ -1136,6 +1136,10 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@10.3.0': + resolution: {integrity: sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==} + engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} + '@fastify/otel@0.17.1': resolution: {integrity: sha512-K4wyxfUZx2ux5o+b6BtTqouYFVILohLZmSbA2tKUueJstNcBnoGPVhllCaOvbQ3ZrXdUxUC/fyrSWSCqHhdOPg==} peerDependencies: @@ -4040,9 +4044,6 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - faker@5.5.3: - resolution: {integrity: sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -7828,6 +7829,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@faker-js/faker@10.3.0': {} + '@fastify/otel@0.17.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -11151,8 +11154,6 @@ snapshots: transitivePeerDependencies: - supports-color - faker@5.5.3: {} - fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} diff --git a/test-utils/mockGenerators/mockUser.ts b/test-utils/mockGenerators/mockUser.ts index 090175d14..f9efb6173 100644 --- a/test-utils/mockGenerators/mockUser.ts +++ b/test-utils/mockGenerators/mockUser.ts @@ -1,12 +1,12 @@ -import faker from 'faker'; +import { faker } from '@faker-js/faker'; import type { RegistrationFormValues } from 'components/Forms/RegistrationForm/RegistrationForm'; export function mockUser(desiredEmail?: string): RegistrationFormValues { - const firstName = faker.name.firstName() as string; - const lastName = faker.name.lastName() as string; + const firstName = faker.person.firstName(); + const lastName = faker.person.lastName(); const email = - desiredEmail || (faker.internet.email(firstName, lastName, 'operationcode.org') as string); - const zipcode = `${faker.address.zipCode()}`; // force to be string + desiredEmail || faker.internet.email({ firstName, lastName, provider: 'operationcode.org' }); + const zipcode = `${faker.location.zipCode()}`; const user = { email, diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 248824c5a..000000000 --- a/types/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'faker'; From 5ebac67419a8cf739c931515d2126e3f13e6e328 Mon Sep 17 00:00:00 2001 From: Kyle Holmberg Date: Mon, 23 Mar 2026 20:25:19 +0700 Subject: [PATCH 2/3] Migrate to ESLint 9 flat config + update Prettier --- .eslintignore | 10 - .eslintrc.js | 305 --- .lintstagedrc | 2 +- .storybook/main.ts | 4 +- .storybook/preview.js | 2 +- .vscode/settings.json | 9 +- app/about/page.tsx | 2 +- app/api/registration/update/route.ts | 6 +- app/branding/page.tsx | 2 +- app/challenge/page.tsx | 14 +- app/chapters/page.tsx | 6 +- app/contact/page.tsx | 8 +- app/corporate-training/page.tsx | 24 +- app/donate/page.tsx | 4 +- app/get_involved/page.tsx | 24 +- app/history/page.tsx | 4 +- app/jobs/page.tsx | 2 +- app/join/JoinContent.tsx | 6 +- app/join/success/page.tsx | 2 +- app/page.tsx | 6 +- app/podcast/page.tsx | 10 +- app/policy/page.tsx | 14 +- app/press/page.tsx | 6 +- app/project_rebuild/page.tsx | 4 +- .../code_platoon/CodePlatoonContent.tsx | 2 +- app/scholarship/page.tsx | 22 +- app/services/page.tsx | 10 +- app/slack_guide/page.tsx | 6 +- app/sponsorship/page.tsx | 14 +- app/team/page.tsx | 4 +- app/thank_you/ThankYouContent.tsx | 2 +- common/config/svgo.js | 1 - common/constants/partners.ts | 2 +- common/styles/globals.css | 8 +- common/utils/api-utils.ts | 17 +- common/utils/array-utils.ts | 2 +- common/utils/cva.ts | 3 +- common/utils/next-utils.js | 2 +- components/Accordion/Accordion.tsx | 10 +- .../__stories__/Accordion.stories.tsx | 2 +- components/Alert/Alert.tsx | 16 +- .../Alert/__stories__/Alert.stories.tsx | 2 +- .../__snapshots__/Alert.test.tsx.snap | 2 +- components/Analytics/AnalyticsProvider.tsx | 8 +- .../Badge/__stories__/Badge.stories.tsx | 6 +- .../Branding/ColorSection/ColorSection.tsx | 2 +- .../__snapshots__/ColorSection.test.tsx.snap | 32 +- .../Branding/FontSection/FontSection.tsx | 24 +- .../__snapshots__/FontSection.test.tsx.snap | 14 +- .../Branding/LogoSection/LogoSection.tsx | 16 +- .../__snapshots__/LogoSection.test.tsx.snap | 18 +- components/Branding/Swatch/Swatch.tsx | 4 +- .../__snapshots__/Swatch.test.tsx.snap | 4 +- components/Buttons/Button/Button.tsx | 23 +- .../Button/__stories__/Button.stories.tsx | 2 +- .../__snapshots__/Button.test.tsx.snap | 4 +- .../Buttons/CloseButton/CloseButton.tsx | 5 +- .../__stories__/CloseButton.stories.tsx | 4 +- .../__snapshots__/CloseButton.test.tsx.snap | 2 +- .../__stories__/LinkButton.stories.tsx | 2 +- .../__snapshots__/LinkButton.test.tsx.snap | 4 +- components/Cards/Card/Card.tsx | 4 +- .../Cards/Card/__stories__/Card.stories.tsx | 2 +- .../__snapshots__/Card.test.tsx.snap | 4 +- components/Cards/FlatCard/FlatCard.tsx | 12 +- .../FlatCard/__stories__/FlatCard.stories.tsx | 2 +- .../__snapshots__/FlatCard.test.tsx.snap | 12 +- components/Cards/ImageCard/ImageCard.tsx | 8 +- .../__stories__/ImageCard.stories.tsx | 2 +- .../__snapshots__/ImageCard.test.tsx.snap | 10 +- components/Cards/ValueCard/ValueCard.tsx | 2 +- .../__stories__/ValueCard.stories.tsx | 2 +- .../__snapshots__/ValueCard.test.tsx.snap | 2 +- components/Container/Container.tsx | 2 +- .../__stories__/Container.stories.tsx | 2 +- .../__snapshots__/Container.test.tsx.snap | 2 +- components/Content/Content.tsx | 4 +- .../Content/__stories__/Content.stories.tsx | 2 +- components/Content/__tests__/Content.test.tsx | 1 - .../__snapshots__/Content.test.tsx.snap | 16 +- components/Drawer/Drawer.tsx | 6 +- .../Drawer/__stories__/Drawer.stories.tsx | 2 +- .../__snapshots__/Drawer.test.tsx.snap | 8 +- components/ErrorDisplay/ErrorDisplay.tsx | 2 +- .../__snapshots__/ErrorDisplay.test.tsx.snap | 2 +- .../FeaturedJobItem/FeaturedJobItem.tsx | 6 +- .../__stories__/FeaturedJobItem.stories.tsx | 2 +- .../FeaturedJobItem.test.tsx.snap | 14 +- components/Footer/Footer.tsx | 16 +- .../__snapshots__/Footer.test.tsx.snap | 14 +- components/Form/Checkbox/Checkbox.tsx | 12 +- components/Form/Input/Input.tsx | 16 +- .../Form/Input/__stories__/Input.stories.tsx | 4 +- .../Form/Input/__tests__/Input.test.tsx | 4 +- .../__snapshots__/Input.test.tsx.snap | 2 +- .../Form/Label/__stories__/Label.stories.tsx | 2 +- components/Form/MultiStepForm.tsx | 21 +- components/Form/Select/SelectMulti.tsx | 16 +- components/Form/Select/SelectSingle.tsx | 18 +- components/Form/Select/ThemedReactSelect.tsx | 20 +- .../__stories__/SelectMulti.stories.tsx | 8 +- .../__stories__/SelectSingle.stories.tsx | 8 +- .../Select/__tests__/SelectMulti.test.tsx | 4 +- .../Select/__tests__/SelectSingle.test.tsx | 4 +- .../__tests__/ThemedReactSelect.test.tsx | 2 +- .../Form/__tests__/MultiStepForm.test.tsx | 3 +- .../RegistrationForm/RegistrationForm.tsx | 10 +- .../__stories__/RegistrationForm.stories.tsx | 2 +- .../__tests__/RegistrationForm.test.tsx | 8 +- .../RegistrationForm.test.tsx.snap | 36 +- .../UpdateProfileForm/UpdateProfileForm.tsx | 12 +- .../UpdateProfileForm.test.tsx.snap | 10 +- .../steps/MilitaryDetails.tsx | 4 +- .../steps/MilitaryStatus.tsx | 2 +- .../steps/__tests__/MilitaryDetails.test.tsx | 2 +- .../steps/__tests__/MilitaryStatus.test.tsx | 2 +- .../steps/__tests__/PersonalDetails.test.tsx | 2 +- .../__tests__/ProfessionalDetails.test.tsx | 2 +- .../MilitaryStatus.test.tsx.snap | 2 +- .../ProfessionalDetails.test.tsx.snap | 4 +- components/Heading/Heading.tsx | 16 +- .../Heading/__stories__/Heading.stories.tsx | 2 +- .../__snapshots__/Heading.test.tsx.snap | 12 +- components/HeroBanner/HeroBanner.tsx | 4 +- .../__stories__/HeroBanner.stories.tsx | 2 +- .../__snapshots__/HeroBanner.test.tsx.snap | 10 +- components/InlineLoadingSpinner.tsx | 2 +- components/Modal/Modal.tsx | 4 +- .../Modal/__stories__/Modal.stories.tsx | 4 +- .../__snapshots__/Modal.test.tsx.snap | 6 +- components/Nav/Nav.tsx | 16 +- components/Nav/NavListItem/NavListItem.tsx | 24 +- .../__tests__/NavListItem.test.tsx | 2 +- .../__snapshots__/NavListItem.test.tsx.snap | 4 +- components/Nav/NavMobile/NavMobile.tsx | 20 +- .../NavMobile/__tests__/NavMobile.test.tsx | 2 +- .../__snapshots__/NavMobile.test.tsx.snap | 2 +- .../__tests__/__snapshots__/Nav.test.tsx.snap | 8 +- components/OutboundLink/OutboundLink.tsx | 2 +- .../__stories__/OutboundLink.stories.tsx | 2 +- .../__snapshots__/OutboundLink.test.tsx.snap | 2 +- .../PartnerLogoLink/PartnerLogoLink.tsx | 2 +- .../__stories__/PartnerLogoLink.stories.tsx | 2 +- components/Press/PressLinks/PressLinks.tsx | 10 +- .../__snapshots__/PressLinks.test.tsx.snap | 22 +- components/Press/PressPhotos/PressPhotos.tsx | 6 +- .../__snapshots__/PressPhotos.test.tsx.snap | 2 +- components/Press/PressVideos/PressVideos.tsx | 4 +- .../__snapshots__/PressVideos.test.tsx.snap | 2 +- components/Press/index.ts | 5 - .../__stories__/ProgressIndicator.stories.tsx | 2 +- .../__stories__/DonateSection.stories.tsx | 2 +- .../__snapshots__/DonateSection.test.tsx.snap | 12 +- .../__snapshots__/JoinSection.test.tsx.snap | 14 +- .../SponsorsSection/SponsorsSection.tsx | 12 +- .../__tests__/SponsorsSection.test.tsx | 2 +- .../SponsorsSection.test.tsx.snap | 22 +- .../__stories__/ScreenReaderOnly.stories.tsx | 4 +- .../ScrollToTopButton/ScrollToTopButton.tsx | 8 +- .../ScrollToTopButton.test.tsx.snap | 4 +- components/SocialMedia/SocialMedia.tsx | 2 +- .../__snapshots__/SocialMedia.test.tsx.snap | 2 +- .../__stories__/SuccessStory.stories.tsx | 2 +- .../__snapshots__/SuccessStory.test.tsx.snap | 8 +- components/Timeline/Timeline.tsx | 12 +- .../Timeline/TimelineEvent/TimelineEvent.tsx | 2 +- .../__snapshots__/TimelineEvent.test.tsx.snap | 2 +- .../Timeline/TimelineNav/TimelineNav.tsx | 6 +- .../__snapshots__/TimelineNav.test.tsx.snap | 16 +- .../__snapshots__/Timeline.test.tsx.snap | 108 +- e2e/hashlink.spec.ts | 4 +- e2e/join.spec.ts | 2 +- eslint.config.mjs | 480 ++++ jsconfig.json | 21 - next.config.js | 2 +- package.json | 47 +- pnpm-lock.yaml | 2130 +++++++++-------- prettier.config.js | 6 +- test-utils/createSnapshotTest.js | 2 +- 179 files changed, 2364 insertions(+), 1955 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js delete mode 100644 components/Press/index.ts create mode 100644 eslint.config.mjs delete mode 100644 jsconfig.json diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index eb0660506..000000000 --- a/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -package.json -pnpm-lock.yaml -.next -.github -bin -static -cypress-coverage -vitest-coverage -.storybook-dist diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 8f59b3990..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @type {import("eslint").Linter.Config} - */ -module.exports = { - ignorePatterns: ['next-env.d.ts'], - extends: [ - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:react/recommended', - 'plugin:jsx-a11y/recommended', - 'plugin:storybook/recommended', - 'plugin:prettier/recommended', - 'prettier', - ], - env: { - browser: true, - commonjs: true, - es6: true, - node: true, - }, - parser: '@typescript-eslint/parser', - plugins: ['unicorn', '@operation_code/custom-rules', 'import', 'lodash', '@typescript-eslint'], - rules: { - // Import Rules - 'import/extensions': [ - 'error', - 'never', - { - css: 'always', - jpg: 'always', - json: 'always', - png: 'always', - svg: 'always', - stories: 'always', - }, - ], - 'import/no-unresolved': 'off', - 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], - 'import/order': [ - 'error', - { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'] }, - ], - 'import/prefer-default-export': 'off', - - // OC eslint Plugin Rules - '@operation_code/custom-rules/proptype-definition-above-fn': 'error', - - // JSX-A11Y Plugin Rules - 'jsx-a11y/anchor-is-valid': [ - 'error', - { - components: ['Link'], - specialLink: ['hrefLeft', 'hrefRight'], - aspects: ['invalidHref', 'preferButton'], - }, - ], - 'jsx-a11y/label-has-associated-control': [ - 2, - { - labelComponents: ['Label'], - labelAttributes: ['for'], - controlComponents: ['Input', 'Select'], - }, - ], - - // Lodash Plugin Rules - 'lodash/import-scope': ['error', 'method'], - - // React Plugin Rules - 'react/function-component-definition': [ - 'error', - { - namedComponents: ['arrow-function', 'function-declaration'], - unnamedComponents: ['arrow-function', 'function-expression'], - }, - ], - 'react/forbid-prop-types': ['error', { forbid: ['any'] }], - 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }], - 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.tsx'] }], - 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], - 'react/no-unescaped-entities': 'off', - 'react/jsx-no-target-blank': 'off', // browsers protect against this vulnerability now - 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], - 'react/jsx-one-expression-per-line': 'off', - 'react/jsx-props-no-spreading': ['off'], - 'react/no-did-mount-set-state': 'off', - 'react/no-unused-prop-types': 'error', - 'react/no-unused-state': 'error', - 'react/prefer-stateless-function': ['off'], - 'react/react-in-jsx-scope': 'off', - 'react/state-in-constructor': ['error', 'never'], - 'react/static-property-placement': ['off'], - - // Unicorn Plugin Rules - 'unicorn/catch-error-name': 'error', - 'unicorn/error-message': 'error', - 'unicorn/no-abusive-eslint-disable': 'error', - 'unicorn/no-fn-reference-in-iterator': 'error', - 'unicorn/no-for-loop': 'error', - 'unicorn/no-unreadable-array-destructuring': 'error', - 'unicorn/no-zero-fractions': 'error', - 'unicorn/prefer-includes': 'error', - 'unicorn/prefer-node-append': 'error', - 'unicorn/prefer-node-remove': 'error', - 'unicorn/prefer-query-selector': 'error', - 'unicorn/prefer-spread': 'error', - 'unicorn/prefer-starts-ends-with': 'error', - 'unicorn/prefer-text-content': 'error', - 'unicorn/prefer-type-error': 'error', - 'unicorn/throw-new-error': 'error', - - // Vanilla ESLint Rules - 'arrow-body-style': 'off', - 'class-methods-use-this': 'off', - 'comma-dangle': ['error', 'only-multiline'], - 'implicit-arrow-linebreak': 'off', - 'multiline-ternary': 'off', - 'no-console': 'warn', - 'no-extra-boolean-cast': 'off', - 'no-promise-executor-return': 'off', - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'react-select', - message: 'Please use `components/Form/Select/ThemedReactSelect` instead.', - }, - { - name: 'prop-types', - importNames: ['default'], - message: `Please use named imports of "prop-types".\n Example: "import { func } from 'prop-types';"`, - }, - { - name: 'formik', - importNames: ['Form'], - message: `Please use our Form component to have good defaults defined.\n "import Form from 'components/Form/Form';"`, - }, - { - name: 'react', - importNames: ['default'], - message: 'React is globally availble for all page files.', - }, - { - name: 'tailwind-merge', - importNames: ['twMerge'], - message: - 'Please import `cx` from `common/utils/cva.ts` instead of directly from tailwind-merge.', - }, - { - name: 'class-variance-authority', - importNames: ['cx', 'cva'], - message: - 'Please import from `common/utils/cva.ts` instead of directly from class-variance-authority.', - }, - ], - }, - ], - 'no-use-before-define': 'off', - }, - overrides: [ - { - files: ['./**/*.test.js', './**/*.test.jsx', './**/*.test.ts', './**/*.test.tsx'], - plugins: ['vitest'], - extends: ['plugin:vitest-globals/recommended', 'plugin:vitest/recommended'], - env: { - 'vitest-globals/env': true, - }, - rules: { - 'vitest/consistent-test-it': ['error', { fn: 'it', withinDescribe: 'it' }], - 'vitest/expect-expect': [ - 'error', - { assertFunctionNames: ['expect', 'createSnapshotTest'] }, - ], - 'vitest/prefer-lowercase-title': ['error', { ignore: ['describe'] }], - 'vitest/no-test-prefixes': 'error', - 'vitest/no-test-return-statement': 'error', - 'vitest/prefer-strict-equal': 'error', - 'vitest/valid-describe-callback': 'error', - }, - }, - { - files: ['./**/*.ts', './**/*.tsx'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: true, - }, - extends: ['plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic'], - rules: { - // Deactivate rules not meant for TS - 'no-restricted-imports': 'off', - - // React Plugin Rules - 'react/prop-types': 'off', // https://github.com/jsx-eslint/eslint-plugin-react/issues/3651 - 'react/no-array-index-key': 'off', - 'react/require-default-props': 'off', - - // Typescript Rules - '@typescript-eslint/consistent-type-imports': ['error'], - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'variable', - types: ['boolean'], - format: ['PascalCase', 'UPPER_CASE'], - prefix: [ - 'is', - 'are', - 'was', - 'should', - 'has', - 'can', - 'did', - 'will', - 'IS_', - 'ARE_', - 'WAS_', - 'SHOULD_', - 'HAS_', - 'CAN_', - 'DID_', - 'WILL_', - ], - }, - ], - '@typescript-eslint/no-empty-interface': ['error', { allowSingleExtends: true }], - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'react-select', - message: 'Please use `components/Form/Select/ThemedReactSelect` instead.', - }, - { - name: 'formik', - importNames: ['Form'], - message: `Please use our Form component to have good defaults defined.\n "import { Form } from 'components/Form/Form';"`, - }, - { - name: 'react', - importNames: ['default'], - message: 'React is globally availble for all page files.', - }, - { - name: 'tailwind-merge', - importNames: ['twMerge'], - message: - 'Please import `cx` from `common/utils/cva.ts` instead of directly from tailwind-merge.', - }, - { - name: 'class-variance-authority', - importNames: ['cx', 'cva'], - message: - 'Please import from `common/utils/cva.ts` instead of directly from class-variance-authority.', - }, - ], - }, - ], - '@typescript-eslint/no-unused-vars': ['error', { vars: 'all', varsIgnorePattern: '_' }], - '@typescript-eslint/unbound-method': 'off', // gives false negatives in arrow funcs - }, - }, - { - files: ['./e2e/**/*.spec.ts'], - extends: 'plugin:playwright/recommended', - rules: { - 'func-names': 'off', - 'vitest/expect-expect': 'off', - 'vitest/valid-expect': 'off', - 'no-unused-expressions': ['off'], - 'playwright/expect-expect': [ - 'warn', - { - assertFunctionNames: ['expect', 'assertError', 'assertFailedLogin'], - }, - ], - }, - }, - { - files: ['./**/*.test.ts', './**/*.test.tsx'], - rules: { - '@typescript-eslint/no-non-null-assertion': 'off', - }, - }, - { - files: ['./app/api/**/*.ts'], - rules: { - 'no-console': 'off', - }, - }, - { - files: ['components/nav.js', 'components/Timeline/historyData.js'], - rules: { - 'react/react-in-jsx-scope': 'off', - }, - }, - { - files: ['components/nav.js', 'components/Footer/Footer.js'], - rules: { 'jsx-a11y/anchor-is-valid': 'off' }, - }, - ], - root: true, -}; diff --git a/.lintstagedrc b/.lintstagedrc index 49717b314..ec5bddca6 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,4 +1,4 @@ { - "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"], + "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix --no-warn-ignored"], "*.css": ["prettier --write"] } diff --git a/.storybook/main.ts b/.storybook/main.ts index e6ef8e523..1b2bc2509 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -12,10 +12,10 @@ const config: StorybookConfig = { name: '@storybook/nextjs', options: {}, }, - webpackFinal: async config => { + webpackFinal: async (config) => { // Find the Storybook Webpack rule relevant to SVG files. // @ts-expect-error => 'config.module' is possibly 'undefined'.ts(18048) - const imageRule = config.module.rules.find(rule => { + const imageRule = config.module.rules.find((rule) => { // @ts-expect-error => 'rule' is possibly 'null' or 'undefined'.ts(18049) if (rule.test && rule.test.test('.svg')) { console.log({ rule }); diff --git a/.storybook/preview.js b/.storybook/preview.js index d88ee72f8..0d734b316 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -2,7 +2,7 @@ import 'common/styles/globals.css'; import { MINIMAL_VIEWPORTS, INITIAL_VIEWPORTS } from 'storybook/viewport'; export const decorators = [ - Story => ( + (Story) => (
diff --git a/.vscode/settings.json b/.vscode/settings.json index 62e7c2cad..4e22e86ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,6 @@ { - "typescript.tsdk": "node_modules/typescript/lib", + "js/ts.tsdk.path": "node_modules/typescript/lib", "tailwindCSS.classAttributes": ["className"], "tailwindCSS.classFunctions": ["cx", "cva", "clsx", "classMerge", "twMerge"], - "tailwindCSS.lint.cssConflict": "ignore", - "typescript.preferences.autoImportFileExcludePatterns": [ - "class-variance-authority", - "clsx", - "tailwind-merge" - ] + "tailwindCSS.lint.cssConflict": "ignore" } diff --git a/app/about/page.tsx b/app/about/page.tsx index a73168041..286d13435 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -16,7 +16,7 @@ function About() {
diff --git a/app/api/registration/update/route.ts b/app/api/registration/update/route.ts index 7073b1077..edf16a531 100644 --- a/app/api/registration/update/route.ts +++ b/app/api/registration/update/route.ts @@ -34,9 +34,9 @@ export async function PATCH(request: NextRequest) { finalize: shouldFinalize, } = (await request.json()) as Partial & { finalize?: boolean }; - const branchOfService = selectedBranchOfServiceOptions?.map(option => option.value) ?? []; - const ethnicity = selectedEthnicityOptions?.map(option => option.value) ?? []; - const joinReason = selectedJoinReasonOptions?.map(option => option.value) ?? []; + const branchOfService = selectedBranchOfServiceOptions?.map((option) => option.value) ?? []; + const ethnicity = selectedEthnicityOptions?.map((option) => option.value) ?? []; + const joinReason = selectedJoinReasonOptions?.map((option) => option.value) ?? []; let militaryBranch = branchOfService; if (militaryAffiliation?.includes('spouse')) { diff --git a/app/branding/page.tsx b/app/branding/page.tsx index d8d87dea4..ba56316f6 100644 --- a/app/branding/page.tsx +++ b/app/branding/page.tsx @@ -16,7 +16,7 @@ function Branding() { <> -
+
Introduction

diff --git a/app/challenge/page.tsx b/app/challenge/page.tsx index 681b07190..84f7a3f1a 100644 --- a/app/challenge/page.tsx +++ b/app/challenge/page.tsx @@ -1,11 +1,11 @@ import type { Metadata } from 'next'; +import range from 'lodash/range'; +import Image from 'next/image'; import { s3 } from 'common/constants/urls'; import HeroBanner from 'components/HeroBanner/HeroBanner'; import Content from 'components/Content/Content'; import OutboundLink from 'components/OutboundLink/OutboundLink'; import challengers from 'static/operationcode_challenge/names'; -import range from 'lodash/range'; -import Image from 'next/image'; export const metadata: Metadata = { title: 'Challenge' }; @@ -29,9 +29,9 @@ export const NamesColumns = () => {

    - {namesInColumn.map(name => ( + {namesInColumn.map((name) => (
  1. {name}
  2. ))}
@@ -59,7 +59,7 @@ function Challenge() { theme="white" columns={[
-
    +
    1. Firstly,{' '} @@ -124,7 +124,7 @@ function Challenge() { screenshot of what the screen should look like on GitHub thus far @@ -207,7 +207,7 @@ function Challenge() {
      Here is a list of the people that have completed this before you:
      -
      +
      ]} />
      , diff --git a/app/chapters/page.tsx b/app/chapters/page.tsx index cb58fe694..9f3149ace 100644 --- a/app/chapters/page.tsx +++ b/app/chapters/page.tsx @@ -46,10 +46,10 @@ function Chapters() { theme="white" title="Locations" columns={[ -
      - {chapterLocations.map(chapter => { +
      + {chapterLocations.map((chapter) => { return ( - +
      @@ -27,7 +27,7 @@ function Contact() {

      -
      +
      You can also reach us via email:
      @@ -41,7 +41,7 @@ function Contact() {
      -
      +
      Connect with us on LinkedIn:
      @@ -54,7 +54,7 @@ function Contact() {
      -
      +
      And - if you really want to - you can snail-mail us:
      diff --git a/app/corporate-training/page.tsx b/app/corporate-training/page.tsx index 6115506a6..f75a2338b 100644 --- a/app/corporate-training/page.tsx +++ b/app/corporate-training/page.tsx @@ -1,8 +1,8 @@ import Image from 'next/image'; import type { Metadata } from 'next'; +import type { ReactNode } from 'react'; import HeroBanner from 'components/HeroBanner/HeroBanner'; import OutboundLink from 'components/OutboundLink/OutboundLink'; -import type { ReactNode } from 'react'; import { cx } from 'common/utils/cva'; export const metadata: Metadata = { title: 'Corporate Training: Breaking Biases' }; @@ -110,7 +110,7 @@ const CorporateTraining = () => { return ( <> @@ -128,24 +128,24 @@ const CorporateTraining = () => { > Partnerships Team - .We look forward to hearing from you! + .We look forward to hearing from you!

      -
        - {biases.map(bias => ( +
          + {biases.map((bias) => (
        1. *]:flex-1', + `flex flex-col-reverse flex-wrap md:flex-row md:flex-nowrap md:*:flex-1 md:even:flex-row-reverse`, 'even:bg-secondary even:text-white', - 'md:[&:nth-child(1n)]:bg-white md:[&:nth-child(2n)]:bg-theme-gray-800 md:[&:nth-child(3n)]:bg-secondary md:[&:nth-child(1n)]:text-secondary md:[&:nth-child(3n)]:text-white', + `md:nth-[1n]:bg-white md:nth-[1n]:text-secondary md:nth-[2n]:bg-theme-gray-800 md:nth-[3n]:bg-secondary md:nth-[3n]:text-white`, )} >
          -
          -

          +
          +

          {bias.title} - {bias.subtitle} + {bias.subtitle}

          {bias.description}

          @@ -154,8 +154,8 @@ const CorporateTraining = () => {
          diff --git a/app/donate/page.tsx b/app/donate/page.tsx index 4526dd77d..bc648641c 100644 --- a/app/donate/page.tsx +++ b/app/donate/page.tsx @@ -13,7 +13,7 @@ function DonatePage() { -
          +

          Why Donate?

          @@ -61,7 +61,7 @@ function DonatePage() {