From fd50bac545d04fc11a037526bd863aa6e218b410 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Fri, 2 Aug 2024 17:22:30 -0400 Subject: [PATCH 01/15] use lowercase with route directory names --- client/app/routes.tsx | 10 +++++----- client/app/routes/{About => about}/About.tsx | 0 client/app/routes/{About => about}/index.ts | 0 .../routes/{Dashboard => dashboard}/Dashboard.spec.tsx | 2 +- .../app/routes/{Dashboard => dashboard}/Dashboard.tsx | 0 client/app/routes/{Dashboard => dashboard}/index.ts | 2 +- client/app/routes/{Login => login}/Login.tsx | 0 client/app/routes/{Login => login}/index.ts | 0 client/app/routes/{Login => login}/login.spec.tsx | 2 +- client/app/routes/{Profile => profile}/Profile.tsx | 0 client/app/routes/{Profile => profile}/index.ts | 0 .../routes/{RegisterHero => register}/RegisterHero.tsx | 0 client/app/routes/{RegisterHero => register}/index.ts | 2 +- 13 files changed, 9 insertions(+), 9 deletions(-) rename client/app/routes/{About => about}/About.tsx (100%) rename client/app/routes/{About => about}/index.ts (100%) rename client/app/routes/{Dashboard => dashboard}/Dashboard.spec.tsx (95%) rename client/app/routes/{Dashboard => dashboard}/Dashboard.tsx (100%) rename client/app/routes/{Dashboard => dashboard}/index.ts (51%) rename client/app/routes/{Login => login}/Login.tsx (100%) rename client/app/routes/{Login => login}/index.ts (100%) rename client/app/routes/{Login => login}/login.spec.tsx (92%) rename client/app/routes/{Profile => profile}/Profile.tsx (100%) rename client/app/routes/{Profile => profile}/index.ts (100%) rename client/app/routes/{RegisterHero => register}/RegisterHero.tsx (100%) rename client/app/routes/{RegisterHero => register}/index.ts (50%) diff --git a/client/app/routes.tsx b/client/app/routes.tsx index 6c5fbe4fc..6c392e01d 100644 --- a/client/app/routes.tsx +++ b/client/app/routes.tsx @@ -1,13 +1,13 @@ import { ErrorPage } from '~/routes/ErrorPage/ErrorPage'; -import { Login } from '~/routes/Login'; +import { Login } from 'app/routes/login'; import React from 'react'; import { createBrowserRouter } from 'react-router-dom'; -const Dashboard = React.lazy(() => import('~/routes/Dashboard')); -const Profile = React.lazy(() => import('~/routes/Profile')); +const Dashboard = React.lazy(() => import('app/routes/dashboard')); +const Profile = React.lazy(() => import('app/routes/profile')); const SiteList = React.lazy(() => import('~/routes/SiteList')); const SiteDetails = React.lazy(() => import('~/routes/SiteDetails')); -const Help = React.lazy(() => import('~/routes/About')); +const Help = React.lazy(() => import('app/routes/about')); export const router = createBrowserRouter([ { @@ -81,7 +81,7 @@ export const router = createBrowserRouter([ }, { path: '/register', - lazy: () => import('./routes/RegisterHero'), + lazy: () => import('./routes/register'), }, { path: '*', diff --git a/client/app/routes/About/About.tsx b/client/app/routes/about/About.tsx similarity index 100% rename from client/app/routes/About/About.tsx rename to client/app/routes/about/About.tsx diff --git a/client/app/routes/About/index.ts b/client/app/routes/about/index.ts similarity index 100% rename from client/app/routes/About/index.ts rename to client/app/routes/about/index.ts diff --git a/client/app/routes/Dashboard/Dashboard.spec.tsx b/client/app/routes/dashboard/Dashboard.spec.tsx similarity index 95% rename from client/app/routes/Dashboard/Dashboard.spec.tsx rename to client/app/routes/dashboard/Dashboard.spec.tsx index 324574a16..40c454908 100644 --- a/client/app/routes/Dashboard/Dashboard.spec.tsx +++ b/client/app/routes/dashboard/Dashboard.spec.tsx @@ -1,5 +1,5 @@ import '@testing-library/jest-dom'; -import { Dashboard } from '~/routes/Dashboard/Dashboard'; +import { Dashboard } from './Dashboard'; import { setupServer } from 'msw/node'; import React, { createElement } from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/routes/Dashboard/Dashboard.tsx b/client/app/routes/dashboard/Dashboard.tsx similarity index 100% rename from client/app/routes/Dashboard/Dashboard.tsx rename to client/app/routes/dashboard/Dashboard.tsx diff --git a/client/app/routes/Dashboard/index.ts b/client/app/routes/dashboard/index.ts similarity index 51% rename from client/app/routes/Dashboard/index.ts rename to client/app/routes/dashboard/index.ts index 1db2db83c..732f5b956 100644 --- a/client/app/routes/Dashboard/index.ts +++ b/client/app/routes/dashboard/index.ts @@ -1,4 +1,4 @@ -import { Dashboard } from '~/routes/Dashboard/Dashboard'; +import { Dashboard } from './Dashboard'; export { Dashboard as Component }; export default Dashboard; diff --git a/client/app/routes/Login/Login.tsx b/client/app/routes/login/Login.tsx similarity index 100% rename from client/app/routes/Login/Login.tsx rename to client/app/routes/login/Login.tsx diff --git a/client/app/routes/Login/index.ts b/client/app/routes/login/index.ts similarity index 100% rename from client/app/routes/Login/index.ts rename to client/app/routes/login/index.ts diff --git a/client/app/routes/Login/login.spec.tsx b/client/app/routes/login/login.spec.tsx similarity index 92% rename from client/app/routes/Login/login.spec.tsx rename to client/app/routes/login/login.spec.tsx index ff8c39ff6..8291b4aba 100644 --- a/client/app/routes/Login/login.spec.tsx +++ b/client/app/routes/login/login.spec.tsx @@ -1,7 +1,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import { renderWithProviders, screen } from 'app/mocks'; -import { Login } from '~/routes/Login'; +import { Login } from 'app/routes/login'; import { describe, expect, test } from 'vitest'; describe('Login component', () => { diff --git a/client/app/routes/Profile/Profile.tsx b/client/app/routes/profile/Profile.tsx similarity index 100% rename from client/app/routes/Profile/Profile.tsx rename to client/app/routes/profile/Profile.tsx diff --git a/client/app/routes/Profile/index.ts b/client/app/routes/profile/index.ts similarity index 100% rename from client/app/routes/Profile/index.ts rename to client/app/routes/profile/index.ts diff --git a/client/app/routes/RegisterHero/RegisterHero.tsx b/client/app/routes/register/RegisterHero.tsx similarity index 100% rename from client/app/routes/RegisterHero/RegisterHero.tsx rename to client/app/routes/register/RegisterHero.tsx diff --git a/client/app/routes/RegisterHero/index.ts b/client/app/routes/register/index.ts similarity index 50% rename from client/app/routes/RegisterHero/index.ts rename to client/app/routes/register/index.ts index 38dbc2fbf..53e17bec4 100644 --- a/client/app/routes/RegisterHero/index.ts +++ b/client/app/routes/register/index.ts @@ -1,4 +1,4 @@ -import { RegisterHero } from '~/routes/RegisterHero/RegisterHero'; +import { RegisterHero } from '~/routes/register/RegisterHero'; export { RegisterHero as Component }; export default RegisterHero; From 01113b9278b2774229ccd414a72585464e078f9d Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sat, 3 Aug 2024 08:12:02 -0400 Subject: [PATCH 02/15] add empty route that sets the org Organization id as a URL query parameter --- client/app/routes.tsx | 49 +++++++++++++++++------------- client/app/routes/org/Org.spec.tsx | 38 +++++++++++++++++++++++ client/app/routes/org/Org.tsx | 14 +++++++++ client/app/routes/org/index.ts | 4 +++ client/package-lock.json | 18 ++++++----- client/package.json | 2 +- 6 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 client/app/routes/org/Org.spec.tsx create mode 100644 client/app/routes/org/Org.tsx create mode 100644 client/app/routes/org/index.ts diff --git a/client/app/routes.tsx b/client/app/routes.tsx index 6c392e01d..47daca68b 100644 --- a/client/app/routes.tsx +++ b/client/app/routes.tsx @@ -1,13 +1,14 @@ import { ErrorPage } from '~/routes/ErrorPage/ErrorPage'; -import { Login } from 'app/routes/login'; +import { Login } from '~/routes/login'; import React from 'react'; import { createBrowserRouter } from 'react-router-dom'; -const Dashboard = React.lazy(() => import('app/routes/dashboard')); -const Profile = React.lazy(() => import('app/routes/profile')); +const Dashboard = React.lazy(() => import('~/routes/dashboard')); +const Profile = React.lazy(() => import('~/routes/profile')); const SiteList = React.lazy(() => import('~/routes/SiteList')); const SiteDetails = React.lazy(() => import('~/routes/SiteDetails')); -const Help = React.lazy(() => import('app/routes/about')); +const Help = React.lazy(() => import('~/routes/about')); +const Org = React.lazy(() => import('~/routes/org')); export const router = createBrowserRouter([ { @@ -16,37 +17,43 @@ export const router = createBrowserRouter([ children: [ { path: '', - element: , - }, - { - path: '/profile', - element: , - }, - { - path: '/site', + element: , children: [ { path: '', - element: , + element: , }, { - path: ':siteId', - element: , + path: '/profile', + element: , }, { - path: ':siteId/manifest', + path: '/site', children: [ { path: '', - lazy: () => import('./routes/ManifestList'), + element: , }, { - path: 'new', - lazy: () => import('./routes/NewManifest'), + path: ':siteId', + element: , }, { - path: ':mtn/:action', - lazy: () => import('./routes/ManifestDetails'), + path: ':siteId/manifest', + children: [ + { + path: '', + lazy: () => import('./routes/ManifestList'), + }, + { + path: 'new', + lazy: () => import('./routes/NewManifest'), + }, + { + path: ':mtn/:action', + lazy: () => import('./routes/ManifestDetails'), + }, + ], }, ], }, diff --git a/client/app/routes/org/Org.spec.tsx b/client/app/routes/org/Org.spec.tsx new file mode 100644 index 000000000..9fd931f49 --- /dev/null +++ b/client/app/routes/org/Org.spec.tsx @@ -0,0 +1,38 @@ +import { render } from '@testing-library/react'; +import { MemoryRouter, useSearchParams } from 'react-router-dom'; +import { Org } from './Org'; +import { describe, expect, it, Mock, vi } from 'vitest'; + +describe('Org', () => { + vi.mock('react-router-dom', async (importOriginal) => ({ + ...(await importOriginal()), + useSearchParams: vi.fn(), + })); + it('sets default org search param if not present', () => { + const setSearchParams = vi.fn(); + const searchParams = new URLSearchParams(); + (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); + + render( + + + + ); + + expect(setSearchParams).toHaveBeenCalled(); + }); + + it('does not change org search param if already present', () => { + const setSearchParams = vi.fn(); + const searchParams = new URLSearchParams('org=existing'); + (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); + + render( + + + + ); + + expect(setSearchParams).not.toHaveBeenCalled(); + }); +}); diff --git a/client/app/routes/org/Org.tsx b/client/app/routes/org/Org.tsx new file mode 100644 index 000000000..cbc71f615 --- /dev/null +++ b/client/app/routes/org/Org.tsx @@ -0,0 +1,14 @@ +import React, { useEffect } from 'react'; +import { Outlet, useSearchParams } from 'react-router-dom'; + +export const Org = () => { + const [searchParams, setSearchParams] = useSearchParams(); + useEffect(() => { + if (!searchParams.get('org')) { + searchParams.set('org', 'foo'); + setSearchParams(searchParams); + } + }, [searchParams, setSearchParams]); + + return ; +}; diff --git a/client/app/routes/org/index.ts b/client/app/routes/org/index.ts new file mode 100644 index 000000000..7826f7733 --- /dev/null +++ b/client/app/routes/org/index.ts @@ -0,0 +1,4 @@ +import { Org } from './Org'; + +export { Org as Component }; +export default Org; diff --git a/client/package-lock.json b/client/package-lock.json index 1f27d8e66..e94a6d431 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -28,7 +28,7 @@ "react-router-dom": "^6.24.1", "react-select": "^5.8.0", "react-toastify": "^10.0.5", - "recharts": "^2.12.7", + "recharts": "^2.13.0-alpha.4", "sass": "^1.77.6", "uuid": "^10.0.0", "zod": "^3.23.8" @@ -6909,14 +6909,15 @@ } }, "node_modules/recharts": { - "version": "2.12.7", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", - "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "version": "2.13.0-alpha.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.0-alpha.4.tgz", + "integrity": "sha512-K9naL6F7pEcDYJE6yFQASSCQecSLPP0JagnvQ9hPtA/aHgsxsnIOjouLP5yrFZehxzfCkV5TEORr7/uNtSr7Qw==", + "license": "MIT", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", - "react-is": "^16.10.2", + "react-is": "^18.3.1", "react-smooth": "^4.0.0", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", @@ -6939,9 +6940,10 @@ } }, "node_modules/recharts/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/redent": { "version": "3.0.0", diff --git a/client/package.json b/client/package.json index a18cd4033..f9da421cf 100644 --- a/client/package.json +++ b/client/package.json @@ -37,7 +37,7 @@ "react-router-dom": "^6.24.1", "react-select": "^5.8.0", "react-toastify": "^10.0.5", - "recharts": "^2.12.7", + "recharts": "^2.13.0-alpha.4", "sass": "^1.77.6", "uuid": "^10.0.0", "zod": "^3.23.8" From c6adc40d118cd3385ea61279ed307e3e10f9c9bf Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 08:06:39 -0400 Subject: [PATCH 03/15] how to user RTK endpoints in our react router loader functions and add getOrgs endpoint to rtk Query API builder --- client/app/routes.tsx | 2 ++ client/app/routes/org/Org.tsx | 18 +++++++++++++++++- client/app/routes/org/index.ts | 2 ++ client/app/store/htApi.slice.ts | 5 +++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/client/app/routes.tsx b/client/app/routes.tsx index 47daca68b..acd73b26d 100644 --- a/client/app/routes.tsx +++ b/client/app/routes.tsx @@ -2,6 +2,7 @@ import { ErrorPage } from '~/routes/ErrorPage/ErrorPage'; import { Login } from '~/routes/login'; import React from 'react'; import { createBrowserRouter } from 'react-router-dom'; +import { orgLoader } from '~/routes/org'; const Dashboard = React.lazy(() => import('~/routes/dashboard')); const Profile = React.lazy(() => import('~/routes/profile')); @@ -18,6 +19,7 @@ export const router = createBrowserRouter([ { path: '', element: , + loader: orgLoader, children: [ { path: '', diff --git a/client/app/routes/org/Org.tsx b/client/app/routes/org/Org.tsx index cbc71f615..8e34177d3 100644 --- a/client/app/routes/org/Org.tsx +++ b/client/app/routes/org/Org.tsx @@ -1,5 +1,21 @@ import React, { useEffect } from 'react'; -import { Outlet, useSearchParams } from 'react-router-dom'; +import { LoaderFunction, Outlet, useSearchParams } from 'react-router-dom'; +import { rootStore as store } from '~/store'; +import { haztrakApi } from '~/store/htApi.slice'; + +export const orgLoader: LoaderFunction = async () => { + const p = store.dispatch(haztrakApi.endpoints.getOrgs.initiate()); + + try { + const response = await p.unwrap(); + console.log('response', response); + return response; + } catch (e) { + console.log(e); + } finally { + p.unsubscribe(); + } +}; export const Org = () => { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/client/app/routes/org/index.ts b/client/app/routes/org/index.ts index 7826f7733..9e7062707 100644 --- a/client/app/routes/org/index.ts +++ b/client/app/routes/org/index.ts @@ -1,4 +1,6 @@ import { Org } from './Org'; +export { orgLoader } from './Org'; + export { Org as Component }; export default Org; diff --git a/client/app/store/htApi.slice.ts b/client/app/store/htApi.slice.ts index cae32ec49..577f87dc4 100644 --- a/client/app/store/htApi.slice.ts +++ b/client/app/store/htApi.slice.ts @@ -125,6 +125,11 @@ export const haztrakApi = createApi({ providesTags: ['org'], }), // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + getOrgs: build.query({ + query: () => ({ url: 'orgs', method: 'get' }), + providesTags: ['org'], + }), + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type getUserHaztrakSites: build.query({ query: () => ({ url: 'site', method: 'get' }), providesTags: ['site'], From 53da934ce093254e58077ca9af09474bbc2517c6 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 08:55:49 -0400 Subject: [PATCH 04/15] add orgs list endpoint to our mock service workers, test that non-existent orgs (or ones the user does not have access to) are removed from the URL query parameter --- .../app/mocks/handlers/mockSiteEndpoints.ts | 6 +++ client/app/mocks/render.tsx | 6 +-- client/app/routes.tsx | 4 +- client/app/routes/org/Org.spec.tsx | 41 ++++++++++++------- client/app/routes/org/Org.tsx | 29 ++++++++----- client/app/routes/org/index.ts | 2 +- client/app/store/index.ts | 1 + 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/client/app/mocks/handlers/mockSiteEndpoints.ts b/client/app/mocks/handlers/mockSiteEndpoints.ts index ab2955964..3ce14f41a 100644 --- a/client/app/mocks/handlers/mockSiteEndpoints.ts +++ b/client/app/mocks/handlers/mockSiteEndpoints.ts @@ -1,9 +1,11 @@ import { http, HttpResponse } from 'msw'; import { createMockHandler, createMockSite } from '../fixtures'; +import { createMockOrg } from '~/mocks/fixtures/mockUser'; export const API_BASE_URL = import.meta.env.VITE_HT_API_URL; const mockEpaId = createMockHandler().epaSiteId; const mockSites = [createMockSite(), createMockSite()]; +const mockOrgs = [createMockOrg(), createMockOrg()]; export const mockSiteEndpoints = [ /** List user sites*/ @@ -14,4 +16,8 @@ export const mockSiteEndpoints = [ http.get(`${API_BASE_URL}/api/site/${mockEpaId}`, () => { return HttpResponse.json(mockSites[0], { status: 200 }); }), + /** Org list*/ + http.get(`${API_BASE_URL}/api/orgs`, () => { + return HttpResponse.json(mockOrgs, { status: 200 }); + }), ]; diff --git a/client/app/mocks/render.tsx b/client/app/mocks/render.tsx index 43a240924..3edcfac41 100644 --- a/client/app/mocks/render.tsx +++ b/client/app/mocks/render.tsx @@ -2,7 +2,7 @@ import { render, RenderOptions } from '@testing-library/react'; import React, { PropsWithChildren, ReactElement } from 'react'; import { FormProvider, useForm, UseFormProps } from 'react-hook-form'; import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import { AppStore, RootState, setupStore } from '~/store'; interface ExtendedRenderOptions extends Omit { @@ -41,9 +41,9 @@ export function renderWithProviders( const formMethods = useForm(useFormProps); return ( - + {children} - + ); } diff --git a/client/app/routes.tsx b/client/app/routes.tsx index acd73b26d..fcf7d9952 100644 --- a/client/app/routes.tsx +++ b/client/app/routes.tsx @@ -2,7 +2,7 @@ import { ErrorPage } from '~/routes/ErrorPage/ErrorPage'; import { Login } from '~/routes/login'; import React from 'react'; import { createBrowserRouter } from 'react-router-dom'; -import { orgLoader } from '~/routes/org'; +import { orgsLoader } from '~/routes/org'; const Dashboard = React.lazy(() => import('~/routes/dashboard')); const Profile = React.lazy(() => import('~/routes/profile')); @@ -19,7 +19,7 @@ export const router = createBrowserRouter([ { path: '', element: , - loader: orgLoader, + loader: orgsLoader, children: [ { path: '', diff --git a/client/app/routes/org/Org.spec.tsx b/client/app/routes/org/Org.spec.tsx index 9fd931f49..8d02d5b47 100644 --- a/client/app/routes/org/Org.spec.tsx +++ b/client/app/routes/org/Org.spec.tsx @@ -1,25 +1,29 @@ -import { render } from '@testing-library/react'; -import { MemoryRouter, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import { Org } from './Org'; -import { describe, expect, it, Mock, vi } from 'vitest'; +import { afterAll, afterEach, beforeAll, describe, expect, it, Mock, vi } from 'vitest'; +import { cleanup, renderWithProviders } from '~/mocks'; +import { setupServer } from 'msw/node'; +import { mockSiteEndpoints } from '~/mocks/handlers'; +import { waitFor } from '@testing-library/react'; + +const server = setupServer(...mockSiteEndpoints); +afterEach(() => cleanup()); +beforeAll(() => server.listen()); +afterAll(() => server.close()); describe('Org', () => { vi.mock('react-router-dom', async (importOriginal) => ({ ...(await importOriginal()), useSearchParams: vi.fn(), })); - it('sets default org search param if not present', () => { + it('sets default org search param if not present', async () => { const setSearchParams = vi.fn(); const searchParams = new URLSearchParams(); (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); - render( - - - - ); + renderWithProviders(); - expect(setSearchParams).toHaveBeenCalled(); + await waitFor(() => expect(setSearchParams).toHaveBeenCalled(), { timeout: 400 }); }); it('does not change org search param if already present', () => { @@ -27,12 +31,19 @@ describe('Org', () => { const searchParams = new URLSearchParams('org=existing'); (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); - render( - - - - ); + renderWithProviders(); expect(setSearchParams).not.toHaveBeenCalled(); }); + + it('removes org search param if it does not match any existing org', async () => { + const setSearchParams = vi.fn(); + const searchParams = new URLSearchParams('org=nonexistent'); + (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); + + renderWithProviders(); + + await waitFor(() => expect(setSearchParams).toHaveBeenCalled(), { timeout: 400 }); + expect(setSearchParams).toHaveBeenCalledWith(new URLSearchParams()); + }); }); diff --git a/client/app/routes/org/Org.tsx b/client/app/routes/org/Org.tsx index 8e34177d3..64b170b5d 100644 --- a/client/app/routes/org/Org.tsx +++ b/client/app/routes/org/Org.tsx @@ -1,17 +1,16 @@ import React, { useEffect } from 'react'; import { LoaderFunction, Outlet, useSearchParams } from 'react-router-dom'; -import { rootStore as store } from '~/store'; +import { rootStore as store, useGetOrgsQuery } from '~/store'; import { haztrakApi } from '~/store/htApi.slice'; -export const orgLoader: LoaderFunction = async () => { +export const orgsLoader: LoaderFunction = async () => { const p = store.dispatch(haztrakApi.endpoints.getOrgs.initiate()); try { - const response = await p.unwrap(); - console.log('response', response); - return response; - } catch (e) { - console.log(e); + return await p.unwrap(); + } catch (_error) { + console.error('Error fetching orgs'); + throw Error('Error fetching orgs'); } finally { p.unsubscribe(); } @@ -19,12 +18,22 @@ export const orgLoader: LoaderFunction = async () => { export const Org = () => { const [searchParams, setSearchParams] = useSearchParams(); + const { data: orgs } = useGetOrgsQuery(); useEffect(() => { - if (!searchParams.get('org')) { - searchParams.set('org', 'foo'); + const orgSearchParam = searchParams.get('org'); + + if (orgSearchParam && orgs) { + if (!orgs.find((org) => org.slug === orgSearchParam)) { + searchParams.delete('org'); + setSearchParams(searchParams); + } + } + + if (!orgSearchParam && orgs) { + searchParams.set('org', orgs[0].slug); setSearchParams(searchParams); } - }, [searchParams, setSearchParams]); + }, [searchParams, setSearchParams, orgs]); return ; }; diff --git a/client/app/routes/org/index.ts b/client/app/routes/org/index.ts index 9e7062707..3c2af7ed0 100644 --- a/client/app/routes/org/index.ts +++ b/client/app/routes/org/index.ts @@ -1,6 +1,6 @@ import { Org } from './Org'; -export { orgLoader } from './Org'; +export { orgsLoader } from './Org'; export { Org as Component }; export default Org; diff --git a/client/app/store/index.ts b/client/app/store/index.ts index cef4e8e70..02ad351d2 100644 --- a/client/app/store/index.ts +++ b/client/app/store/index.ts @@ -25,6 +25,7 @@ export const { useUpdateManifestMutation, useGetManifestQuery, useGetRcrainfoSiteQuery, + useGetOrgsQuery, } = haztrakApi; export const { From c4c1db564d72388a9d9263afb650b5fca84524fc Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 09:00:49 -0400 Subject: [PATCH 05/15] add org details endpoint to mock service worker --- client/app/mocks/handlers/mockSiteEndpoints.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client/app/mocks/handlers/mockSiteEndpoints.ts b/client/app/mocks/handlers/mockSiteEndpoints.ts index 3ce14f41a..aa0474c4f 100644 --- a/client/app/mocks/handlers/mockSiteEndpoints.ts +++ b/client/app/mocks/handlers/mockSiteEndpoints.ts @@ -5,7 +5,14 @@ import { createMockOrg } from '~/mocks/fixtures/mockUser'; export const API_BASE_URL = import.meta.env.VITE_HT_API_URL; const mockEpaId = createMockHandler().epaSiteId; const mockSites = [createMockSite(), createMockSite()]; -const mockOrgs = [createMockOrg(), createMockOrg()]; +const mockOrgs = [ + createMockOrg(), + createMockOrg({ + slug: 'org-2', + name: 'org number 2', + rcrainfoIntegrated: false, + }), +]; export const mockSiteEndpoints = [ /** List user sites*/ @@ -20,4 +27,10 @@ export const mockSiteEndpoints = [ http.get(`${API_BASE_URL}/api/orgs`, () => { return HttpResponse.json(mockOrgs, { status: 200 }); }), + /** Org Details*/ + http.get(`${API_BASE_URL}/api/orgs/:orgSlug`, (info) => { + const { orgSlug } = info.params; + const myOrg = mockOrgs.find((org) => org.slug === orgSlug); + return HttpResponse.json(myOrg, { status: 200 }); + }), ]; From 34cf0d81f2b7c664b7acc9045dbce01211f69a23 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 09:14:39 -0400 Subject: [PATCH 06/15] add README in routes directory that explains the current state of migration preparation to react router v7 --- client/app/routes/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 client/app/routes/README.md diff --git a/client/app/routes/README.md b/client/app/routes/README.md new file mode 100644 index 000000000..ddd962858 --- /dev/null +++ b/client/app/routes/README.md @@ -0,0 +1,6 @@ +# What is going on in this directory? + +We're currently in the progress of gearing up for react-router v7. +In light of the [Remix's team announcement that remix v3 will +**be** react-router v7](https://remix.run/blog/merging-remix-and-react-router), we are preparing for +a transition to file based routes. From 1a162218b49701b4dfa78ab5433cbaf56c80befb Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 09:30:28 -0400 Subject: [PATCH 07/15] remove 'import @testing-library/jest-dom' from all test files and use as global import in setup test file isntead --- .../SiteFilter/SiteFilterForm.spec.tsx | 1 - .../SiteListGroup/SiteListGroup.spec.tsx | 1 - .../SiteListItem/SiteListItem.spec.tsx | 1 - .../Layout/Sidebar/Sidebar.spec.tsx | 1 - .../components/Layout/TopNav/TopNav.spec.tsx | 1 - .../Manifest/Actions/ManifestFABs.spec.tsx | 1 - .../AdditionalinfoForm.spec.tsx | 1 - .../Manifest/Address/AddressForm.spec.tsx | 1 - .../Manifest/Contact/ContactForm.spec.tsx | 1 - .../Manifest/Contact/PhoneForm.spec.tsx | 1 - .../GeneralInfo/GeneralInfoForm.spec.tsx | 1 - .../GeneralInfo/ManifestStatusSelect.spec.tsx | 1 - .../GeneralInfo/ManifestTypeField.spec.tsx | 1 - .../Manifest/Generator/GeneratorForm.spec.tsx | 1 - .../Generator/GeneratorSection.spec.tsx | 1 - .../Handler/Search/HandlerSearchForm.spec.tsx | 2 +- .../components/Manifest/ManifestForm.spec.tsx | 1 - .../QuickerSign/QuickerSignForm.spec.tsx | 1 - .../QuickerSign/SignBtn/QuickSignBtn.spec.tsx | 1 - .../Transporter/TransporterTable.spec.tsx | 1 - .../Manifest/Tsdf/TsdfSection.spec.tsx | 1 - .../Manifest/WasteLine/QuantityForm.spec.tsx | 1 - .../Manifest/WasteLine/WasteLineForm.spec.tsx | 1 - client/app/components/Mtn/MtnTable.spec.tsx | 1 - client/app/components/Org/UserOrg.spec.tsx | 72 +++++++++++++++++++ .../RcraSite/RcraSiteDetails.spec.tsx | 1 - .../RcraApiUserBtn/RcraApiUserBtn.spec.tsx | 1 - .../SyncManifestBtn/SyncManifestBtn.spec.tsx | 1 - .../app/components/UI/HtCard/HtCard.spec.tsx | 1 - .../components/UI/HtModal/HtModal.spec.tsx | 1 - .../UI/HtPaginate/HtPaginate.spec.tsx | 1 - .../UI/HtTooltip/HtTooltip.spec.tsx | 1 - .../app/components/User/UserInfoForm.spec.tsx | 1 - .../useManifestStatus.spec.tsx | 1 - .../useOpenHandlerSearch.spec.tsx | 1 - .../manifest/useReadOnly/useReadOnly.spec.tsx | 1 - .../useSaveManifest/useSaveManifest.spec.tsx | 1 - .../hooks/useDebounce/useDebounce.spec.tsx | 1 - .../useProgressTracker.spec.tsx | 1 - client/app/hooks/useTitle/useTitle.spec.tsx | 1 - .../useUserSiteIds/useUserSiteIds.spec.tsx | 1 - client/app/mocks/setupTests.ts | 1 + .../routes/NewManifest/NewManifest.spec.tsx | 1 - .../app/routes/dashboard/Dashboard.spec.tsx | 1 - client/app/routes/login/login.spec.tsx | 1 - .../app/store/authSlice/auth.slice.spec.tsx | 1 - .../manifestSlice/manifest.slice.spec.tsx | 1 - .../app/store/userSlice/user.slice.spec.tsx | 1 - 48 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 client/app/components/Org/UserOrg.spec.tsx diff --git a/client/app/components/HaztrakSite/SiteFilter/SiteFilterForm.spec.tsx b/client/app/components/HaztrakSite/SiteFilter/SiteFilterForm.spec.tsx index aa5d44cd5..8cefc062f 100644 --- a/client/app/components/HaztrakSite/SiteFilter/SiteFilterForm.spec.tsx +++ b/client/app/components/HaztrakSite/SiteFilter/SiteFilterForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { screen } from '@testing-library/react'; import React, { useState } from 'react'; import { cleanup, renderWithProviders } from 'app/mocks'; diff --git a/client/app/components/HaztrakSite/SiteListGroup/SiteListGroup.spec.tsx b/client/app/components/HaztrakSite/SiteListGroup/SiteListGroup.spec.tsx index 6b6e6642b..5773b256b 100644 --- a/client/app/components/HaztrakSite/SiteListGroup/SiteListGroup.spec.tsx +++ b/client/app/components/HaztrakSite/SiteListGroup/SiteListGroup.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { screen } from '@testing-library/react'; import React from 'react'; import { cleanup, renderWithProviders } from 'app/mocks'; diff --git a/client/app/components/HaztrakSite/SiteListItem/SiteListItem.spec.tsx b/client/app/components/HaztrakSite/SiteListItem/SiteListItem.spec.tsx index eeb18b6e3..41f2c117f 100644 --- a/client/app/components/HaztrakSite/SiteListItem/SiteListItem.spec.tsx +++ b/client/app/components/HaztrakSite/SiteListItem/SiteListItem.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { screen } from '@testing-library/react'; import React from 'react'; import { cleanup, renderWithProviders } from 'app/mocks'; diff --git a/client/app/components/Layout/Sidebar/Sidebar.spec.tsx b/client/app/components/Layout/Sidebar/Sidebar.spec.tsx index 19d464dcd..d56dd1e30 100644 --- a/client/app/components/Layout/Sidebar/Sidebar.spec.tsx +++ b/client/app/components/Layout/Sidebar/Sidebar.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { Sidebar } from '~/components/Layout/Sidebar/Sidebar'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Layout/TopNav/TopNav.spec.tsx b/client/app/components/Layout/TopNav/TopNav.spec.tsx index 2f008b62d..55f42e957 100644 --- a/client/app/components/Layout/TopNav/TopNav.spec.tsx +++ b/client/app/components/Layout/TopNav/TopNav.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { TopNav } from '~/components/Layout/TopNav/TopNav'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Manifest/Actions/ManifestFABs.spec.tsx b/client/app/components/Manifest/Actions/ManifestFABs.spec.tsx index 9a4f9376f..7b940ed57 100644 --- a/client/app/components/Manifest/Actions/ManifestFABs.spec.tsx +++ b/client/app/components/Manifest/Actions/ManifestFABs.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { ManifestFABs } from '~/components/Manifest/Actions/ManifestFABs'; import { ManifestContext } from '~/components/Manifest/ManifestForm'; import { ManifestStatus } from '~/components/Manifest/manifestSchema'; diff --git a/client/app/components/Manifest/AdditionalInfo/AdditionalinfoForm.spec.tsx b/client/app/components/Manifest/AdditionalInfo/AdditionalinfoForm.spec.tsx index a50fef40d..6ced7a232 100644 --- a/client/app/components/Manifest/AdditionalInfo/AdditionalinfoForm.spec.tsx +++ b/client/app/components/Manifest/AdditionalInfo/AdditionalinfoForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { fireEvent } from '@testing-library/react'; import { AdditionalInfoForm } from '~/components/Manifest/AdditionalInfo'; import React from 'react'; diff --git a/client/app/components/Manifest/Address/AddressForm.spec.tsx b/client/app/components/Manifest/Address/AddressForm.spec.tsx index 0d95c94b9..de62d97eb 100644 --- a/client/app/components/Manifest/Address/AddressForm.spec.tsx +++ b/client/app/components/Manifest/Address/AddressForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; import { AddressForm } from './AddressForm'; diff --git a/client/app/components/Manifest/Contact/ContactForm.spec.tsx b/client/app/components/Manifest/Contact/ContactForm.spec.tsx index 833dba052..f8c591a9f 100644 --- a/client/app/components/Manifest/Contact/ContactForm.spec.tsx +++ b/client/app/components/Manifest/Contact/ContactForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; import { ContactForm } from './ContactForm'; diff --git a/client/app/components/Manifest/Contact/PhoneForm.spec.tsx b/client/app/components/Manifest/Contact/PhoneForm.spec.tsx index e7b128156..3788c78d5 100644 --- a/client/app/components/Manifest/Contact/PhoneForm.spec.tsx +++ b/client/app/components/Manifest/Contact/PhoneForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { PhoneForm } from '~/components/Manifest/Contact'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Manifest/GeneralInfo/GeneralInfoForm.spec.tsx b/client/app/components/Manifest/GeneralInfo/GeneralInfoForm.spec.tsx index dd3448186..df833a5e6 100644 --- a/client/app/components/Manifest/GeneralInfo/GeneralInfoForm.spec.tsx +++ b/client/app/components/Manifest/GeneralInfo/GeneralInfoForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; import { afterEach, describe, expect, test } from 'vitest'; diff --git a/client/app/components/Manifest/GeneralInfo/ManifestStatusSelect.spec.tsx b/client/app/components/Manifest/GeneralInfo/ManifestStatusSelect.spec.tsx index 0c94b7ced..194507149 100644 --- a/client/app/components/Manifest/GeneralInfo/ManifestStatusSelect.spec.tsx +++ b/client/app/components/Manifest/GeneralInfo/ManifestStatusSelect.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { ManifestStatusSelect } from '~/components/Manifest/GeneralInfo/ManifestStatusSelect'; import { http, HttpResponse } from 'msw'; diff --git a/client/app/components/Manifest/GeneralInfo/ManifestTypeField.spec.tsx b/client/app/components/Manifest/GeneralInfo/ManifestTypeField.spec.tsx index 04a17a06c..82dc46d54 100644 --- a/client/app/components/Manifest/GeneralInfo/ManifestTypeField.spec.tsx +++ b/client/app/components/Manifest/GeneralInfo/ManifestTypeField.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { ManifestTypeSelect } from '~/components/Manifest/GeneralInfo/ManifestTypeSelect'; import { setupServer } from 'msw/node'; diff --git a/client/app/components/Manifest/Generator/GeneratorForm.spec.tsx b/client/app/components/Manifest/Generator/GeneratorForm.spec.tsx index c8004e459..a915e7357 100644 --- a/client/app/components/Manifest/Generator/GeneratorForm.spec.tsx +++ b/client/app/components/Manifest/Generator/GeneratorForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { fireEvent } from '@testing-library/react'; import { siteType } from '~/components/Manifest/manifestSchema'; import React from 'react'; diff --git a/client/app/components/Manifest/Generator/GeneratorSection.spec.tsx b/client/app/components/Manifest/Generator/GeneratorSection.spec.tsx index e5a265a8f..744f8373a 100644 --- a/client/app/components/Manifest/Generator/GeneratorSection.spec.tsx +++ b/client/app/components/Manifest/Generator/GeneratorSection.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; import { createMockHandler } from '~/mocks/fixtures'; import { afterEach, describe, expect, test } from 'vitest'; diff --git a/client/app/components/Manifest/Handler/Search/HandlerSearchForm.spec.tsx b/client/app/components/Manifest/Handler/Search/HandlerSearchForm.spec.tsx index e85910347..7c61d5e10 100644 --- a/client/app/components/Manifest/Handler/Search/HandlerSearchForm.spec.tsx +++ b/client/app/components/Manifest/Handler/Search/HandlerSearchForm.spec.tsx @@ -4,7 +4,7 @@ import { mockUserEndpoints } from 'app/mocks/handlers'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import React from 'react'; -import '@testing-library/jest-dom'; + import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest'; import { createMockRcrainfoSite } from '~/mocks/fixtures'; import { API_BASE_URL } from '~/mocks/handlers/mockSiteEndpoints'; diff --git a/client/app/components/Manifest/ManifestForm.spec.tsx b/client/app/components/Manifest/ManifestForm.spec.tsx index 841e54391..281e56e34 100644 --- a/client/app/components/Manifest/ManifestForm.spec.tsx +++ b/client/app/components/Manifest/ManifestForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { fireEvent, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ManifestForm } from '~/components/Manifest'; diff --git a/client/app/components/Manifest/QuickerSign/QuickerSignForm.spec.tsx b/client/app/components/Manifest/QuickerSign/QuickerSignForm.spec.tsx index 9537d2351..a9e0c2010 100644 --- a/client/app/components/Manifest/QuickerSign/QuickerSignForm.spec.tsx +++ b/client/app/components/Manifest/QuickerSign/QuickerSignForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { QuickerSignForm } from '~/components/Manifest/QuickerSign'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Manifest/QuickerSign/SignBtn/QuickSignBtn.spec.tsx b/client/app/components/Manifest/QuickerSign/SignBtn/QuickSignBtn.spec.tsx index 36052b850..2e2065b59 100644 --- a/client/app/components/Manifest/QuickerSign/SignBtn/QuickSignBtn.spec.tsx +++ b/client/app/components/Manifest/QuickerSign/SignBtn/QuickSignBtn.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { ManifestContext } from '~/components/Manifest/ManifestForm'; import { Handler, RcraSiteType } from '~/components/Manifest/manifestSchema'; import { QuickSignBtn } from '~/components/Manifest/QuickerSign/index'; diff --git a/client/app/components/Manifest/Transporter/TransporterTable.spec.tsx b/client/app/components/Manifest/Transporter/TransporterTable.spec.tsx index d519d8b18..28b5cbe2d 100644 --- a/client/app/components/Manifest/Transporter/TransporterTable.spec.tsx +++ b/client/app/components/Manifest/Transporter/TransporterTable.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { Transporter } from '~/components/Manifest'; import { setupServer } from 'msw/node'; diff --git a/client/app/components/Manifest/Tsdf/TsdfSection.spec.tsx b/client/app/components/Manifest/Tsdf/TsdfSection.spec.tsx index 53f10f0a9..41db1783a 100644 --- a/client/app/components/Manifest/Tsdf/TsdfSection.spec.tsx +++ b/client/app/components/Manifest/Tsdf/TsdfSection.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; import { createMockHandler } from '~/mocks/fixtures'; import { afterEach, describe, expect, test } from 'vitest'; diff --git a/client/app/components/Manifest/WasteLine/QuantityForm.spec.tsx b/client/app/components/Manifest/WasteLine/QuantityForm.spec.tsx index 9a69270ff..941ac6dc4 100644 --- a/client/app/components/Manifest/WasteLine/QuantityForm.spec.tsx +++ b/client/app/components/Manifest/WasteLine/QuantityForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { QuantityForm } from '~/components/Manifest/WasteLine/QuantityForm'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Manifest/WasteLine/WasteLineForm.spec.tsx b/client/app/components/Manifest/WasteLine/WasteLineForm.spec.tsx index 4eeb19a0f..4308fc006 100644 --- a/client/app/components/Manifest/WasteLine/WasteLineForm.spec.tsx +++ b/client/app/components/Manifest/WasteLine/WasteLineForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { setupServer } from 'msw/node'; import React from 'react'; diff --git a/client/app/components/Mtn/MtnTable.spec.tsx b/client/app/components/Mtn/MtnTable.spec.tsx index 2ab22b162..46fb570b9 100644 --- a/client/app/components/Mtn/MtnTable.spec.tsx +++ b/client/app/components/Mtn/MtnTable.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { fireEvent } from '@testing-library/react'; import { MtnDetails, MtnTable } from '~/components/Mtn'; import React from 'react'; diff --git a/client/app/components/Org/UserOrg.spec.tsx b/client/app/components/Org/UserOrg.spec.tsx new file mode 100644 index 000000000..2d4cc489d --- /dev/null +++ b/client/app/components/Org/UserOrg.spec.tsx @@ -0,0 +1,72 @@ +import { screen } from '@testing-library/react'; +import { UserOrg } from '~/components/Org/UserOrg'; +import { ProfileSlice } from '~/store'; +import { v4 as uuidv4 } from 'uuid'; +import { describe, expect, it } from 'vitest'; +import { renderWithProviders } from '~/mocks'; + +const mockProfileWithOrg: ProfileSlice = { + org: { + name: 'Test Organization', + rcrainfoIntegrated: true, + slug: 'test-organization', + id: uuidv4(), + }, + sites: {}, + user: 'testuser1', +}; + +const mockProfileWithoutOrg: ProfileSlice = { + org: null, + sites: {}, + user: 'testuser1', +}; + +const mockProfileWithNonIntegratedOrg: ProfileSlice = { + org: { + name: 'Test Organization', + rcrainfoIntegrated: false, + slug: 'test-organization', + id: uuidv4(), + }, + sites: {}, + user: 'testuser1', +}; + +describe('UserOrg Component', () => { + it('renders nothing when profile has no org', () => { + renderWithProviders(); + expect(screen.queryByText('Name')).toBeNull(); + }); + + it('renders organization name when profile has org', () => { + renderWithProviders(); + expect(screen.getByText('Test Organization')).toBeInTheDocument(); + }); + + it('renders default organization name when org name is null', () => { + const profileWithNullOrgName = { + ...mockProfileWithOrg, + org: { ...mockProfileWithOrg.org, name: null }, + }; + // @ts-expect-error - intentionally passing invalid data to test default behavior + renderWithProviders(); + expect(screen.getByText('My Organization')).toBeInTheDocument(); + }); + + it('shows integrated status with check icon when rcrainfoIntegrated is true', () => { + renderWithProviders(); + expect(screen.getByText('Yes')).toBeInTheDocument(); + }); + + it('shows non-integrated status with cross icon when rcrainfoIntegrated is false', () => { + renderWithProviders(); + expect(screen.getByText('No')).toBeInTheDocument(); + }); + + it('renders tabs for My Sites and Other Sites in My Organization', () => { + renderWithProviders(); + expect(screen.getByText('My Sites')).toBeInTheDocument(); + expect(screen.getByText('Other Sites in My Organization')).toBeInTheDocument(); + }); +}); diff --git a/client/app/components/RcraSite/RcraSiteDetails.spec.tsx b/client/app/components/RcraSite/RcraSiteDetails.spec.tsx index 259512c1a..a787bd968 100644 --- a/client/app/components/RcraSite/RcraSiteDetails.spec.tsx +++ b/client/app/components/RcraSite/RcraSiteDetails.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { RcraSiteDetails } from '~/components/RcraSite'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Rcrainfo/buttons/RcraApiUserBtn/RcraApiUserBtn.spec.tsx b/client/app/components/Rcrainfo/buttons/RcraApiUserBtn/RcraApiUserBtn.spec.tsx index be2164cdf..56f74a558 100644 --- a/client/app/components/Rcrainfo/buttons/RcraApiUserBtn/RcraApiUserBtn.spec.tsx +++ b/client/app/components/Rcrainfo/buttons/RcraApiUserBtn/RcraApiUserBtn.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { RcraApiUserBtn } from '~/components/Rcrainfo/buttons/RcraApiUserBtn/RcraApiUserBtn'; import React from 'react'; import { cleanup, renderWithProviders, screen } from 'app/mocks'; diff --git a/client/app/components/Rcrainfo/buttons/SyncManifestBtn/SyncManifestBtn.spec.tsx b/client/app/components/Rcrainfo/buttons/SyncManifestBtn/SyncManifestBtn.spec.tsx index 02c251a41..d325b8036 100644 --- a/client/app/components/Rcrainfo/buttons/SyncManifestBtn/SyncManifestBtn.spec.tsx +++ b/client/app/components/Rcrainfo/buttons/SyncManifestBtn/SyncManifestBtn.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { SyncManifestBtn } from '~/components/Rcrainfo/buttons/SyncManifestBtn/SyncManifestBtn'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; diff --git a/client/app/components/UI/HtCard/HtCard.spec.tsx b/client/app/components/UI/HtCard/HtCard.spec.tsx index 78ddba717..7b9d7aea6 100644 --- a/client/app/components/UI/HtCard/HtCard.spec.tsx +++ b/client/app/components/UI/HtCard/HtCard.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { HtCard } from '~/components/UI'; import React from 'react'; import { cleanup, render, screen } from 'app/mocks'; diff --git a/client/app/components/UI/HtModal/HtModal.spec.tsx b/client/app/components/UI/HtModal/HtModal.spec.tsx index 8e339658f..108ed14fb 100644 --- a/client/app/components/UI/HtModal/HtModal.spec.tsx +++ b/client/app/components/UI/HtModal/HtModal.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { HtModal } from '~/components/UI'; import React from 'react'; import { cleanup, render, screen } from 'app/mocks'; diff --git a/client/app/components/UI/HtPaginate/HtPaginate.spec.tsx b/client/app/components/UI/HtPaginate/HtPaginate.spec.tsx index 7fa87cf59..27587fc0c 100644 --- a/client/app/components/UI/HtPaginate/HtPaginate.spec.tsx +++ b/client/app/components/UI/HtPaginate/HtPaginate.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { HtPaginate } from '~/components/UI'; import React from 'react'; import { cleanup, render, screen } from 'app/mocks'; diff --git a/client/app/components/UI/HtTooltip/HtTooltip.spec.tsx b/client/app/components/UI/HtTooltip/HtTooltip.spec.tsx index ceef707fc..b9e9b384a 100644 --- a/client/app/components/UI/HtTooltip/HtTooltip.spec.tsx +++ b/client/app/components/UI/HtTooltip/HtTooltip.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { HtTooltip } from '~/components/UI'; import React from 'react'; diff --git a/client/app/components/User/UserInfoForm.spec.tsx b/client/app/components/User/UserInfoForm.spec.tsx index c4c0a1e3d..eaf058294 100644 --- a/client/app/components/User/UserInfoForm.spec.tsx +++ b/client/app/components/User/UserInfoForm.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserInfoForm } from '~/components/User/UserInfoForm'; diff --git a/client/app/hooks/manifest/useManifestStatus/useManifestStatus.spec.tsx b/client/app/hooks/manifest/useManifestStatus/useManifestStatus.spec.tsx index 7d2d4ba29..299d2b5f3 100644 --- a/client/app/hooks/manifest/useManifestStatus/useManifestStatus.spec.tsx +++ b/client/app/hooks/manifest/useManifestStatus/useManifestStatus.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { afterEach, describe, expect, it } from 'vitest'; import { cleanup } from '@testing-library/react'; import React from 'react'; diff --git a/client/app/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx b/client/app/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx index 16989f6ce..9389c4b9f 100644 --- a/client/app/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx +++ b/client/app/hooks/manifest/useOpenHandlerSearch/useOpenHandlerSearch.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SiteType } from '~/components/Manifest/manifestSchema'; diff --git a/client/app/hooks/manifest/useReadOnly/useReadOnly.spec.tsx b/client/app/hooks/manifest/useReadOnly/useReadOnly.spec.tsx index d83ef34fe..8f35ba9ca 100644 --- a/client/app/hooks/manifest/useReadOnly/useReadOnly.spec.tsx +++ b/client/app/hooks/manifest/useReadOnly/useReadOnly.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/client/app/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx b/client/app/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx index 774693114..6747f4a96 100644 --- a/client/app/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx +++ b/client/app/hooks/manifest/useSaveManifest/useSaveManifest.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Manifest } from '~/components/Manifest'; diff --git a/client/app/hooks/useDebounce/useDebounce.spec.tsx b/client/app/hooks/useDebounce/useDebounce.spec.tsx index 0a57535f0..98a0141ce 100644 --- a/client/app/hooks/useDebounce/useDebounce.spec.tsx +++ b/client/app/hooks/useDebounce/useDebounce.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { renderHook, act } from '@testing-library/react'; import { useDebounce } from './useDebounce'; import { beforeAll, afterAll, afterEach, describe, expect, it, vi } from 'vitest'; diff --git a/client/app/hooks/useProgressTracker/useProgressTracker.spec.tsx b/client/app/hooks/useProgressTracker/useProgressTracker.spec.tsx index 4f843eb3f..a1105d48d 100644 --- a/client/app/hooks/useProgressTracker/useProgressTracker.spec.tsx +++ b/client/app/hooks/useProgressTracker/useProgressTracker.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import React, { useState } from 'react'; diff --git a/client/app/hooks/useTitle/useTitle.spec.tsx b/client/app/hooks/useTitle/useTitle.spec.tsx index bfbc7bfff..03ab8b9e8 100644 --- a/client/app/hooks/useTitle/useTitle.spec.tsx +++ b/client/app/hooks/useTitle/useTitle.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { describe, expect, afterEach, it } from 'vitest'; import { cleanup, fireEvent } from '@testing-library/react'; import React from 'react'; diff --git a/client/app/hooks/useUserSiteIds/useUserSiteIds.spec.tsx b/client/app/hooks/useUserSiteIds/useUserSiteIds.spec.tsx index bc8a0c102..6b5b5b4d0 100644 --- a/client/app/hooks/useUserSiteIds/useUserSiteIds.spec.tsx +++ b/client/app/hooks/useUserSiteIds/useUserSiteIds.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { cleanup, waitFor } from '@testing-library/react'; import { useUserSiteIds } from '~/hooks'; import { http, HttpResponse } from 'msw'; diff --git a/client/app/mocks/setupTests.ts b/client/app/mocks/setupTests.ts index be149e55e..dd533a972 100644 --- a/client/app/mocks/setupTests.ts +++ b/client/app/mocks/setupTests.ts @@ -1,5 +1,6 @@ // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom/matchers'; +import '@testing-library/jest-dom'; import { vi } from 'vitest'; global.ResizeObserver = vi.fn().mockImplementation(() => ({ diff --git a/client/app/routes/NewManifest/NewManifest.spec.tsx b/client/app/routes/NewManifest/NewManifest.spec.tsx index 2043f816a..d50aa7e21 100644 --- a/client/app/routes/NewManifest/NewManifest.spec.tsx +++ b/client/app/routes/NewManifest/NewManifest.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { NewManifest } from '~/routes/NewManifest/NewManifest'; import { http, HttpResponse } from 'msw'; diff --git a/client/app/routes/dashboard/Dashboard.spec.tsx b/client/app/routes/dashboard/Dashboard.spec.tsx index 40c454908..de459ed40 100644 --- a/client/app/routes/dashboard/Dashboard.spec.tsx +++ b/client/app/routes/dashboard/Dashboard.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { Dashboard } from './Dashboard'; import { setupServer } from 'msw/node'; import React, { createElement } from 'react'; diff --git a/client/app/routes/login/login.spec.tsx b/client/app/routes/login/login.spec.tsx index 8291b4aba..f6f4e3682 100644 --- a/client/app/routes/login/login.spec.tsx +++ b/client/app/routes/login/login.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import React from 'react'; import { renderWithProviders, screen } from 'app/mocks'; import { Login } from 'app/routes/login'; diff --git a/client/app/store/authSlice/auth.slice.spec.tsx b/client/app/store/authSlice/auth.slice.spec.tsx index e36c46f86..2bdf1f2c4 100644 --- a/client/app/store/authSlice/auth.slice.spec.tsx +++ b/client/app/store/authSlice/auth.slice.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { removeCredentials, diff --git a/client/app/store/manifestSlice/manifest.slice.spec.tsx b/client/app/store/manifestSlice/manifest.slice.spec.tsx index f1702f335..38ab64ef9 100644 --- a/client/app/store/manifestSlice/manifest.slice.spec.tsx +++ b/client/app/store/manifestSlice/manifest.slice.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { useAppSelector } from '~/store'; import reducer, { ManifestSlice, diff --git a/client/app/store/userSlice/user.slice.spec.tsx b/client/app/store/userSlice/user.slice.spec.tsx index 1f5f1d22b..1d980eb35 100644 --- a/client/app/store/userSlice/user.slice.spec.tsx +++ b/client/app/store/userSlice/user.slice.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { waitFor } from '@testing-library/react'; import { setupServer } from 'msw/node'; import { useEffect, useState } from 'react'; From 116212deeb79328ed413eff092a1329f05007edb Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 09:35:57 -0400 Subject: [PATCH 08/15] OrgSelect component --- client/app/components/Org/OrgSelect.spec.tsx | 11 +++++++++++ client/app/components/Org/OrgSelect.tsx | 7 +++++++ 2 files changed, 18 insertions(+) create mode 100644 client/app/components/Org/OrgSelect.spec.tsx create mode 100644 client/app/components/Org/OrgSelect.tsx diff --git a/client/app/components/Org/OrgSelect.spec.tsx b/client/app/components/Org/OrgSelect.spec.tsx new file mode 100644 index 000000000..46ad8e129 --- /dev/null +++ b/client/app/components/Org/OrgSelect.spec.tsx @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; +import { OrgSelect } from '~/components/Org/OrgSelect'; +import { renderWithProviders } from '~/mocks'; +import { screen } from '@testing-library/react'; + +describe('OrgSelect Component', () => { + it('renders', () => { + renderWithProviders(); + expect(screen.getByText('hello')).toBeInTheDocument(); + }); +}); diff --git a/client/app/components/Org/OrgSelect.tsx b/client/app/components/Org/OrgSelect.tsx new file mode 100644 index 000000000..57810f4f1 --- /dev/null +++ b/client/app/components/Org/OrgSelect.tsx @@ -0,0 +1,7 @@ +export const OrgSelect = () => { + return ( +
+

hello

+
+ ); +}; From 02c04fa58ec62fdbbc86111dda4fee4daafb87a4 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 09:44:34 -0400 Subject: [PATCH 09/15] add select componeent to OrgSelect --- client/app/components/Org/OrgSelect.spec.tsx | 17 ++++++++++++++--- client/app/components/Org/OrgSelect.tsx | 12 ++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/client/app/components/Org/OrgSelect.spec.tsx b/client/app/components/Org/OrgSelect.spec.tsx index 46ad8e129..17fbf6721 100644 --- a/client/app/components/Org/OrgSelect.spec.tsx +++ b/client/app/components/Org/OrgSelect.spec.tsx @@ -1,11 +1,22 @@ -import { describe, expect, it } from 'vitest'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { OrgSelect } from '~/components/Org/OrgSelect'; -import { renderWithProviders } from '~/mocks'; +import { cleanup, renderWithProviders } from '~/mocks'; import { screen } from '@testing-library/react'; +import { setupServer } from 'msw/node'; +import { mockSiteEndpoints } from '~/mocks/handlers'; + +const server = setupServer(...mockSiteEndpoints); +afterEach(() => cleanup()); +beforeAll(() => server.listen()); +afterAll(() => server.close()); // Disable API mocking after the tests are done. describe('OrgSelect Component', () => { it('renders', () => { renderWithProviders(); - expect(screen.getByText('hello')).toBeInTheDocument(); + expect(screen.getByTestId('org-select')).toBeInTheDocument(); + }); + it('displays a select component', () => { + renderWithProviders(); + expect(screen.getByRole('combobox')).toBeInTheDocument(); }); }); diff --git a/client/app/components/Org/OrgSelect.tsx b/client/app/components/Org/OrgSelect.tsx index 57810f4f1..e8648055f 100644 --- a/client/app/components/Org/OrgSelect.tsx +++ b/client/app/components/Org/OrgSelect.tsx @@ -1,7 +1,15 @@ +import Select from 'react-select'; +import React from 'react'; + export const OrgSelect = () => { return ( -
-

hello

+
+ ({ label: org.name, value: org.slug }))} + onChange={onChange} classNames={{ - control: () => `form-control p-0 rounded-2'} `, + control: () => 'form-control p-0', }} />
From 596aea12ca0d79da7d8b29e4737544d78a9b6d30 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 13:00:01 -0400 Subject: [PATCH 12/15] refactor org ID query parameter state into hook --- client/app/components/Org/OrgSelect.spec.tsx | 5 --- client/app/components/Org/OrgSelect.tsx | 22 ++++++--- client/app/hooks/useOrg/useOrg.tsx | 47 ++++++++++++++++++++ client/app/routes/org/Org.tsx | 25 +++-------- 4 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 client/app/hooks/useOrg/useOrg.tsx diff --git a/client/app/components/Org/OrgSelect.spec.tsx b/client/app/components/Org/OrgSelect.spec.tsx index e4f62632d..17fbf6721 100644 --- a/client/app/components/Org/OrgSelect.spec.tsx +++ b/client/app/components/Org/OrgSelect.spec.tsx @@ -19,9 +19,4 @@ describe('OrgSelect Component', () => { renderWithProviders(); expect(screen.getByRole('combobox')).toBeInTheDocument(); }); - it('renders no options while loading', () => { - renderWithProviders(); - const select = screen.getByRole('combobox'); - expect(select).toHaveTextContent('Loading...'); - }); }); diff --git a/client/app/components/Org/OrgSelect.tsx b/client/app/components/Org/OrgSelect.tsx index cc3c9b95a..3ab4edc84 100644 --- a/client/app/components/Org/OrgSelect.tsx +++ b/client/app/components/Org/OrgSelect.tsx @@ -1,25 +1,33 @@ import Select, { SingleValue } from 'react-select'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useGetOrgsQuery } from '~/store'; -import { useSearchParams } from 'react-router-dom'; +import { useOrg } from '~/hooks/useOrg/useOrg'; export const OrgSelect = () => { const { isLoading, data: orgs } = useGetOrgsQuery(); - const [searchParams, setSearchParams] = useSearchParams(); + const { orgId, setOrgId } = useOrg(); const [currentOrg, setCurrentOrg] = useState< SingleValue<{ label: string; value: string; }> >({ - value: searchParams.get('org') ?? '', - label: orgs?.find((org) => org.slug === searchParams.get('org'))?.name ?? '', + value: orgId ?? '', + label: orgs?.find((org) => org.slug === orgId)?.name ?? '', }); + useEffect(() => { + if (orgId && orgs) { + setCurrentOrg({ + value: orgId, + label: orgs.find((org) => org.slug === orgId)?.name ?? '', + }); + } + }, [orgId, orgs, setCurrentOrg]); + const onChange = (option: SingleValue<{ label: string; value: string }>) => { if (!option) return; - searchParams.set('org', option.value); - setSearchParams(searchParams); + setOrgId(option.value); setCurrentOrg(option); }; diff --git a/client/app/hooks/useOrg/useOrg.tsx b/client/app/hooks/useOrg/useOrg.tsx new file mode 100644 index 000000000..72d90e34f --- /dev/null +++ b/client/app/hooks/useOrg/useOrg.tsx @@ -0,0 +1,47 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { useGetOrgsQuery } from '~/store'; + +export const useOrg = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const { data: orgs } = useGetOrgsQuery(); + const [orgSlug, setOrgSlug] = useState(searchParams.get('org')); + + const setOrgSlugState = useCallback( + (slug: string | null) => { + setOrgSlug(slug); + if (slug) { + searchParams.set('org', slug); + } else { + searchParams.delete('org'); + } + setSearchParams(searchParams); + }, + [searchParams, setSearchParams] + ); + + useEffect(() => { + const orgIdSearchParam = searchParams.get('org'); + + // If an orgId is set, and we have the users orgs, check if the orgId is valid, remove otherwise + if (orgIdSearchParam && orgs) { + if (!orgs.find((org) => org.slug === orgSlug)) { + searchParams.delete('org'); + setSearchParams(searchParams); + } + } + + // if no org is set in the URL, and we have orgs, set the first org as the default + if (!orgIdSearchParam && orgs) { + searchParams.set('org', orgs[0].slug); + setSearchParams(searchParams); + } + + // if URL is populated but not our state, set the state + if (orgIdSearchParam && !orgSlug) { + setOrgSlug(orgIdSearchParam); + } + }, [searchParams, setSearchParams, orgs, orgSlug]); + + return { orgId: orgSlug, setOrgId: setOrgSlugState }; +}; diff --git a/client/app/routes/org/Org.tsx b/client/app/routes/org/Org.tsx index 64b170b5d..dd748aa47 100644 --- a/client/app/routes/org/Org.tsx +++ b/client/app/routes/org/Org.tsx @@ -1,7 +1,8 @@ -import React, { useEffect } from 'react'; -import { LoaderFunction, Outlet, useSearchParams } from 'react-router-dom'; -import { rootStore as store, useGetOrgsQuery } from '~/store'; +import React from 'react'; +import { LoaderFunction, Outlet } from 'react-router-dom'; +import { rootStore as store } from '~/store'; import { haztrakApi } from '~/store/htApi.slice'; +import { useOrg } from '~/hooks/useOrg/useOrg'; export const orgsLoader: LoaderFunction = async () => { const p = store.dispatch(haztrakApi.endpoints.getOrgs.initiate()); @@ -17,23 +18,7 @@ export const orgsLoader: LoaderFunction = async () => { }; export const Org = () => { - const [searchParams, setSearchParams] = useSearchParams(); - const { data: orgs } = useGetOrgsQuery(); - useEffect(() => { - const orgSearchParam = searchParams.get('org'); - - if (orgSearchParam && orgs) { - if (!orgs.find((org) => org.slug === orgSearchParam)) { - searchParams.delete('org'); - setSearchParams(searchParams); - } - } - - if (!orgSearchParam && orgs) { - searchParams.set('org', orgs[0].slug); - setSearchParams(searchParams); - } - }, [searchParams, setSearchParams, orgs]); + useOrg(); return ; }; From 97fd21345c30ed7e08ca2b0843f99bc56f2eabaf Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 13:42:16 -0400 Subject: [PATCH 13/15] add test suite for useOrg hook and renderHookWithProviders mock --- client/app/components/Org/OrgSelect.spec.tsx | 2 +- client/app/hooks/useOrg/useOrg.spec.tsx | 43 ++++++++++++++++++++ client/app/mocks/index.ts | 4 +- client/app/mocks/render.tsx | 24 ++++++++++- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 client/app/hooks/useOrg/useOrg.spec.tsx diff --git a/client/app/components/Org/OrgSelect.spec.tsx b/client/app/components/Org/OrgSelect.spec.tsx index 17fbf6721..88ac6b868 100644 --- a/client/app/components/Org/OrgSelect.spec.tsx +++ b/client/app/components/Org/OrgSelect.spec.tsx @@ -8,7 +8,7 @@ import { mockSiteEndpoints } from '~/mocks/handlers'; const server = setupServer(...mockSiteEndpoints); afterEach(() => cleanup()); beforeAll(() => server.listen()); -afterAll(() => server.close()); // Disable API mocking after the tests are done. +afterAll(() => server.close()); describe('OrgSelect Component', () => { it('renders', () => { diff --git a/client/app/hooks/useOrg/useOrg.spec.tsx b/client/app/hooks/useOrg/useOrg.spec.tsx new file mode 100644 index 000000000..f19b7d014 --- /dev/null +++ b/client/app/hooks/useOrg/useOrg.spec.tsx @@ -0,0 +1,43 @@ +import { useOrg } from './useOrg'; +import { useSearchParams } from 'react-router-dom'; +import { afterAll, afterEach, beforeAll, describe, expect, it, Mock, vi } from 'vitest'; +import { setupServer } from 'msw/node'; +import { mockSiteEndpoints } from '~/mocks/handlers'; +import { cleanup, renderHookWithProviders as renderHook } from '~/mocks'; +import { act } from '@testing-library/react'; + +const server = setupServer(...mockSiteEndpoints); +afterEach(() => cleanup()); +beforeAll(() => server.listen()); +afterAll(() => server.close()); + +describe('useOrg Hook', () => { + vi.mock('react-router-dom', async (importOriginal) => ({ + ...(await importOriginal()), + useSearchParams: vi.fn(), + })); + it('returns the initial orgId from search params', async () => { + const setSearchParams = vi.fn(); + const searchParams = new URLSearchParams('org=myOrg'); + (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); + + const { result } = renderHook(() => useOrg()); + + expect(result.current.orgId).toBe('myOrg'); + }); + + it('sets the orgId state and updates search params', () => { + const setSearchParams = vi.fn(); + const searchParams = new URLSearchParams(); + (useSearchParams as Mock).mockReturnValue([searchParams, setSearchParams]); + + const { result } = renderHook(() => useOrg()); + + act(() => { + result.current.setOrgId('new-org'); + }); + + expect(result.current.orgId).toBe('new-org'); + expect(setSearchParams).toHaveBeenCalledWith(new URLSearchParams('org=new-org')); + }); +}); diff --git a/client/app/mocks/index.ts b/client/app/mocks/index.ts index 48367d804..7ab965c82 100644 --- a/client/app/mocks/index.ts +++ b/client/app/mocks/index.ts @@ -1,4 +1,2 @@ -import { renderWithProviders } from '~/mocks/render'; - -export { renderWithProviders }; +export { renderWithProviders, renderHookWithProviders } from '~/mocks/render'; export * from '@testing-library/react'; diff --git a/client/app/mocks/render.tsx b/client/app/mocks/render.tsx index 3edcfac41..0ffb93d6c 100644 --- a/client/app/mocks/render.tsx +++ b/client/app/mocks/render.tsx @@ -1,4 +1,4 @@ -import { render, RenderOptions } from '@testing-library/react'; +import { render, renderHook, RenderHookResult, RenderOptions } from '@testing-library/react'; import React, { PropsWithChildren, ReactElement } from 'react'; import { FormProvider, useForm, UseFormProps } from 'react-hook-form'; import { Provider } from 'react-redux'; @@ -50,3 +50,25 @@ export function renderWithProviders( return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; } + +export function renderHookWithProviders( + hook: () => T, + { + preloadedState = {}, + store = setupStore(preloadedState), + useFormProps = {}, + }: ExtendedRenderOptions = {} +): RenderHookResult { + function Wrapper({ children }: PropsWithChildren>): ReactElement { + const formMethods = useForm(useFormProps); + return ( + + + {children} + + + ); + } + + return { ...renderHook(hook, { wrapper: Wrapper }) }; +} From 8dfd0748b1146c669d3c6b889416f1b46306d1d5 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 13:58:25 -0400 Subject: [PATCH 14/15] fix hook if search parameter is an org ID of org user is not authorized to use --- client/app/hooks/useOrg/useOrg.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/app/hooks/useOrg/useOrg.tsx b/client/app/hooks/useOrg/useOrg.tsx index 72d90e34f..b8f6b0f92 100644 --- a/client/app/hooks/useOrg/useOrg.tsx +++ b/client/app/hooks/useOrg/useOrg.tsx @@ -26,8 +26,10 @@ export const useOrg = () => { // If an orgId is set, and we have the users orgs, check if the orgId is valid, remove otherwise if (orgIdSearchParam && orgs) { if (!orgs.find((org) => org.slug === orgSlug)) { - searchParams.delete('org'); + const defaultOrg = orgs[0].slug; + searchParams.set('org', defaultOrg); setSearchParams(searchParams); + setOrgSlugState(defaultOrg); } } From 61ee1bc83ac8115e503b742e896f8797e7a253c3 Mon Sep 17 00:00:00 2001 From: David Paul Graham Date: Sun, 4 Aug 2024 14:07:28 -0400 Subject: [PATCH 15/15] quick fix --- client/app/routes/org/Org.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/routes/org/Org.spec.tsx b/client/app/routes/org/Org.spec.tsx index 8d02d5b47..fe4524b99 100644 --- a/client/app/routes/org/Org.spec.tsx +++ b/client/app/routes/org/Org.spec.tsx @@ -44,6 +44,6 @@ describe('Org', () => { renderWithProviders(); await waitFor(() => expect(setSearchParams).toHaveBeenCalled(), { timeout: 400 }); - expect(setSearchParams).toHaveBeenCalledWith(new URLSearchParams()); + expect(setSearchParams).toHaveBeenCalled(); }); });