Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions .eslintrc.json

This file was deleted.

16 changes: 13 additions & 3 deletions cypress/e2e/accountGeneral.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ describe('Account General Page', () => {
const password = 'IloveOrangeCones123!';
const email = 'cypressTestUser@mobilitydata.org';
beforeEach(() => {
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');
cy.createNewUserAndSignIn(email, password);
cy.visit('/');
cy.injectAuthenticatedUser(email);
cy.get('[data-cy="accountHeader"]').should('exist').click();
cy.location('pathname').should('eq', '/account');
});
Expand Down Expand Up @@ -47,7 +47,17 @@ describe('Account General Page', () => {
it('should save updated full name and organization', () => {
cy.intercept('PUT', '**/v1/user', {
statusCode: 200,
body: {},
body: {
id: 'rcxs3svpuWf7CkZpSU7mGOHszh22',
email: 'cypressTestUser@mobilitydata.org',
full_name: 'Updated Name',
legacy_org_name: 'Updated Organization',
email_verified: null,
is_registered_to_receive_api_announcements: false,
features: [],
created_at: '2026-06-04T18:04:56.511967Z',
updated_at: '2026-07-10T13:11:26.556462Z',
},
}).as('updateUser');

cy.contains('button', 'Edit').click();
Expand Down
7 changes: 4 additions & 3 deletions cypress/e2e/addFeedForm.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ describe('Add Feed Form', () => {
result: { message: 'Data written to the new sheet successfully!' },
},
});
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');

cy.createNewUserAndSignIn(
'cypressTestUser@mobilitydata.org',
'BigCoolPassword123!',
);

const email = 'cypressTestUser@mobilitydata.org';
cy.visit('/');
cy.injectAuthenticatedUser(email);
cy.get('[data-cy="accountHeader"]').should('exist'); // assures that the user is signed in
cy.get('[data-cy="header-add-a-feed"]').click();
// Assures that the firebase remote config has loaded for the first test
Expand Down
28 changes: 22 additions & 6 deletions cypress/e2e/changepassword.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ const currentPassword = 'IloveOrangeCones123!';
const newPassword = currentPassword + 'TEST';
const email = 'cypressTestUser@mobilitydata.org';

// tests are too flaky, to revisit
describe.skip('Change Password Screen', () => {
describe('Change Password Screen', () => {
beforeEach(() => {
cy.visit('/');
cy.get('[data-testid="home-title"]').should('exist');
cy.createNewUserAndSignIn(email, currentPassword);
cy.visit('/');
cy.injectAuthenticatedUser(email);
cy.get('[data-cy="accountHeader"]').should('exist').click(); // assures that the user is signed in
cy.get('[data-cy="changePasswordButtonAccount"]').should('exist').click();
});
Expand All @@ -28,7 +27,21 @@ describe.skip('Change Password Screen', () => {
).should('exist');
});

it('should change password', () => {
it.only('should change password', () => {
cy.intercept('GET', '**/v1/user', {
statusCode: 200,
body: {
id: 'rcxs3svpuWf7CkZpSU7mGOHszh22',
email: 'cypressTestUser@mobilitydata.org',
full_name: 'Updated Name',
legacy_org_name: 'Updated Organization',
email_verified: null,
is_registered_to_receive_api_announcements: false,
features: [],
created_at: '2026-06-04T18:04:56.511967Z',
updated_at: '2026-07-10T13:11:26.556462Z',
},
}).as('updateUser');
cy.intercept('POST', '/retrieveUserInformation', {
statusCode: 200,
body: {
Expand All @@ -53,7 +66,10 @@ describe.skip('Change Password Screen', () => {
// logout
cy.get('[data-cy="mobileNavTrigger"]').click();
cy.get('[data-cy="mobile-signOutButton"]').click();
cy.get('[data-cy="confirmSignOutButton"]').should('exist').should('not.be.disabled').click();
cy.get('[data-cy="confirmSignOutButton"]')
.should('exist')
.should('not.be.disabled')
.click();
cy.visit('/sign-in');
cy.get('[data-cy="signInEmailInput"]').type(email);
cy.get('[data-cy="signInPasswordInput"]').type(newPassword);
Expand Down
6 changes: 4 additions & 2 deletions cypress/e2e/signin.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ describe('Sign In page', () => {

describe('redirect after sign-in', () => {
it('should redirect to the contribute page when signed in with add_feed=true', () => {
cy.createNewUserAndSignIn(email, password);
cy.visit('/sign-in?add_feed=true');
cy.injectAuthenticatedUser(email);
cy.get('[data-testid=signin]').should('exist');
cy.createNewUserAndSignIn(email, password);
cy.location('pathname', { timeout: 10000 }).should('eq', '/contribute');
});

it('should redirect to the redirect_to path after sign-in', () => {
const redirectPath = '/feeds';
cy.createNewUserAndSignIn(email, password);
cy.visit(`/sign-in?redirect_to=${encodeURIComponent(redirectPath)}`);
cy.injectAuthenticatedUser(email);
cy.get('[data-testid=signin]').should('exist');
cy.createNewUserAndSignIn(email, password);
cy.location('pathname', { timeout: 10000 }).should('eq', redirectPath);
});

Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/signup.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('Sign up full registration flow', () => {
// Create a real Firebase emulator user and sign in so that the
// complete-registration saga can obtain an access token, but leave
// Redux state untouched so each test can set the exact status it needs.
cy.createNewFirebaseUser(email, password);
cy.createNewUserAndSignIn(email, password);
});

it('should redirect verify-email → complete-registration → contribute when signed up with add_feed=true', () => {
Expand Down
27 changes: 4 additions & 23 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,33 +91,14 @@ Cypress.Commands.add(
(email: string, password: string) => {
const auth = app.auth();
cy.then(async () => {
// Sign out first so the SDK drops its cached token before we wipe
// emulator accounts — prevents stale background accounts:lookup calls
await auth.signOut();
await fetch(
'http://localhost:9099/emulator/v1/projects/mobility-feeds-dev/accounts',
{ method: 'DELETE' },
);
await auth.createUserWithEmailAndPassword(email, password);
await auth.signInWithEmailAndPassword(email, password);
cy.injectAuthenticatedUser(email);
});
},
);

/**
* Wipes Firebase auth emulator accounts, creates a new user and signs in
* via Firebase, but does NOT inject any Redux state. Use this when a test
* needs Firebase auth active but controls the Redux state itself.
*/
Cypress.Commands.add(
'createNewFirebaseUser',
(email: string, password: string) => {
const auth = app.auth();
cy.then(async () => {
await fetch(
'http://localhost:9099/emulator/v1/projects/mobility-feeds-dev/accounts',
{ method: 'DELETE' },
);
await auth.createUserWithEmailAndPassword(email, password);
await auth.signInWithEmailAndPassword(email, password);
await auth.createUserWithEmailAndPassword(email, password).then(res => console.log('User created:', res));
});
},
);
8 changes: 0 additions & 8 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@ declare global {
* @param password password of the new user
*/
createNewUserAndSignIn(email: string, password: string): void;

/**
* Wipes the firebase auth emulator, creates a user and signs in via Firebase only.
* Does NOT inject any Redux state — the test controls Redux state directly.
* @param email email of the new user
* @param password password of the new user
*/
createNewFirebaseUser(email: string, password: string): void;
}
}
}
71 changes: 71 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import next from 'eslint-config-next/core-web-vitals';
import prettierRecommended from 'eslint-plugin-prettier/recommended';

export default tseslint.config(
{
ignores: [
'dist/',
'build/',
'out/',
'node_modules/',
'.next/',
'coverage/',
'**/*.config.js',
'**/*.config.mjs',
'next-env.d.ts',
],
},
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked.map((c) => ({ ...c, files: ['**/*.{ts,tsx}'] })),
// next[1] (next/typescript) is skipped — it re-declares @typescript-eslint,
// conflicting with tseslint.configs.recommended above, which we use instead
// for TypeScript 6 compatibility and the full recommended ruleset.
(({ languageOptions: _lang, ...rest }) => rest)(next[0]),
next[3], // core-web-vitals: promotes no-html-link-for-pages + no-sync-scripts to errors
{
files: ['**/*.{ts,tsx,js,jsx}'],
languageOptions: {
parserOptions: { projectService: true },
},
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'none',
varsIgnorePattern: '^_',
caughtErrors: 'none',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
// TODO: hooks called inside render callbacks, incorrect hook usage patterns,
// and missing/extra effect dependencies — to be fixed in a separate ticket.
'react-hooks/rules-of-hooks': 'off',
'react-hooks/refs': 'off',
'react-hooks/set-state-in-render': 'off',
'react-hooks/set-state-in-effect': 'off',
'react-hooks/immutability': 'off',
'react-hooks/exhaustive-deps': 'off',
// TypeScript handles these; disable the core JS versions.
'no-undef': 'off',
'no-unused-vars': 'off',
// Type-checked rules from recommendedTypeChecked that are too noisy
// for the current codebase — kept off to preserve parity with the
// previous eslint-config-standard-with-typescript baseline.
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
},
prettierRecommended,
);
35 changes: 8 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@
"start:dev": "next dev",
"start:dev:mock": "NEXT_PUBLIC_API_MOCKING=enabled next dev -p 3001",
"start:prod": "next build && next start",
"lint": "eslint src --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint src --ext .ts,.tsx,.js,.jsx --fix",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "CI=true jest",
"e2e:setup": "next build && concurrently -k -n \"next,firebase\" -c \"cyan,magenta\" \"NEXT_PUBLIC_API_MOCKING=enabled next start -p 3001\" \"firebase emulators:start --only auth --project mobility-feeds-dev\"",
"e2e:setup:dev": "concurrently -k -n \"next,firebase\" -c \"cyan,magenta\" \"NEXT_PUBLIC_API_MOCKING=enabled FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 next dev -p 3001\" \"firebase emulators:start --only auth --project mobility-feeds-dev\"",
"e2e:setup": "next build && concurrently -k -n \"next,firebase\" -c \"cyan,magenta\" \"NEXT_PUBLIC_API_MOCKING=enabled FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 next start -p 3001\" \"firebase emulators:start --only auth --project mobility-feeds-dev\"",
"e2e:run": "CYPRESS_BASE_URL=http://localhost:3001 cypress run",
"e2e:open": "CYPRESS_BASE_URL=http://localhost:3001 cypress open",
"firebase:auth:emulator:dev": "firebase emulators:start --only auth --project mobility-feeds-dev",
Expand All @@ -76,19 +77,6 @@
"resolutions": {
"tar": "^7.5.7"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"devDependencies": {
"@react-firebase/auth": "^0.2.10",
"@swc/core": "^1.15.8",
Expand All @@ -107,22 +95,14 @@
"@types/react-redux": "^7.1.27",
"@types/react-window": "^2.0.0",
"@types/redux-saga": "^0.10.5",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"concurrently": "^9.2.1",
"cypress": "^13.2.0",
"cypress": "^15.18.1",
"dotenv": "^16.4.5",
"env-cmd": "^10.1.0",
"eslint": "^8.49.0",
"eslint": "^9.35.0",
"eslint-config-next": "^16.1.2",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^39.0.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^16.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-unused-imports": "^3.0.0",
"firebase-tools": "^15.3.1",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
Expand All @@ -132,7 +112,8 @@
"prettier": "^3.0.3",
"ts-loader": "^9.4.4",
"ts-node": "^10.9.2",
"typescript": "^5.2.2",
"typescript": "6.0.3",
"typescript-eslint": "^8.58.1",
"wait-on": "^7.0.1"
},
"engines": {
Expand Down
14 changes: 6 additions & 8 deletions src/app/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,7 @@ declare module '@mui/material/styles' {
interface TypeText {
lightContrast?: string;
}
}

declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
sectionTitle: true;
}
}

declare module '@mui/material/styles/createMixins' {
// Allow for custom mixins to be added
interface Mixins {
code: Partial<PaletteColor> & {
Expand All @@ -32,6 +24,12 @@ declare module '@mui/material/styles/createMixins' {
}
}

declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
sectionTitle: true;
}
}

export enum ThemeModeEnum {
light = 'light',
dark = 'dark',
Expand Down
Loading
Loading