Skip to content

Commit

Permalink
MCR-3793: state routing for unlocked rates (#2190)
Browse files Browse the repository at this point in the history
* added edit rate route and constants

* placeholder and routing setup

* setup rerouting for :id

* setup redirect

* setup placeholder and some testing

* renamed feature flag

* updated routes

* components tweaked to redirect in a manner more inline with established patterns

* updated tests

* adjusted for launchdarkly update

* remove unused param

* removed unnecessary spacing
  • Loading branch information
ruizajtruss authored Jan 29, 2024
1 parent c1c7cb0 commit e645afa
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 7 deletions.
5 changes: 4 additions & 1 deletion services/app-web/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ROUTES = [
'HELP',
'SETTINGS',
'RATES_SUMMARY',
'RATE_EDIT',
'SUBMISSIONS',
'SUBMISSIONS_NEW',
'SUBMISSIONS_TYPE',
Expand Down Expand Up @@ -46,7 +47,8 @@ const RoutesRecord: Record<RouteT, string> = {
GRAPHQL_EXPLORER: '/dev/graphql-explorer',
HELP: '/help',
SETTINGS: '/settings',
RATES_SUMMARY: 'rates/:id',
RATES_SUMMARY: '/rates/:id',
RATE_EDIT: '/rates/:id/edit',
SUBMISSIONS: '/submissions',
SUBMISSIONS_NEW: '/submissions/new',
SUBMISSIONS_EDIT_TOP_LEVEL: '/submissions/:id/edit/*',
Expand Down Expand Up @@ -122,6 +124,7 @@ const PageTitlesRecord: Record<RouteT | 'UNKNOWN_ROUTE', string> = {
DASHBOARD_RATES: 'Rate review dashboard',
DASHBOARD_SUBMISSIONS: 'Dashboard',
RATES_SUMMARY: 'Rate summary',
RATE_EDIT: 'Edit rate',
SUBMISSIONS: 'Submissions',
SUBMISSIONS_NEW: 'New submission',
SUBMISSIONS_EDIT_TOP_LEVEL: 'Submissions',
Expand Down
1 change: 1 addition & 0 deletions services/app-web/src/constants/tealium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const CONTENT_TYPE_BY_ROUTE: Record<RouteT | 'UNKNOWN_ROUTE', string> = {
GRAPHQL_EXPLORER: 'dev',
SETTINGS: 'table',
RATES_SUMMARY: 'summary',
RATE_EDIT: 'form',
SUBMISSIONS: 'form',
SUBMISSIONS_NEW: 'form',
SUBMISSIONS_EDIT_TOP_LEVEL: 'form',
Expand Down
11 changes: 9 additions & 2 deletions services/app-web/src/pages/App/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
} from '../QuestionResponse'
import { GraphQLExplorer } from '../GraphQLExplorer/GraphQLExplorer'
import { RateSummary } from '../SubmissionSummary/RateSummary'
import { RateEdit } from '../RateEdit/RateEdit'

function componentForAuthMode(
authMode: AuthModeType
Expand Down Expand Up @@ -75,7 +76,7 @@ const StateUserRoutes = ({
}): React.ReactElement => {
// feature flag
const ldClient = useLDClient()
const showRateSummaryPage: boolean = ldClient?.variation(
const showRatePages: boolean = ldClient?.variation(
featureFlags.RATE_EDIT_UNLOCK.flag,
featureFlags.RATE_EDIT_UNLOCK.defaultValue
)
Expand Down Expand Up @@ -106,7 +107,13 @@ const StateUserRoutes = ({
path={RoutesRecord.SUBMISSIONS_NEW}
element={<NewStateSubmissionForm />}
/>
{showRateSummaryPage && (
{showRatePages && (
<Route
path={RoutesRecord.RATE_EDIT}
element={<RateEdit />}
/>
)}
{showRatePages && (
<Route
path={RoutesRecord.RATES_SUMMARY}
element={<RateSummary />}
Expand Down
48 changes: 48 additions & 0 deletions services/app-web/src/pages/RateEdit/RateEdit.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { screen, waitFor } from '@testing-library/react'
import { renderWithProviders } from "../../testHelpers"
import { RateEdit } from "./RateEdit"
import { fetchCurrentUserMock, fetchRateMockSuccess, mockValidStateUser } from "../../testHelpers/apolloMocks"
import { RoutesRecord } from '../../constants'
import { Route, Routes } from 'react-router-dom'

// Wrap test component in some top level routes to allow getParams to be tested
const wrapInRoutes = (children: React.ReactNode) => {
return (
<Routes>
<Route path={RoutesRecord.RATE_EDIT} element={children} />
</Routes>
)
}

describe('RateEdit', () => {
afterAll(() => jest.clearAllMocks())

describe('Viewing RateEdit as a state user', () => {

it('renders without errors', async () => {
renderWithProviders(wrapInRoutes(<RateEdit />), {
apolloProvider: {
mocks: [
fetchCurrentUserMock({
user: mockValidStateUser(),
statusCode: 200,
}),
fetchRateMockSuccess({ rate: { id: '1337', status: 'UNLOCKED' } }),
],
},
routerProvider: {
route: '/rates/1337/edit'
},
featureFlags: {
'rate-edit-unlock': true
}
})

await waitFor(() => {
expect(screen.queryByTestId('rate-edit')).toBeInTheDocument()
})
})
})
})


52 changes: 52 additions & 0 deletions services/app-web/src/pages/RateEdit/RateEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useFetchRateQuery } from "../../gen/gqlClient";
import { GridContainer } from "@trussworks/react-uswds";
import { Loading } from "../../components";
import { GenericErrorPage } from "../Errors/GenericErrorPage";

type RouteParams = {
id: string
}

export const RateEdit = (): React.ReactElement => {
const navigate = useNavigate()
const { id } = useParams<keyof RouteParams>()
if (!id) {
throw new Error(
'PROGRAMMING ERROR: id param not set in state submission form.'
)
}

const { data, loading, error } = useFetchRateQuery({
variables: {
input: {
rateID: id,
},
},
})

const rate = data?.fetchRate.rate

if (loading) {
return (
<GridContainer>
<Loading />
</GridContainer>
)
} else if (error || !rate ) {
return <GenericErrorPage />
}

if (rate.status !== 'UNLOCKED') {
navigate(`/rates/${id}`)
}

return (
<h1 data-testid="rate-edit">
You've reached the '/rates/:id/edit' url placeholder for the incoming standalone edit rate form
<br/>
Ticket: <a href="https://qmacbis.atlassian.net/browse/MCR-3771">MCR-3771</a>
</h1>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { RateSummary } from './RateSummary'
import { RoutesRecord } from '../../../constants'
import { Route, Routes } from 'react-router-dom'
import { RateEdit } from '../../RateEdit/RateEdit'

// Wrap test component in some top level routes to allow getParams to be tested
const wrapInRoutes = (children: React.ReactNode) => {
Expand Down Expand Up @@ -114,7 +115,7 @@ describe('RateSummary', () => {
})

describe('Viewing RateSummary as a State user', () => {
it('renders without errors', async () => {
it('renders SingleRateSummarySection component without errors for locked rate', async () => {
renderWithProviders(wrapInRoutes(<RateSummary />), {
apolloProvider: {
mocks: [
Expand Down Expand Up @@ -142,6 +143,41 @@ describe('RateSummary', () => {
).toBeInTheDocument()
})

it('redirects to RateEdit component from RateSummary without errors for unlocked rate', async () => {
renderWithProviders(
<Routes>
<Route
path={RoutesRecord.RATES_SUMMARY}
element={<RateSummary />}
/>
<Route
path={RoutesRecord.RATE_EDIT}
element={<RateEdit />}
/>
</Routes>,
{
apolloProvider: {
mocks: [
fetchCurrentUserMock({
user: mockValidStateUser(),
statusCode: 200,
}),
fetchRateMockSuccess({ rate: { id: '1337', status: 'UNLOCKED' } }),
],
},
routerProvider: {
route: '/rates/1337'
},
featureFlags: {
'rate-edit-unlock': true
}
})

await waitFor(() => {
expect(screen.queryByTestId('rate-edit')).toBeInTheDocument()
})
})

it('renders expected error page when rate ID is invalid', async () => {
renderWithProviders(wrapInRoutes(<RateSummary />), {
apolloProvider: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GridContainer, Icon, Link } from '@trussworks/react-uswds'
import React, { useEffect, useState } from 'react'
import { NavLink, useParams } from 'react-router-dom'
import { NavLink, useNavigate, useParams } from 'react-router-dom'

import { Loading } from '../../../components'
import { usePage } from '../../../contexts/PageContext'
Expand All @@ -19,6 +19,7 @@ export const RateSummary = (): React.ReactElement => {
// Page level state
const { loggedInUser } = useAuth()
const { updateHeading } = usePage()
const navigate = useNavigate()
const [rateName, setRateName] = useState<string | undefined>(undefined)
const { id } = useParams<keyof RouteParams>()
if (!id) {
Expand Down Expand Up @@ -52,6 +53,11 @@ export const RateSummary = (): React.ReactElement => {
return <GenericErrorPage />
}

//Redirecting a state user to the edit page if rate is unlocked
if (loggedInUser?.role === 'STATE_USER' && rate.status === 'UNLOCKED') {
navigate(`/rates/${id}/edit`)
}

if (
rateName !== currentRateRev.formData.rateCertificationName &&
currentRateRev.formData.rateCertificationName
Expand All @@ -68,8 +74,7 @@ export const RateSummary = (): React.ReactElement => {
<div>
<Link
asCustom={NavLink}
//TODO: Will have to remove this conditional along with associated loggedInUser prop once the rate dashboard
//is made available to state users
//TODO: Will have to remove this conditional once the rate dashboard is made available to state users
to={{
pathname:
loggedInUser?.__typename === 'StateUser'
Expand Down

0 comments on commit e645afa

Please sign in to comment.