From 168a9f63cfbb060f70fa8c3ef54460b367f1716e Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Thu, 12 Sep 2024 23:08:33 -0400 Subject: [PATCH 01/20] corrected page header, Case Detail tab name, added div code Jira ticket: CAMS-428 --- user-interface/src/case-detail/CaseDetailScreen.scss | 2 +- user-interface/src/case-detail/panels/CaseDetailHeader.tsx | 5 +++-- .../src/case-detail/panels/CaseDetailNavigation.tsx | 2 +- user-interface/src/case-detail/workitems.md | 7 +++++++ 4 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 user-interface/src/case-detail/workitems.md diff --git a/user-interface/src/case-detail/CaseDetailScreen.scss b/user-interface/src/case-detail/CaseDetailScreen.scss index 25097bbff..d971ade96 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.scss +++ b/user-interface/src/case-detail/CaseDetailScreen.scss @@ -8,7 +8,7 @@ } .link-icon { - margin-left: .25rem; + margin-left: 0.25rem; vertical-align: middle; } .case-card-list { diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index ba91094bf..0ca6aed25 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -14,7 +14,7 @@ export interface CaseDetailHeaderProps { export default function CaseDetailHeader(props: CaseDetailHeaderProps) { const { isFixed, fix, loosen } = useFixedPosition(); - const courtInformation = `${props.caseDetail?.courtName} - ${props.caseDetail?.courtDivisionName}`; + const courtInformation = `${props.caseDetail?.courtName} - ${props.caseDetail?.courtDivisionName} (${props.caseDetail?.courtDivisionCode})`; // u00A0 is a non-breaking space. Using   in the string literal does not display correctly. const chapterInformation = `${props.caseDetail?.petitionLabel} Chapter\u00A0${props.caseDetail?.chapter}`; const appEl = document.querySelector('.App'); @@ -119,8 +119,9 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {

{props.isLoading && <>Loading Case Details...} - {!props.isLoading && props.caseDetail?.caseTitle} + {!props.isLoading && 'Case Detail'}

+ {!props.isLoading &&

{props.caseDetail?.caseTitle}

}
diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx index 83206c240..a6abc86ee 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx @@ -52,7 +52,7 @@ function CaseDetailNavigationComponent({ onClick={() => setActiveNav(NavState.BASIC_INFO)} data-testid="basic-info-link" > - Basic Information + Case Summary
  • diff --git a/user-interface/src/case-detail/workitems.md b/user-interface/src/case-detail/workitems.md new file mode 100644 index 000000000..777dd6d18 --- /dev/null +++ b/user-interface/src/case-detail/workitems.md @@ -0,0 +1,7 @@ +- [ x] Look at H1 with name of page. Page is not titled, Perhaps should be Case Details +- [ ] Correct the Back to case list link. Could we know where the user came from to go back? Could + be coming from My Cases, Staff Assignment, or Case Search. +- [ x] Rename Basic Information to Case Details? Case Summary? +- [ ] Rename Audit History (Audit has baggage) +- [x ] Display the division code somewhere +- [ ] fix the space between labels and data. From 9214a64504a3ed058fbd06bfccd11e6051ea2608 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Thu, 12 Sep 2024 23:32:46 -0400 Subject: [PATCH 02/20] removed workitems Jira ticket: CAMS- --- user-interface/src/case-detail/workitems.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 user-interface/src/case-detail/workitems.md diff --git a/user-interface/src/case-detail/workitems.md b/user-interface/src/case-detail/workitems.md deleted file mode 100644 index 777dd6d18..000000000 --- a/user-interface/src/case-detail/workitems.md +++ /dev/null @@ -1,7 +0,0 @@ -- [ x] Look at H1 with name of page. Page is not titled, Perhaps should be Case Details -- [ ] Correct the Back to case list link. Could we know where the user came from to go back? Could - be coming from My Cases, Staff Assignment, or Case Search. -- [ x] Rename Basic Information to Case Details? Case Summary? -- [ ] Rename Audit History (Audit has baggage) -- [x ] Display the division code somewhere -- [ ] fix the space between labels and data. From bdfc4b11600cf1cc11947e3dac84f2bb8e8c0b20 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Fri, 13 Sep 2024 08:48:23 -0400 Subject: [PATCH 03/20] modified tests to accomadate for changed Jira ticket: CAMS-428 --- .../src/case-detail/CaseDetailScreen.test.tsx | 8 +++++--- .../src/case-detail/panels/CaseDetailHeader.test.tsx | 12 +++++++++--- .../src/case-detail/panels/CaseDetailHeader.tsx | 6 +++++- .../src/case-detail/panels/CaseDetailNavigation.tsx | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/user-interface/src/case-detail/CaseDetailScreen.test.tsx b/user-interface/src/case-detail/CaseDetailScreen.test.tsx index 9b0ec2388..bef046fcc 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.test.tsx @@ -77,7 +77,7 @@ describe('Case Detail screen tests', () => { await waitFor( async () => { - const title = screen.getByTestId('case-detail-heading'); + const title = screen.getByTestId('case-detail-heading-title'); expect(title.innerHTML).toEqual('The Beach Boys'); const caseNumber = document.querySelector('.case-number'); @@ -99,7 +99,9 @@ describe('Case Detail screen tests', () => { expect(chapter.innerHTML).toEqual('Voluntary Chapter 15'); const courtName = screen.getByTestId('court-name-and-district'); - expect(courtName.innerHTML).toEqual('Court of Law - Manhattan'); + expect(courtName.innerHTML).toEqual( + `Court of Law - Manhattan (${testCaseDetail.courtDivisionCode})`, + ); const region = screen.getByTestId('case-detail-region-id'); expect(region.innerHTML).toEqual('Region 2 - New York Office'); @@ -608,7 +610,7 @@ describe('Case Detail screen tests', () => { await waitFor( async () => { - const title = screen.getByTestId('case-detail-heading'); + const title = screen.getByTestId('case-detail-heading-title'); expect(title.innerHTML).toEqual('The Beach Boys'); const unassignedElement = document.querySelector('.unassigned-placeholder'); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index 698a8ecda..32dee83e6 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -37,10 +37,12 @@ describe('Case Detail Header tests', () => { ); const isLoadingH1 = screen.getByTestId('case-detail-heading'); + const isLoadingH2 = screen.getByTestId('case-detail-heading-title'); const isFinishedH2 = screen.getByTestId('h2-with-case-info'); const caseChapter = screen.getByTestId('case-chapter'); - expect(isLoadingH1).toContainHTML(testCaseDetail.caseTitle); + expect(isLoadingH1).toContainHTML('Case Detail'); + expect(isLoadingH2).toContainHTML(testCaseDetail.caseTitle); expect(isFinishedH2).toBeInTheDocument(); expect(caseChapter.innerHTML).toEqual( `${testCaseDetail.petitionLabel} Chapter ${testCaseDetail.chapter}`, @@ -65,11 +67,15 @@ describe('Case Detail Header tests', () => { const app = await screen.findByTestId('app-component-test-id'); await waitFor( async () => { - const title = await screen.findByTestId('case-detail-heading'); - expect(title.innerHTML).toEqual(testCaseDetail.caseTitle); + const heading = await screen.findByTestId('case-detail-heading'); + expect(heading.innerHTML).toEqual('Case Detail'); }, { timeout: 1000 }, ); + await waitFor(async () => { + const title = await screen.findByTestId('case-detail-heading-title'); + expect(title.innerHTML).toEqual(testCaseDetail.caseTitle); + }); let normalHeader = await screen.findByTestId('case-detail-header'); expect(normalHeader).toBeInTheDocument(); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index 0ca6aed25..81f5aef54 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -121,7 +121,11 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { {props.isLoading && <>Loading Case Details...} {!props.isLoading && 'Case Detail'} - {!props.isLoading &&

    {props.caseDetail?.caseTitle}

    } + {!props.isLoading && props.caseDetail && ( +

    + {props.caseDetail.caseTitle} +

    + )}
    diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx index a6abc86ee..ab7ecbf9b 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx @@ -52,7 +52,7 @@ function CaseDetailNavigationComponent({ onClick={() => setActiveNav(NavState.BASIC_INFO)} data-testid="basic-info-link" > - Case Summary + Case Details
  • From 8e6d687faa5becf3b8f2d4a76f0cf87342374063 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Fri, 13 Sep 2024 08:55:56 -0400 Subject: [PATCH 04/20] renamed audit history to Case History Jira ticket: CAMS-428 --- .../src/case-detail/panels/CaseDetailAuditHistory.tsx | 2 +- user-interface/src/case-detail/panels/CaseDetailNavigation.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx index b479594e3..1ad50e71f 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx @@ -110,7 +110,7 @@ export default function CaseDetailAuditHistory(props: CaseDetailAuditHistoryProp return (
    -

    Audit History

    +

    Case History

    {isAuditHistoryLoading && } {!isAuditHistoryLoading && ( <> diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx index ab7ecbf9b..09809100e 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx @@ -76,7 +76,7 @@ function CaseDetailNavigationComponent({ }} data-testid="audit-history-link" > - Audit History + Case History
  • {showAssociatedCasesList && ( From f795ee0cf0c5dcc67f286591946cc03609881ce5 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Fri, 13 Sep 2024 10:56:13 -0400 Subject: [PATCH 05/20] reverted nav link renames Jira ticket: CAMS-428 --- .../src/case-detail/panels/CaseDetailAuditHistory.tsx | 2 +- .../src/case-detail/panels/CaseDetailNavigation.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx index 1ad50e71f..b479594e3 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx @@ -110,7 +110,7 @@ export default function CaseDetailAuditHistory(props: CaseDetailAuditHistoryProp return (
    -

    Case History

    +

    Audit History

    {isAuditHistoryLoading && } {!isAuditHistoryLoading && ( <> diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx index 09809100e..83206c240 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx @@ -52,7 +52,7 @@ function CaseDetailNavigationComponent({ onClick={() => setActiveNav(NavState.BASIC_INFO)} data-testid="basic-info-link" > - Case Details + Basic Information
  • @@ -76,7 +76,7 @@ function CaseDetailNavigationComponent({ }} data-testid="audit-history-link" > - Case History + Audit History
  • {showAssociatedCasesList && ( From 6f05c3dade0ad1918c02a8935b694cae7662a374 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Mon, 16 Sep 2024 11:59:24 -0400 Subject: [PATCH 06/20] renamed Basc Info and components to Case Overview updated tests accordingly surface level changed Audit History to Change History Jira ticket: CAMS-428 --- .../src/case-detail/CaseDetailScreen.test.tsx | 4 ++-- .../src/case-detail/CaseDetailScreen.tsx | 2 +- .../CaseDetailScreenSortAndFilter.test.tsx | 8 ++++---- .../case-detail/panels/CaseDetailAuditHistory.tsx | 2 +- .../panels/CaseDetailNavigation.test.tsx | 12 ++++++------ .../case-detail/panels/CaseDetailNavigation.tsx | 14 +++++++------- ...icInfo.test.tsx => CaseDetailOverview.test.tsx} | 8 ++++---- ...eDetailBasicInfo.tsx => CaseDetailOverview.tsx} | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) rename user-interface/src/case-detail/panels/{CaseDetailBasicInfo.test.tsx => CaseDetailOverview.test.tsx} (98%) rename user-interface/src/case-detail/panels/{CaseDetailBasicInfo.tsx => CaseDetailOverview.tsx} (99%) diff --git a/user-interface/src/case-detail/CaseDetailScreen.test.tsx b/user-interface/src/case-detail/CaseDetailScreen.test.tsx index bef046fcc..f001fe9eb 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.test.tsx @@ -734,8 +734,8 @@ describe('Case Detail screen tests', () => { ); const navRouteTestCases = [ - ['case-detail/1234', 'basic-info-link'], - ['case-detail/1234/', 'basic-info-link'], + ['case-detail/1234', 'case-overview-link'], + ['case-detail/1234/', 'case-overview-link'], ['case-detail/1234/court-docket/', 'court-docket-link'], ]; diff --git a/user-interface/src/case-detail/CaseDetailScreen.tsx b/user-interface/src/case-detail/CaseDetailScreen.tsx index bf6512623..6b126b417 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.tsx @@ -26,7 +26,7 @@ import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; import { useApi2 } from '@/lib/hooks/UseApi2'; const CaseDetailHeader = lazy(() => import('./panels/CaseDetailHeader')); -const CaseDetailBasicInfo = lazy(() => import('./panels/CaseDetailBasicInfo')); +const CaseDetailBasicInfo = lazy(() => import('./panels/CaseDetailOverview')); const CaseDetailCourtDocket = lazy(() => import('./panels/CaseDetailCourtDocket')); type SortDirection = 'Oldest' | 'Newest'; diff --git a/user-interface/src/case-detail/CaseDetailScreenSortAndFilter.test.tsx b/user-interface/src/case-detail/CaseDetailScreenSortAndFilter.test.tsx index 0d2a6ec50..9178f7e8e 100644 --- a/user-interface/src/case-detail/CaseDetailScreenSortAndFilter.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreenSortAndFilter.test.tsx @@ -96,7 +96,7 @@ describe('Case Detail sort, search, and filter tests', () => { expect(searchInput).toBeInTheDocument(); }); - const basicInfoLink = screen.getByTestId('basic-info-link'); + const basicInfoLink = screen.getByTestId('case-overview-link'); fireEvent.click(basicInfoLink as Element); await waitFor(() => { sortButton = screen.queryByTestId(sortButtonId); @@ -139,7 +139,7 @@ describe('Case Detail sort, search, and filter tests', () => { expect(searchInput).toBeInTheDocument(); }); - const basicInfoLink = screen.getByTestId('basic-info-link'); + const basicInfoLink = screen.getByTestId('case-overview-link'); fireEvent.click(basicInfoLink as Element); await waitFor(() => { sortButton = screen.queryByTestId(sortButtonId); @@ -183,7 +183,7 @@ describe('Case Detail sort, search, and filter tests', () => { expect(filterSelectElement).toBeInTheDocument(); }); - const basicInfoLink = screen.getByTestId('basic-info-link'); + const basicInfoLink = screen.getByTestId('case-overview-link'); fireEvent.click(basicInfoLink as Element); await waitFor(() => { filterSelectElement = document.querySelector(filterSelectClass); @@ -220,7 +220,7 @@ describe('Case Detail sort, search, and filter tests', () => { expect(filterSelectElement).toBeInTheDocument(); }); - const basicInfoLink = screen.getByTestId('basic-info-link'); + const basicInfoLink = screen.getByTestId('case-overview-link'); fireEvent.click(basicInfoLink as Element); await waitFor(() => { filterSelectElement = document.querySelector(filterSelectClass); diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx index b479594e3..be86fe352 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx @@ -110,7 +110,7 @@ export default function CaseDetailAuditHistory(props: CaseDetailAuditHistoryProp return (
    -

    Audit History

    +

    Change History

    {isAuditHistoryLoading && } {!isAuditHistoryLoading && ( <> diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.test.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.test.tsx index 05978d2e7..7cd24493e 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.test.tsx @@ -6,19 +6,19 @@ describe('Navigation tests', () => { const activeNavClass = 'usa-current'; test(`should return ${activeNavClass} when the activeNav equals the stateToCheck`, () => { - const result = setCurrentNav(NavState.BASIC_INFO, NavState.BASIC_INFO); + const result = setCurrentNav(NavState.CASE_OVERVIEW, NavState.CASE_OVERVIEW); expect(result).toEqual(activeNavClass); }); test('should return an empty string when the activeNav does not equal the stateToCheck', () => { - const result = setCurrentNav(NavState.BASIC_INFO, NavState.COURT_DOCKET); + const result = setCurrentNav(NavState.CASE_OVERVIEW, NavState.COURT_DOCKET); expect(result).toEqual(''); }); test.each([ - ['basic-info-link'], + ['case-overview-link'], ['court-docket-link'], ['audit-history-link'], ['associated-cases-link'], @@ -27,7 +27,7 @@ describe('Navigation tests', () => { , @@ -45,10 +45,10 @@ describe('Navigation tests', () => { }); }); - test(`mapNavState should return ${NavState.BASIC_INFO} when the url does not contain a path after the case number`, () => { + test(`mapNavState should return ${NavState.CASE_OVERVIEW} when the url does not contain a path after the case number`, () => { const result = mapNavState('case-detail/1234'); - expect(result).toEqual(NavState.BASIC_INFO); + expect(result).toEqual(NavState.CASE_OVERVIEW); }); test(`mapNavState should return ${NavState.COURT_DOCKET} when the url path contains 'court-docket' after the case number`, () => { diff --git a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx index 83206c240..5e15f18d8 100644 --- a/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailNavigation.tsx @@ -11,7 +11,7 @@ export function mapNavState(path: string) { case 'associated-cases': return NavState.ASSOCIATED_CASES; default: - return NavState.BASIC_INFO; + return NavState.CASE_OVERVIEW; } } @@ -23,7 +23,7 @@ export interface CaseDetailNavigationProps { } export enum NavState { - BASIC_INFO, + CASE_OVERVIEW, COURT_DOCKET, AUDIT_HISTORY, ASSOCIATED_CASES, @@ -47,12 +47,12 @@ function CaseDetailNavigationComponent({
    • setActiveNav(NavState.BASIC_INFO)} - data-testid="basic-info-link" + onClick={() => setActiveNav(NavState.CASE_OVERVIEW)} + data-testid="case-overview-link" > - Basic Information + Case Overview
    • @@ -76,7 +76,7 @@ function CaseDetailNavigationComponent({ }} data-testid="audit-history-link" > - Audit History + Change History
    • {showAssociatedCasesList && ( diff --git a/user-interface/src/case-detail/panels/CaseDetailBasicInfo.test.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx similarity index 98% rename from user-interface/src/case-detail/panels/CaseDetailBasicInfo.test.tsx rename to user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx index e20e28802..798ca0c6d 100644 --- a/user-interface/src/case-detail/panels/CaseDetailBasicInfo.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx @@ -1,5 +1,5 @@ import { BrowserRouter } from 'react-router-dom'; -import CaseDetailBasicInfo, { CaseDetailBasicInfoProps } from './CaseDetailBasicInfo'; +import CaseDetailOverview, { CaseDetailOverviewProps } from './CaseDetailOverview'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { formatDate } from '@/lib/utils/datetime'; import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; @@ -68,8 +68,8 @@ describe('Case detail basic information panel', () => { }; vi.spyOn(Api2, 'getAttorneys').mockResolvedValue(attorneyListResponse); - function renderWithProps(props?: Partial) { - const defaultProps: CaseDetailBasicInfoProps = { + function renderWithProps(props?: Partial) { + const defaultProps: CaseDetailOverviewProps = { caseDetail: BASE_TEST_CASE_DETAIL, showReopenDate: false, onCaseAssignment: vi.fn(), @@ -78,7 +78,7 @@ describe('Case detail basic information panel', () => { const renderProps = { ...defaultProps, ...props }; render( - + , ); } diff --git a/user-interface/src/case-detail/panels/CaseDetailBasicInfo.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx similarity index 99% rename from user-interface/src/case-detail/panels/CaseDetailBasicInfo.tsx rename to user-interface/src/case-detail/panels/CaseDetailOverview.tsx index 42cbb531b..8169037e6 100644 --- a/user-interface/src/case-detail/panels/CaseDetailBasicInfo.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx @@ -20,13 +20,13 @@ import Icon from '@/lib/components/uswds/Icon'; const informationUnavailable = 'Information is not available.'; const taxIdUnavailable = 'Tax ID information is not available.'; -export interface CaseDetailBasicInfoProps { +export interface CaseDetailOverviewProps { caseDetail: CaseDetail; showReopenDate: boolean; onCaseAssignment: (props: CallbackProps) => void; } -export default function CaseDetailBasicInfo(props: CaseDetailBasicInfoProps) { +export default function CaseDetailOverview(props: CaseDetailOverviewProps) { const { caseDetail, showReopenDate, onCaseAssignment } = props; const assignmentModalRef = useRef(null); From 0f8018d02aa7b639db0119f3ffffa5c0b9a02644 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Mon, 16 Sep 2024 17:31:31 -0600 Subject: [PATCH 07/20] Fixed non-critical Accessibility Issues resolved the following issues: * Look at H1 with name of page. Page is not titled, Perhaps should be Case Details * Correct the Back to case list link. Could we know where the user came from to go back? Could be coming from My Cases, Staff Assignment, or Case Search. * Rename Basic Information to Case Details Overview? * Rename Audit History (Audit has baggage) * Display the division code somewhere * fix the space between labels and data. * Change History empty state alert (no changes made) should say "No changes have been made to this case." Jira ticket: CAMS-428 --- .../src/case-detail/CaseDetailScreen.scss | 85 +++++----- .../panels/CaseDetailAuditHistory.tsx | 2 +- .../case-detail/panels/CaseDetailHeader.tsx | 46 ++++-- .../case-detail/panels/CaseDetailOverview.tsx | 148 +++++++++--------- .../DataVerificationScreen.tsx | 7 + .../src/lib/hooks/UseLocationTracker.ts | 36 +++++ user-interface/src/my-cases/MyCasesScreen.tsx | 9 +- user-interface/src/search/SearchScreen.tsx | 6 + .../screen/StaffAssignmentScreen.tsx | 9 +- 9 files changed, 212 insertions(+), 136 deletions(-) create mode 100644 user-interface/src/lib/hooks/UseLocationTracker.ts diff --git a/user-interface/src/case-detail/CaseDetailScreen.scss b/user-interface/src/case-detail/CaseDetailScreen.scss index d971ade96..59843da2d 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.scss +++ b/user-interface/src/case-detail/CaseDetailScreen.scss @@ -1,52 +1,55 @@ @import '../styles/theme.scss'; -.App { - .body { - .case-detail { - h4 { - margin-block-start: 0; - margin-block-end: 0; + +.case-detail { + h4 { + margin-block-start: 0; + margin-block-end: 0; + } + + .link-icon { + margin-left: 0.25rem; + vertical-align: middle; + } + .case-card-list { + .case-card { + line-height: 1.5; + + h3 { + margin-bottom: 0.5rem; } - .link-icon { - margin-left: 0.25rem; - vertical-align: middle; + li.transfer { + margin-bottom: 1rem; } - .case-card-list { - .case-card { - li.transfer { - margin-bottom: 1rem; - } - .case-detail-item-name { - padding-right: 0.5rem; - } - .transfer-court { - span { - white-space: nowrap; - } - } - } + .case-detail-item-name { + padding-right: .5rem; } - .assigned-staff-list { - .case-detail-region-id { - padding-bottom: 0.5rem; - } - .individual-assignee { - .vertical-divider { - padding: 0 0.5rem; - } + .transfer-court { + span { + white-space: nowrap; } } - ul { - padding-bottom: 0.5rem; - } - .filter-and-search { - .form-field { - padding-bottom: 1.5rem; - } - .docket-summary-facets label { - margin-bottom: 0.5rem; - } + } + } + .assigned-staff-list { + .case-detail-region-id { + padding-bottom: 0.5rem; + } + .individual-assignee { + .vertical-divider { + padding: 0 0.5rem; } } } + ul { + padding-bottom: 0.5rem; + } + .filter-and-search { + .form-field { + padding-bottom: 1.5rem; + } + .docket-summary-facets label { + margin-bottom: 0.5rem; + } + } } diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx index be86fe352..c0dcf1f23 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx @@ -117,7 +117,7 @@ export default function CaseDetailAuditHistory(props: CaseDetailAuditHistoryProp {caseHistory.length < 1 && (
      { if (camsHeader) { const caseDetailHeader = document.querySelector('.case-detail-header.fixed'); @@ -35,6 +38,14 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { } }; + function getBackLinkText() { + if (previousLocation.includes('my-cases')) return 'My Cases'; + if (previousLocation.includes('search')) return 'Case Search'; + if (previousLocation.includes('staff-assignment')) return 'Staff Assignment'; + if (previousLocation.includes('data-verification')) return 'Data Verification'; + return 'Case List'; + } + useEffect(() => { if (!props.isLoading && appEl && camsHeader) { appEl.addEventListener('scroll', modifyHeader); @@ -53,9 +64,9 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {
      - + - Back to Case List + Back to {getBackLinkText()}
      @@ -106,9 +117,9 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {
      - + - Back to Case List + Back to {getBackLinkText()}
      @@ -117,14 +128,19 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {
      -

      - {props.isLoading && <>Loading Case Details...} - {!props.isLoading && 'Case Detail'} -

      - {!props.isLoading && props.caseDetail && ( -

      - {props.caseDetail.caseTitle} -

      + {props.isLoading && ( +

      Loading Case Details...

      + )} + {!props.isLoading && ( +

      + Case Details + {props.caseDetail && ( + + {' '} + - {props.caseDetail.caseTitle} + + )} +

      )}
      @@ -135,7 +151,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {

      - {getCaseNumber(props.caseId)} + {props.caseId}

      @@ -147,7 +163,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {

      - {getCaseNumber(props.caseId)} + {props.caseId}

      diff --git a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx index 8169037e6..19c7b119b 100644 --- a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx @@ -290,46 +290,44 @@ export default function CaseDetailOverview(props: CaseDetailOverviewProps) {

      Consolidation

      -
        -
        -

        {consolidationTypeMap.get(caseDetail.consolidation[0].consolidationType)}

        -
        - {caseDetail.consolidation[0].documentType === 'CONSOLIDATION_FROM' && ( - Lead Case: (this case) - )} - {caseDetail.consolidation[0].documentType === 'CONSOLIDATION_TO' && ( - <> - Lead Case: - {' '} - - {caseDetail.consolidation[0].otherCase.caseTitle} - - - )} -
        +
        +

        {consolidationTypeMap.get(caseDetail.consolidation[0].consolidationType)}

        +
        {caseDetail.consolidation[0].documentType === 'CONSOLIDATION_FROM' && ( -
        - - Cases Consolidated: {caseDetail.consolidation.length + 1} + Lead Case: (this case) + )} + {caseDetail.consolidation[0].documentType === 'CONSOLIDATION_TO' && ( + <> + Lead Case: + {' '} + + {caseDetail.consolidation[0].otherCase.caseTitle} -
        + )} +
        + {caseDetail.consolidation[0].documentType === 'CONSOLIDATION_FROM' && (
        - Order Filed: - - {/* This order date is not likely the correct one. Clarification from Phoenix has been requested */} - {formatDate(caseDetail.consolidation[0].orderDate)} + + Cases Consolidated: {caseDetail.consolidation.length + 1}
        + )} +
        + Order Filed: + + {/* This order date is not likely the correct one. Clarification from Phoenix has been requested */} + {formatDate(caseDetail.consolidation[0].orderDate)} +
        -
      +
      )} {!!caseDetail.transfers?.length && caseDetail.transfers.length > 0 && ( @@ -337,50 +335,46 @@ export default function CaseDetailOverview(props: CaseDetailOverviewProps) {

      Transferred Case

      -
        -
        - {caseDetail.transfers - ?.sort(sortTransfers) - .map((transfer: Transfer, idx: number) => { - return ( -
      • -

        - Transferred {transfer.documentType === 'TRANSFER_FROM' ? 'from' : 'to'}: -

        -
        - Case Number: - -
        -
        - - {transfer.documentType === 'TRANSFER_FROM' ? 'Previous' : 'New'}{' '} - Court: - - - {transfer.otherCase.courtName} -{' '} - {transfer.otherCase.courtDivisionName} - -
        -
        - Order Filed: - - {formatDate(transfer.orderDate)} - -
        -
      • - ); - })} -
        +
          + {caseDetail.transfers + ?.sort(sortTransfers) + .map((transfer: Transfer, idx: number) => { + return ( +
        • +

          + Transferred {transfer.documentType === 'TRANSFER_FROM' ? 'from' : 'to'}: +

          +
          + Case Number: + +
          +
          + + {transfer.documentType === 'TRANSFER_FROM' ? 'Previous' : 'New'} Court: + + + {transfer.otherCase.courtName} - {transfer.otherCase.courtDivisionName} + +
          +
          + Order Filed: + + {formatDate(transfer.orderDate)} + +
          +
        • + ); + })}
        )} diff --git a/user-interface/src/data-verification/DataVerificationScreen.tsx b/user-interface/src/data-verification/DataVerificationScreen.tsx index 0f7633251..e8fe0596c 100644 --- a/user-interface/src/data-verification/DataVerificationScreen.tsx +++ b/user-interface/src/data-verification/DataVerificationScreen.tsx @@ -26,6 +26,7 @@ import { ResponseBody } from '@common/api/response'; import { CamsRole } from '@common/cams/roles'; import LocalStorage from '@/lib/utils/local-storage'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; +import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export function officeSorter(a: OfficeDetails, b: OfficeDetails) { const aKey = a.courtName + '-' + a.courtDivisionName; @@ -51,6 +52,12 @@ export default function DataVerificationScreen() { const globalAlert = useGlobalAlert(); const session = LocalStorage.getSession(); + const { updateLocation } = useLocationTracker(); + + useEffect(() => { + updateLocation(); + }, [location.pathname]); + const regionNumber = '02'; const api = useApi2(); diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts new file mode 100644 index 000000000..ceefc8c29 --- /dev/null +++ b/user-interface/src/lib/hooks/UseLocationTracker.ts @@ -0,0 +1,36 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; + +export function isValidPath(path: string): boolean { + const pathRegex = /^\/[a-zA-Z0-9\-_]+$/; + return pathRegex.test(path); +} + +export default function useLocationTracker() { + const location = useLocation(); + const [previousLocation, setPreviousLocation] = useState(() => { + const storedLocation = localStorage.getItem('previousLocation'); + + return storedLocation && isValidPath(storedLocation) ? storedLocation : '/my-cases'; + }); + + const updateLocation = useCallback((newLocation?: string) => { + if (newLocation && isValidPath(newLocation)) { + setPreviousLocation(newLocation); + localStorage.setItem('previousLocation', newLocation); + } else { + const pathLocation = location.pathname ?? '/my-cases'; + setPreviousLocation(pathLocation); + localStorage.setItem('previousLocation', pathLocation); + } + }, []); + + useEffect(() => { + const storedLocation = localStorage.getItem('previousLocation'); + if (storedLocation && isValidPath(storedLocation)) { + setPreviousLocation(storedLocation); + } + }); + + return { previousLocation, updateLocation }; +} diff --git a/user-interface/src/my-cases/MyCasesScreen.tsx b/user-interface/src/my-cases/MyCasesScreen.tsx index 47ea49415..78ddd8ee6 100644 --- a/user-interface/src/my-cases/MyCasesScreen.tsx +++ b/user-interface/src/my-cases/MyCasesScreen.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { UswdsButtonStyle } from '@/lib/components/uswds/Button'; import Modal from '@/lib/components/uswds/modal/Modal'; import { ModalRefType } from '@/lib/components/uswds/modal/modal-refs'; @@ -16,10 +16,13 @@ import './MyCasesScreen.scss'; import ScreenInfoButton from '@/lib/components/cams/ScreenInfoButton'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; +import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export const MyCasesScreen = () => { const screenTitle = 'My Cases'; + const { updateLocation } = useLocationTracker(); + const infoModalRef = useRef(null); const infoModalId = 'info-modal'; const session = LocalStorage.getSession(); @@ -43,6 +46,10 @@ export const MyCasesScreen = () => { }, }; + useEffect(() => { + updateLocation(); + }, [location.pathname]); + return ( diff --git a/user-interface/src/search/SearchScreen.tsx b/user-interface/src/search/SearchScreen.tsx index 0c05b8fbc..c508cf002 100644 --- a/user-interface/src/search/SearchScreen.tsx +++ b/user-interface/src/search/SearchScreen.tsx @@ -19,6 +19,7 @@ import { SearchResultsRow } from './SearchResultsRow'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; +import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export default function SearchScreen() { const [searchPredicate, setSearchPredicate] = useState({ @@ -35,6 +36,11 @@ export default function SearchScreen() { const api = useApi2(); const globalAlert = useGlobalAlert(); + const { updateLocation } = useLocationTracker(); + + useEffect(() => { + updateLocation(); + }, [location.pathname]); function getChapters() { const chapterArray: ComboOption[] = []; diff --git a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx index a3b05d0b8..81144e40d 100644 --- a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx +++ b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx @@ -8,7 +8,7 @@ import { DEFAULT_SEARCH_OFFSET, } from '@common/api/search'; import { CamsUser } from '@common/cams/users'; -import { useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { SearchResults, SearchResultsRowProps } from '@/search-results/SearchResults'; import { StaffAssignmentHeader } from '../header/StaffAssignmentHeader'; import { StaffAssignmentRow } from '../row/StaffAssignmentRow'; @@ -19,6 +19,7 @@ import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; import ScreenInfoButton from '@/lib/components/cams/ScreenInfoButton'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; +import useLocationTracker from '@/lib/hooks/UseLocationTracker'; function getPredicateByUserContext(user: CamsUser): CasesSearchPredicate { const predicate: CasesSearchPredicate = { @@ -68,6 +69,12 @@ export const StaffAssignmentScreen = () => { }, }; + const { updateLocation } = useLocationTracker(); + + useEffect(() => { + updateLocation(); + }, [location.pathname]); + return ( From 89687e911b9c32c8b00c3e5fa8f220448b3407d0 Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Tue, 17 Sep 2024 11:18:49 -0400 Subject: [PATCH 08/20] fixed tests related to accessibility updates Jira ticket: CAMS-428 Co-authored-by: Arthur Morrow <133667008+amorrow-flexion@users.noreply.github.com> Co-authored-by: James Brooks <12275865+jamesobrooks@users.noreply.github.com> --- .../src/case-detail/CaseDetailScreen.test.tsx | 60 ++++++++++++++++--- .../panels/CaseDetailAuditHistory.test.tsx | 2 +- .../panels/CaseDetailHeader.test.tsx | 6 +- .../src/my-cases/MyCasesScreen.test.tsx | 6 +- .../screen/StaffAssignmentScreen.test.tsx | 19 +++++- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/user-interface/src/case-detail/CaseDetailScreen.test.tsx b/user-interface/src/case-detail/CaseDetailScreen.test.tsx index f001fe9eb..5265a6737 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.test.tsx @@ -7,6 +7,7 @@ import { formatDate } from '@/lib/utils/datetime'; import { CaseDetail } from '@common/cams/cases'; import { Debtor, DebtorAttorney } from '@common/cams/parties'; import { MockAttorneys } from '@common/cams/test-utilities/attorneys.mock'; +import * as detailHeader from './panels/CaseDetailHeader'; const caseId = '101-23-12345'; @@ -31,13 +32,57 @@ describe('Case Detail screen tests', () => { type MaybeString = string | undefined; - beforeAll(() => { + beforeEach(() => { + vi.restoreAllMocks(); process.env = { ...env, CAMS_PA11Y: 'true', }; }); + test('should render CaseDetailHeader', async () => { + const testCaseDetail: CaseDetail = { + caseId: caseId, + dxtrId: '123', + chapter: '15', + regionId: '02', + officeName: 'New York', + officeCode: '000', + caseTitle: 'The Beach Boys', + dateFiled: '01-04-1962', + judgeName: rickBHartName, + courtId: '01', + courtName: 'Court of Law', + courtDivisionName: 'Manhattan', + courtDivisionCode: '081', + debtorTypeLabel: 'Corporate Business', + petitionLabel: 'Voluntary', + closedDate: '01-08-1963', + dismissedDate: '01-08-1964', + assignments: [brianWilson, carlWilson], + debtor: { + name: 'Roger Rabbit', + address1: '123 Rabbithole Lane', + address2: 'Apt 117', + address3: 'Suite C', + cityStateZipCountry: 'Ciudad Obregón GR 25443, MX', + }, + debtorAttorney, + groupDesignator: '01', + regionName: 'Test Region', + }; + const headerSpy = vi.spyOn(detailHeader, 'default'); + + render( + + + , + ); + await waitFor(() => { + expect(headerSpy).toHaveBeenCalled(); + }); + }); + test('should display case title, case number, dates, assignees, judge name, and debtor for the case', async () => { const testCaseDetail: CaseDetail = { caseId: caseId, @@ -78,10 +123,11 @@ describe('Case Detail screen tests', () => { await waitFor( async () => { const title = screen.getByTestId('case-detail-heading-title'); - expect(title.innerHTML).toEqual('The Beach Boys'); + const expectedTitle = ` - ${testCaseDetail.caseTitle}`; + expect(title.innerHTML).toEqual(expectedTitle); const caseNumber = document.querySelector('.case-number'); - expect(caseNumber?.innerHTML).toEqual(getCaseNumber(caseId)); + expect(caseNumber?.innerHTML).toEqual(caseId); const dateFiled = screen.getByTestId('case-detail-filed-date'); expect(dateFiled).toHaveTextContent('Filed'); @@ -99,9 +145,7 @@ describe('Case Detail screen tests', () => { expect(chapter.innerHTML).toEqual('Voluntary Chapter 15'); const courtName = screen.getByTestId('court-name-and-district'); - expect(courtName.innerHTML).toEqual( - `Court of Law - Manhattan (${testCaseDetail.courtDivisionCode})`, - ); + expect(courtName.innerHTML).toEqual(`Court of Law (${testCaseDetail.courtDivisionName})`); const region = screen.getByTestId('case-detail-region-id'); expect(region.innerHTML).toEqual('Region 2 - New York Office'); @@ -607,11 +651,11 @@ describe('Case Detail screen tests', () => { , ); - + const expectedTitle = ` - ${testCaseDetail.caseTitle}`; await waitFor( async () => { const title = screen.getByTestId('case-detail-heading-title'); - expect(title.innerHTML).toEqual('The Beach Boys'); + expect(title.innerHTML).toEqual(expectedTitle); const unassignedElement = document.querySelector('.unassigned-placeholder'); expect(unassignedElement).toBeInTheDocument(); diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx index 22b4425e6..955d9e902 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx @@ -133,7 +133,7 @@ describe('audit history tests', () => { render(); const emptyAssignments = await screen.findByTestId('empty-assignments-test-id'); - expect(emptyAssignments).toHaveTextContent('There are no assignments in the case history.'); + expect(emptyAssignments).toHaveTextContent('No changes have been made to this case'); const historyTable = screen.queryByTestId('history-table'); expect(historyTable).not.toBeInTheDocument(); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index 32dee83e6..57ca6269c 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -67,14 +67,14 @@ describe('Case Detail Header tests', () => { const app = await screen.findByTestId('app-component-test-id'); await waitFor( async () => { - const heading = await screen.findByTestId('case-detail-heading'); - expect(heading.innerHTML).toEqual('Case Detail'); + const heading = await screen.findByTestId('case-detail-heading-title'); + expect(heading.innerHTML).toEqual(` - ${testCaseDetail.caseTitle}`); }, { timeout: 1000 }, ); await waitFor(async () => { const title = await screen.findByTestId('case-detail-heading-title'); - expect(title.innerHTML).toEqual(testCaseDetail.caseTitle); + expect(title.innerHTML).toEqual(` - ${testCaseDetail.caseTitle}`); }); let normalHeader = await screen.findByTestId('case-detail-header'); diff --git a/user-interface/src/my-cases/MyCasesScreen.test.tsx b/user-interface/src/my-cases/MyCasesScreen.test.tsx index 695845678..76268e757 100644 --- a/user-interface/src/my-cases/MyCasesScreen.test.tsx +++ b/user-interface/src/my-cases/MyCasesScreen.test.tsx @@ -82,7 +82,11 @@ describe('MyCasesScreen', () => { }); vi.spyOn(LocalStorage, 'getSession').mockReturnValue(MockData.getCamsSession({ user })); - render(); + render( + + + , + ); const body = document.querySelector('body'); expect(body).toHaveTextContent('Invalid user expectation'); diff --git a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.test.tsx b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.test.tsx index d02dbc5bb..8bb862956 100644 --- a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.test.tsx +++ b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.test.tsx @@ -12,6 +12,7 @@ import Api2 from '@/lib/models/api2'; import testingUtilities from '@/lib/testing/testing-utilities'; import { SearchResultsProps } from '@/search-results/SearchResults'; import { CamsRole } from '@common/cams/roles'; +import { BrowserRouter } from 'react-router-dom'; describe('StaffAssignmentScreen', () => { test('should render a list of cases assigned to a case assignment manager', async () => { @@ -39,7 +40,11 @@ describe('StaffAssignmentScreen', () => { .spyOn(staffAssignmentRow, 'StaffAssignmentRow') .mockReturnValue(<>); - render(); + render( + + + , + ); expect(SearchResults).toHaveBeenCalledWith( { @@ -62,7 +67,11 @@ describe('StaffAssignmentScreen', () => { test('should render permission invalid error when CaseAssignmentManager is not found in user roles', async () => { testingUtilities.setUserWithRoles([]); const alertSpy = testingUtilities.spyOnGlobalAlert(); - render(); + render( + + + , + ); expect(alertSpy.error).toHaveBeenCalledWith('Invalid Permissions'); }); @@ -71,7 +80,11 @@ describe('StaffAssignmentScreen', () => { testingUtilities.setUser({ offices: undefined, roles: [CamsRole.CaseAssignmentManager] }); const SearchResults = vi.spyOn(searchResultsModule, 'SearchResults'); - render(); + render( + + + , + ); expect(SearchResults).toHaveBeenCalledWith( { From 1696b1f462375404a6ab78b35db9882be39101fc Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Tue, 17 Sep 2024 10:57:00 -0600 Subject: [PATCH 09/20] Exp - always take user back to original tab When clicking back link, the user is directed back to original tab. Tab name is updated on login. If user closes original tab, a new tab is opened with the same name. Jira ticket: CAMS-428 --- .../case-detail/panels/CaseDetailHeader.tsx | 6 +++--- .../src/lib/hooks/UseLocationTracker.ts | 18 ++++++++++++++++-- user-interface/src/login/Login.tsx | 2 ++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index 573f7c77e..5031883b6 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -21,7 +21,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { const appEl = document.querySelector('.App'); const camsHeader = document.querySelector('.cams-header'); - const { previousLocation } = useLocationTracker(); + const { previousLocation, homeTab } = useLocationTracker(); const modifyHeader = () => { if (camsHeader) { @@ -64,7 +64,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {
        - + Back to {getBackLinkText()} @@ -117,7 +117,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) {
        - + Back to {getBackLinkText()} diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts index ceefc8c29..4952c20c3 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.ts +++ b/user-interface/src/lib/hooks/UseLocationTracker.ts @@ -13,6 +13,11 @@ export default function useLocationTracker() { return storedLocation && isValidPath(storedLocation) ? storedLocation : '/my-cases'; }); + const [homeTab, setHomeTab] = useState(() => { + const target = localStorage.getItem('homeTab'); + + return target ?? ''; + }); const updateLocation = useCallback((newLocation?: string) => { if (newLocation && isValidPath(newLocation)) { @@ -23,14 +28,23 @@ export default function useLocationTracker() { setPreviousLocation(pathLocation); localStorage.setItem('previousLocation', pathLocation); } + if (window.name.match(/^CAMS_WINDOW_[0-9]+/)) { + setHomeTab(window.name); + localStorage.setItem('homeTab', window.name); + } }, []); useEffect(() => { const storedLocation = localStorage.getItem('previousLocation'); + const storedTab = localStorage.getItem('homeTab'); + if (storedLocation && isValidPath(storedLocation)) { setPreviousLocation(storedLocation); } - }); + if (storedTab && storedTab.length > 0) { + setHomeTab(storedTab); + } + }, []); - return { previousLocation, updateLocation }; + return { previousLocation, homeTab, updateLocation }; } diff --git a/user-interface/src/login/Login.tsx b/user-interface/src/login/Login.tsx index a7e9857a7..ee107a086 100644 --- a/user-interface/src/login/Login.tsx +++ b/user-interface/src/login/Login.tsx @@ -64,6 +64,8 @@ export function Login(props: LoginProps): React.ReactNode { } } + window.name = `CAMS_WINDOW_${Math.floor(Date.now() / 1000)}`; + let providerComponent; switch (provider) { case 'okta': From b0302cd7c01ad80233b815b422dab5cb6e0fff4f Mon Sep 17 00:00:00 2001 From: Arthur Morrow Date: Tue, 17 Sep 2024 16:30:27 -0400 Subject: [PATCH 10/20] initial testing for UseLocationTracker Jira ticket: CAMS-428 Co-authored-by: Fritz Madden <96319835+fmaddenflx@users.noreply.github.com> --- .../src/lib/hooks/UseLocationTracker.test.ts | 45 +++++++++++++++++++ .../src/lib/hooks/UseLocationTracker.ts | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 user-interface/src/lib/hooks/UseLocationTracker.test.ts diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.ts b/user-interface/src/lib/hooks/UseLocationTracker.test.ts new file mode 100644 index 000000000..df8dbdb5c --- /dev/null +++ b/user-interface/src/lib/hooks/UseLocationTracker.test.ts @@ -0,0 +1,45 @@ +import testingUtilities from '../testing/testing-utilities'; +import { isValidPath } from './UseLocationTracker'; +import useLocationTracker from './UseLocationTracker'; + +describe('isValidPath utility tests', () => { + test('should determine that a path is valid', () => { + expect(isValidPath('/f00')).toBe(true); + expect(isValidPath('/foo/bar')).toBe(true); + expect(isValidPath('/foo/bar/baz?query=something.or:something&also=this,or,that')).toBe(true); + expect(isValidPath('/_f00')).toBe(true); + }); + + test('should determine that a path is invalid', () => { + expect(isValidPath('/')).toBe(false); + expect(isValidPath('foobar')).toBe(false); + expect(isValidPath('//foobar')).toBe(false); + expect(isValidPath('0foo')).toBe(false); + expect(isValidPath('/0foo')).toBe(false); + }); +}); + +describe('useLocationTracker tests', () => { + test('should update local storage on call to updateLocation and return the new values when referencing previousLocation and homeTab', () => { + vi.mock('react-router-dom', () => ({ + ...vi.importActual('react-router-dom'), + useLocation: () => ({ + pathname: '/test/path', + }), + })); + testingUtilities.spyOnUseState(); + + const { previousLocation, homeTab } = useLocationTracker(); + + expect(previousLocation).toEqual('/my-cases'); + expect(homeTab).toEqual(''); + + window.name = 'foobar'; + + expect(homeTab).toEqual(''); + + window.name = 'CAMS_WINDOW_012'; + + expect(homeTab).toEqual(''); + }); +}); diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts index 4952c20c3..a807ec25d 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.ts +++ b/user-interface/src/lib/hooks/UseLocationTracker.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; export function isValidPath(path: string): boolean { - const pathRegex = /^\/[a-zA-Z0-9\-_]+$/; + const pathRegex = /^\/[a-zA-Z\-_]+[a-zA-Z0-9\-._~!$?&'()*+,;=:@/]*$/; return pathRegex.test(path); } From 881570973464f7f26ab137d05d64589520516aa3 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Tue, 17 Sep 2024 17:29:52 -0600 Subject: [PATCH 11/20] 100% test coverage on UseLocationTracker hook Jira ticket: CAMS-428 --- .../src/lib/hooks/UseLocationTracker.mock.tsx | 32 +++++ .../src/lib/hooks/UseLocationTracker.test.ts | 45 ------- .../src/lib/hooks/UseLocationTracker.test.tsx | 114 ++++++++++++++++++ .../src/lib/hooks/UseLocationTracker.ts | 3 +- 4 files changed, 148 insertions(+), 46 deletions(-) create mode 100644 user-interface/src/lib/hooks/UseLocationTracker.mock.tsx delete mode 100644 user-interface/src/lib/hooks/UseLocationTracker.test.ts create mode 100644 user-interface/src/lib/hooks/UseLocationTracker.test.tsx diff --git a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx new file mode 100644 index 000000000..24031f3a6 --- /dev/null +++ b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx @@ -0,0 +1,32 @@ +import { useEffect } from 'react'; +import useLocationTracker from './UseLocationTracker'; + +type TestComponentProps = { + location?: string; + target?: string; + updateLocation?: boolean; +}; + +export default function TestComponent(props: TestComponentProps) { + const { previousLocation, homeTab, updateLocation } = useLocationTracker(); + + useEffect(() => { + if (props.location) { + updateLocation(props.location); + } + if (props.updateLocation) { + updateLocation(); + } + if (props.target) { + window.name = props.target; + } + }, []); + + return ( + + ); +} diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.ts b/user-interface/src/lib/hooks/UseLocationTracker.test.ts deleted file mode 100644 index df8dbdb5c..000000000 --- a/user-interface/src/lib/hooks/UseLocationTracker.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import testingUtilities from '../testing/testing-utilities'; -import { isValidPath } from './UseLocationTracker'; -import useLocationTracker from './UseLocationTracker'; - -describe('isValidPath utility tests', () => { - test('should determine that a path is valid', () => { - expect(isValidPath('/f00')).toBe(true); - expect(isValidPath('/foo/bar')).toBe(true); - expect(isValidPath('/foo/bar/baz?query=something.or:something&also=this,or,that')).toBe(true); - expect(isValidPath('/_f00')).toBe(true); - }); - - test('should determine that a path is invalid', () => { - expect(isValidPath('/')).toBe(false); - expect(isValidPath('foobar')).toBe(false); - expect(isValidPath('//foobar')).toBe(false); - expect(isValidPath('0foo')).toBe(false); - expect(isValidPath('/0foo')).toBe(false); - }); -}); - -describe('useLocationTracker tests', () => { - test('should update local storage on call to updateLocation and return the new values when referencing previousLocation and homeTab', () => { - vi.mock('react-router-dom', () => ({ - ...vi.importActual('react-router-dom'), - useLocation: () => ({ - pathname: '/test/path', - }), - })); - testingUtilities.spyOnUseState(); - - const { previousLocation, homeTab } = useLocationTracker(); - - expect(previousLocation).toEqual('/my-cases'); - expect(homeTab).toEqual(''); - - window.name = 'foobar'; - - expect(homeTab).toEqual(''); - - window.name = 'CAMS_WINDOW_012'; - - expect(homeTab).toEqual(''); - }); -}); diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx new file mode 100644 index 000000000..3ff2c570f --- /dev/null +++ b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx @@ -0,0 +1,114 @@ +import { vi, Mock } from 'vitest'; +import { render } from '@testing-library/react'; +import { useLocation } from 'react-router-dom'; +import { isValidPath } from './UseLocationTracker'; +import TestComponent from './UseLocationTracker.mock'; + +describe('isValidPath utility tests', () => { + test('should determine that a path is valid', () => { + expect(isValidPath('/f00')).toBe(true); + expect(isValidPath('/foo/bar')).toBe(true); + expect(isValidPath('/foo/bar/baz?query=something.or:something&also=this,or,that')).toBe(true); + expect(isValidPath('/_f00')).toBe(true); + }); + + test('should determine that a path is invalid', () => { + expect(isValidPath('/')).toBe(false); + expect(isValidPath('foobar')).toBe(false); + expect(isValidPath('//foobar')).toBe(false); + expect(isValidPath('0foo')).toBe(false); + expect(isValidPath('/0foo')).toBe(false); + }); +}); + +vi.mock('react-router-dom', () => ({ + ...vi.importActual('react-router-dom'), + useLocation: vi.fn(), +})); + +describe('useLocationTracker tests', () => { + afterEach(() => { + vi.restoreAllMocks(); + window.name = ''; + localStorage.setItem('homeTab', ''); + localStorage.setItem('previousLocation', ''); + }); + + test('should return expected default previousLocation and homeTab values when no window.name is set and no previous URL is set', () => { + render(); + + const link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', '/my-cases'); + expect(link).toHaveAttribute('target', ''); + }); + + test('should return expected default previousLocation and homeTab values when window.name is set to an invalid name and no previous URL is set', () => { + const newTarget = 'foobar'; + window.name = newTarget; + + render(); + + const link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', '/my-cases'); + expect(link).toHaveAttribute('target', ''); + }); + + test('should return expected default previousLocation and homeTab values when an invalid path is supplied is set to an invalid name and no previous URL is set', () => { + const path = 'foobar'; + (useLocation as Mock).mockReturnValue({ pathname: undefined }); + + render(); + + const link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', '/my-cases'); + expect(link).toHaveAttribute('target', ''); + }); + + test('should reset previousLocation and homeTab to values from localStorage after forced refresh of browser tab', () => { + const path = '/foobar'; + const target = 'CAMS_WINDOW_012'; + window.name = target; + (useLocation as Mock).mockReturnValue({ pathname: undefined }); + + render(); + + let link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', path); + expect(link).toHaveAttribute('target', target); + + render(); + + link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', path); + expect(link).toHaveAttribute('target', target); + }); + + test('should return expected given previousLocation and homeTab values when a valid path was previously visited', () => { + (useLocation as Mock).mockReturnValue({ pathname: '/some/valid/path' }); + + render(); + + const link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', '/some/valid/path'); + expect(link).toHaveAttribute('target', ''); + }); + + test('should call updateLocation and return given previousLocation and homeTab values when window.name is set to a valid name and previous URL is supplied to useLocationTracker', () => { + const path = '/supplied/path'; + const newTarget = 'CAMS_WINDOW_012'; + window.name = newTarget; + + render(); + + const link = document.querySelector('a.back-button'); + + expect(link).toHaveAttribute('href', path); + expect(link).toHaveAttribute('target', newTarget); + }); +}); diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts index a807ec25d..702100072 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.ts +++ b/user-interface/src/lib/hooks/UseLocationTracker.ts @@ -24,7 +24,8 @@ export default function useLocationTracker() { setPreviousLocation(newLocation); localStorage.setItem('previousLocation', newLocation); } else { - const pathLocation = location.pathname ?? '/my-cases'; + const pathLocation = + location.pathname && location.pathname.length > 0 ? location.pathname : '/my-cases'; setPreviousLocation(pathLocation); localStorage.setItem('previousLocation', pathLocation); } From dd315a0249da2d8a1b0dd65ce39dc4e2b062f330 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Wed, 18 Sep 2024 10:19:32 -0600 Subject: [PATCH 12/20] Test coverage > 96% for CaseDetailHeader.tsx Jira ticket: CAMS-428 --- .../panels/CaseDetailHeader.test.tsx | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index 57ca6269c..bdb29a0bc 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -1,22 +1,25 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; +import * as useLocationTracker from '@/lib/hooks/UseLocationTracker'; import CaseDetailHeader from './CaseDetailHeader'; import CaseDetailScreen from '../CaseDetailScreen'; import { MockData } from '@common/cams/test-utilities/mock-data'; +import { ResourceActions } from '@common/cams/actions'; +import { CaseDetail } from '@common/cams/cases'; + +function basicRender(caseDetail: ResourceActions, isLoading: boolean) { + render( + + + , + ); +} describe('Case Detail Header tests', () => { const testCaseDetail = MockData.getCaseDetail(); test('should render loading info when isLoading is true', () => { - render( - - - , - ); + basicRender(testCaseDetail, true); const isLoadingH1 = screen.getByTestId('case-detail-heading'); const isLoadingH2 = screen.getByTestId('loading-h2'); @@ -26,15 +29,7 @@ describe('Case Detail Header tests', () => { }); test('should render case detail info when isLoading is false', () => { - render( - - - , - ); + basicRender(testCaseDetail, false); const isLoadingH1 = screen.getByTestId('case-detail-heading'); const isLoadingH2 = screen.getByTestId('case-detail-heading-title'); @@ -121,4 +116,38 @@ describe('Case Detail Header tests', () => { { timeout: 5000 }, ); }); + + describe('back link tests', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + const backLinkTestCases = [ + ['/my-cases', '', 'My Cases'], + ['/search', 'CAMS_WINDOW_012', 'Case Search'], + ['/staff-assignment', 'CAMS_WINDOW_012', 'Staff Assignment'], + ['/data-verification', 'CAMS_WINDOW_345', 'Data Verification'], + ['/foobar', 'CAMS_WINDOW_678', 'Case List'], + ]; + + test.each(backLinkTestCases)( + 'back link should be setup to link back to Search in tab CAMS_WINDOW_012', + (previousLocation: string, homeTab: string, displayText: string) => { + vi.spyOn(useLocationTracker, 'default').mockImplementation(() => { + return { + previousLocation, + homeTab, + updateLocation: vi.fn(), + }; + }); + + basicRender(testCaseDetail, false); + + const backLink = document.querySelector('.back-button'); + expect(backLink).toHaveAttribute('href', previousLocation); + expect(backLink).toHaveAttribute('target', homeTab); + expect(backLink).toHaveTextContent(`Back to ${displayText}`); + }, + ); + }); }); From 5ede4d6db1d1cdac998b9508442e0f7afe52d207 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Wed, 18 Sep 2024 12:59:44 -0600 Subject: [PATCH 13/20] Fixed a security iss in the location tracker mock Jira ticket: CAMS-428 --- user-interface/src/lib/hooks/UseLocationTracker.mock.tsx | 7 ++++++- user-interface/src/lib/hooks/UseLocationTracker.test.tsx | 8 ++++---- user-interface/src/lib/hooks/UseLocationTracker.ts | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx index 24031f3a6..7798d90fe 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx +++ b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx @@ -24,7 +24,12 @@ export default function TestComponent(props: TestComponentProps) { return ( diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx index 3ff2c570f..c69f14be2 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx +++ b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx @@ -40,7 +40,7 @@ describe('useLocationTracker tests', () => { const link = document.querySelector('a.back-button'); expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', ''); + expect(link).toHaveAttribute('target', '_self'); }); test('should return expected default previousLocation and homeTab values when window.name is set to an invalid name and no previous URL is set', () => { @@ -52,7 +52,7 @@ describe('useLocationTracker tests', () => { const link = document.querySelector('a.back-button'); expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', ''); + expect(link).toHaveAttribute('target', '_self'); }); test('should return expected default previousLocation and homeTab values when an invalid path is supplied is set to an invalid name and no previous URL is set', () => { @@ -64,7 +64,7 @@ describe('useLocationTracker tests', () => { const link = document.querySelector('a.back-button'); expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', ''); + expect(link).toHaveAttribute('target', '_self'); }); test('should reset previousLocation and homeTab to values from localStorage after forced refresh of browser tab', () => { @@ -96,7 +96,7 @@ describe('useLocationTracker tests', () => { const link = document.querySelector('a.back-button'); expect(link).toHaveAttribute('href', '/some/valid/path'); - expect(link).toHaveAttribute('target', ''); + expect(link).toHaveAttribute('target', '_self'); }); test('should call updateLocation and return given previousLocation and homeTab values when window.name is set to a valid name and previous URL is supplied to useLocationTracker', () => { diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts index 702100072..1ea9b6dc2 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.ts +++ b/user-interface/src/lib/hooks/UseLocationTracker.ts @@ -16,7 +16,7 @@ export default function useLocationTracker() { const [homeTab, setHomeTab] = useState(() => { const target = localStorage.getItem('homeTab'); - return target ?? ''; + return target ?? '_self'; }); const updateLocation = useCallback((newLocation?: string) => { From 0e1c0aeadc44fb9b5d6a6a961b46a5900aa18171 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Wed, 18 Sep 2024 14:58:57 -0600 Subject: [PATCH 14/20] Hopefully fixed security issues with mock replace anchor tag in mock with a div Jira ticket: CAMS-428 --- .../src/lib/hooks/UseLocationTracker.mock.tsx | 11 +---- .../src/lib/hooks/UseLocationTracker.test.tsx | 42 +++++++++---------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx index 7798d90fe..edacbdd77 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx +++ b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx @@ -23,15 +23,8 @@ export default function TestComponent(props: TestComponentProps) { }, []); return ( -
        - - Link - +
        + Link
        ); } diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx index c69f14be2..43d70f41e 100644 --- a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx +++ b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx @@ -37,10 +37,10 @@ describe('useLocationTracker tests', () => { test('should return expected default previousLocation and homeTab values when no window.name is set and no previous URL is set', () => { render(); - const link = document.querySelector('a.back-button'); + const link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', '_self'); + expect(link).toHaveAttribute('data-href', '/my-cases'); + expect(link).toHaveAttribute('data-target', '_self'); }); test('should return expected default previousLocation and homeTab values when window.name is set to an invalid name and no previous URL is set', () => { @@ -49,10 +49,10 @@ describe('useLocationTracker tests', () => { render(); - const link = document.querySelector('a.back-button'); + const link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', '_self'); + expect(link).toHaveAttribute('data-href', '/my-cases'); + expect(link).toHaveAttribute('data-target', ''); }); test('should return expected default previousLocation and homeTab values when an invalid path is supplied is set to an invalid name and no previous URL is set', () => { @@ -61,10 +61,10 @@ describe('useLocationTracker tests', () => { render(); - const link = document.querySelector('a.back-button'); + const link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', '/my-cases'); - expect(link).toHaveAttribute('target', '_self'); + expect(link).toHaveAttribute('data-href', '/my-cases'); + expect(link).toHaveAttribute('data-target', ''); }); test('should reset previousLocation and homeTab to values from localStorage after forced refresh of browser tab', () => { @@ -75,17 +75,17 @@ describe('useLocationTracker tests', () => { render(); - let link = document.querySelector('a.back-button'); + let link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', path); - expect(link).toHaveAttribute('target', target); + expect(link).toHaveAttribute('data-href', path); + expect(link).toHaveAttribute('data-target', target); render(); - link = document.querySelector('a.back-button'); + link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', path); - expect(link).toHaveAttribute('target', target); + expect(link).toHaveAttribute('data-href', path); + expect(link).toHaveAttribute('data-target', target); }); test('should return expected given previousLocation and homeTab values when a valid path was previously visited', () => { @@ -93,10 +93,10 @@ describe('useLocationTracker tests', () => { render(); - const link = document.querySelector('a.back-button'); + const link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', '/some/valid/path'); - expect(link).toHaveAttribute('target', '_self'); + expect(link).toHaveAttribute('data-href', '/some/valid/path'); + expect(link).toHaveAttribute('data-target', ''); }); test('should call updateLocation and return given previousLocation and homeTab values when window.name is set to a valid name and previous URL is supplied to useLocationTracker', () => { @@ -106,9 +106,9 @@ describe('useLocationTracker tests', () => { render(); - const link = document.querySelector('a.back-button'); + const link = document.querySelector('.back-button'); - expect(link).toHaveAttribute('href', path); - expect(link).toHaveAttribute('target', newTarget); + expect(link).toHaveAttribute('data-href', path); + expect(link).toHaveAttribute('data-target', newTarget); }); }); From 27e7f280cfcced876930f7d6b4cfafd6b463e467 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Fri, 20 Sep 2024 10:57:03 -0600 Subject: [PATCH 15/20] Removed location tracker hook and back links Jira ticket: CAMS-428 Co-authored-by: Fritz Madden <96319835+fmaddenflx@users.noreply.github.com> Co-authored-by: Arthur Morrow <133667008+amorrow-flexion@users.noreply.github.com> Co-authored-by: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Co-authored-by: Brian Posey <15091170+btposey@users.noreply.github.com>, --- .../panels/CaseDetailHeader.test.tsx | 35 ------ .../case-detail/panels/CaseDetailHeader.tsx | 33 ----- .../DataVerificationScreen.tsx | 6 - .../src/lib/hooks/UseLocationTracker.mock.tsx | 30 ----- .../src/lib/hooks/UseLocationTracker.test.tsx | 114 ------------------ .../src/lib/hooks/UseLocationTracker.ts | 51 -------- user-interface/src/login/Login.tsx | 2 - user-interface/src/my-cases/MyCasesScreen.tsx | 9 +- user-interface/src/search/SearchScreen.tsx | 6 - .../screen/StaffAssignmentScreen.tsx | 9 +- 10 files changed, 2 insertions(+), 293 deletions(-) delete mode 100644 user-interface/src/lib/hooks/UseLocationTracker.mock.tsx delete mode 100644 user-interface/src/lib/hooks/UseLocationTracker.test.tsx delete mode 100644 user-interface/src/lib/hooks/UseLocationTracker.ts diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index bdb29a0bc..f59582566 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -1,6 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; -import * as useLocationTracker from '@/lib/hooks/UseLocationTracker'; import CaseDetailHeader from './CaseDetailHeader'; import CaseDetailScreen from '../CaseDetailScreen'; import { MockData } from '@common/cams/test-utilities/mock-data'; @@ -116,38 +115,4 @@ describe('Case Detail Header tests', () => { { timeout: 5000 }, ); }); - - describe('back link tests', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - const backLinkTestCases = [ - ['/my-cases', '', 'My Cases'], - ['/search', 'CAMS_WINDOW_012', 'Case Search'], - ['/staff-assignment', 'CAMS_WINDOW_012', 'Staff Assignment'], - ['/data-verification', 'CAMS_WINDOW_345', 'Data Verification'], - ['/foobar', 'CAMS_WINDOW_678', 'Case List'], - ]; - - test.each(backLinkTestCases)( - 'back link should be setup to link back to Search in tab CAMS_WINDOW_012', - (previousLocation: string, homeTab: string, displayText: string) => { - vi.spyOn(useLocationTracker, 'default').mockImplementation(() => { - return { - previousLocation, - homeTab, - updateLocation: vi.fn(), - }; - }); - - basicRender(testCaseDetail, false); - - const backLink = document.querySelector('.back-button'); - expect(backLink).toHaveAttribute('href', previousLocation); - expect(backLink).toHaveAttribute('target', homeTab); - expect(backLink).toHaveTextContent(`Back to ${displayText}`); - }, - ); - }); }); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index 5031883b6..ff82795b2 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -1,11 +1,8 @@ import './CaseDetailHeader.scss'; -import { Link } from 'react-router-dom'; -import Icon from '@/lib/components/uswds/Icon'; import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; import { useEffect } from 'react'; import useFixedPosition from '@/lib/hooks/UseFixedPosition'; import { CaseDetail } from '@common/cams/cases'; -import useLocationTracker from '../../lib/hooks/UseLocationTracker'; export interface CaseDetailHeaderProps { isLoading: boolean; @@ -21,8 +18,6 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { const appEl = document.querySelector('.App'); const camsHeader = document.querySelector('.cams-header'); - const { previousLocation, homeTab } = useLocationTracker(); - const modifyHeader = () => { if (camsHeader) { const caseDetailHeader = document.querySelector('.case-detail-header.fixed'); @@ -38,14 +33,6 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { } }; - function getBackLinkText() { - if (previousLocation.includes('my-cases')) return 'My Cases'; - if (previousLocation.includes('search')) return 'Case Search'; - if (previousLocation.includes('staff-assignment')) return 'Staff Assignment'; - if (previousLocation.includes('data-verification')) return 'Data Verification'; - return 'Case List'; - } - useEffect(() => { if (!props.isLoading && appEl && camsHeader) { appEl.addEventListener('scroll', modifyHeader); @@ -61,15 +48,6 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { {isFixed && ( <>
        -
        -
        -
        - - - Back to {getBackLinkText()} - -
        -
        @@ -114,17 +92,6 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { )} {!isFixed && (
        -
        -
        -
        - - - Back to {getBackLinkText()} - -
        -
        -
        -
        diff --git a/user-interface/src/data-verification/DataVerificationScreen.tsx b/user-interface/src/data-verification/DataVerificationScreen.tsx index e8fe0596c..6aea4c43c 100644 --- a/user-interface/src/data-verification/DataVerificationScreen.tsx +++ b/user-interface/src/data-verification/DataVerificationScreen.tsx @@ -26,7 +26,6 @@ import { ResponseBody } from '@common/api/response'; import { CamsRole } from '@common/cams/roles'; import LocalStorage from '@/lib/utils/local-storage'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; -import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export function officeSorter(a: OfficeDetails, b: OfficeDetails) { const aKey = a.courtName + '-' + a.courtDivisionName; @@ -52,11 +51,6 @@ export default function DataVerificationScreen() { const globalAlert = useGlobalAlert(); const session = LocalStorage.getSession(); - const { updateLocation } = useLocationTracker(); - - useEffect(() => { - updateLocation(); - }, [location.pathname]); const regionNumber = '02'; diff --git a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx b/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx deleted file mode 100644 index edacbdd77..000000000 --- a/user-interface/src/lib/hooks/UseLocationTracker.mock.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect } from 'react'; -import useLocationTracker from './UseLocationTracker'; - -type TestComponentProps = { - location?: string; - target?: string; - updateLocation?: boolean; -}; - -export default function TestComponent(props: TestComponentProps) { - const { previousLocation, homeTab, updateLocation } = useLocationTracker(); - - useEffect(() => { - if (props.location) { - updateLocation(props.location); - } - if (props.updateLocation) { - updateLocation(); - } - if (props.target) { - window.name = props.target; - } - }, []); - - return ( -
        - Link -
        - ); -} diff --git a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx b/user-interface/src/lib/hooks/UseLocationTracker.test.tsx deleted file mode 100644 index 43d70f41e..000000000 --- a/user-interface/src/lib/hooks/UseLocationTracker.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { vi, Mock } from 'vitest'; -import { render } from '@testing-library/react'; -import { useLocation } from 'react-router-dom'; -import { isValidPath } from './UseLocationTracker'; -import TestComponent from './UseLocationTracker.mock'; - -describe('isValidPath utility tests', () => { - test('should determine that a path is valid', () => { - expect(isValidPath('/f00')).toBe(true); - expect(isValidPath('/foo/bar')).toBe(true); - expect(isValidPath('/foo/bar/baz?query=something.or:something&also=this,or,that')).toBe(true); - expect(isValidPath('/_f00')).toBe(true); - }); - - test('should determine that a path is invalid', () => { - expect(isValidPath('/')).toBe(false); - expect(isValidPath('foobar')).toBe(false); - expect(isValidPath('//foobar')).toBe(false); - expect(isValidPath('0foo')).toBe(false); - expect(isValidPath('/0foo')).toBe(false); - }); -}); - -vi.mock('react-router-dom', () => ({ - ...vi.importActual('react-router-dom'), - useLocation: vi.fn(), -})); - -describe('useLocationTracker tests', () => { - afterEach(() => { - vi.restoreAllMocks(); - window.name = ''; - localStorage.setItem('homeTab', ''); - localStorage.setItem('previousLocation', ''); - }); - - test('should return expected default previousLocation and homeTab values when no window.name is set and no previous URL is set', () => { - render(); - - const link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', '/my-cases'); - expect(link).toHaveAttribute('data-target', '_self'); - }); - - test('should return expected default previousLocation and homeTab values when window.name is set to an invalid name and no previous URL is set', () => { - const newTarget = 'foobar'; - window.name = newTarget; - - render(); - - const link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', '/my-cases'); - expect(link).toHaveAttribute('data-target', ''); - }); - - test('should return expected default previousLocation and homeTab values when an invalid path is supplied is set to an invalid name and no previous URL is set', () => { - const path = 'foobar'; - (useLocation as Mock).mockReturnValue({ pathname: undefined }); - - render(); - - const link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', '/my-cases'); - expect(link).toHaveAttribute('data-target', ''); - }); - - test('should reset previousLocation and homeTab to values from localStorage after forced refresh of browser tab', () => { - const path = '/foobar'; - const target = 'CAMS_WINDOW_012'; - window.name = target; - (useLocation as Mock).mockReturnValue({ pathname: undefined }); - - render(); - - let link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', path); - expect(link).toHaveAttribute('data-target', target); - - render(); - - link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', path); - expect(link).toHaveAttribute('data-target', target); - }); - - test('should return expected given previousLocation and homeTab values when a valid path was previously visited', () => { - (useLocation as Mock).mockReturnValue({ pathname: '/some/valid/path' }); - - render(); - - const link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', '/some/valid/path'); - expect(link).toHaveAttribute('data-target', ''); - }); - - test('should call updateLocation and return given previousLocation and homeTab values when window.name is set to a valid name and previous URL is supplied to useLocationTracker', () => { - const path = '/supplied/path'; - const newTarget = 'CAMS_WINDOW_012'; - window.name = newTarget; - - render(); - - const link = document.querySelector('.back-button'); - - expect(link).toHaveAttribute('data-href', path); - expect(link).toHaveAttribute('data-target', newTarget); - }); -}); diff --git a/user-interface/src/lib/hooks/UseLocationTracker.ts b/user-interface/src/lib/hooks/UseLocationTracker.ts deleted file mode 100644 index 1ea9b6dc2..000000000 --- a/user-interface/src/lib/hooks/UseLocationTracker.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useLocation } from 'react-router-dom'; - -export function isValidPath(path: string): boolean { - const pathRegex = /^\/[a-zA-Z\-_]+[a-zA-Z0-9\-._~!$?&'()*+,;=:@/]*$/; - return pathRegex.test(path); -} - -export default function useLocationTracker() { - const location = useLocation(); - const [previousLocation, setPreviousLocation] = useState(() => { - const storedLocation = localStorage.getItem('previousLocation'); - - return storedLocation && isValidPath(storedLocation) ? storedLocation : '/my-cases'; - }); - const [homeTab, setHomeTab] = useState(() => { - const target = localStorage.getItem('homeTab'); - - return target ?? '_self'; - }); - - const updateLocation = useCallback((newLocation?: string) => { - if (newLocation && isValidPath(newLocation)) { - setPreviousLocation(newLocation); - localStorage.setItem('previousLocation', newLocation); - } else { - const pathLocation = - location.pathname && location.pathname.length > 0 ? location.pathname : '/my-cases'; - setPreviousLocation(pathLocation); - localStorage.setItem('previousLocation', pathLocation); - } - if (window.name.match(/^CAMS_WINDOW_[0-9]+/)) { - setHomeTab(window.name); - localStorage.setItem('homeTab', window.name); - } - }, []); - - useEffect(() => { - const storedLocation = localStorage.getItem('previousLocation'); - const storedTab = localStorage.getItem('homeTab'); - - if (storedLocation && isValidPath(storedLocation)) { - setPreviousLocation(storedLocation); - } - if (storedTab && storedTab.length > 0) { - setHomeTab(storedTab); - } - }, []); - - return { previousLocation, homeTab, updateLocation }; -} diff --git a/user-interface/src/login/Login.tsx b/user-interface/src/login/Login.tsx index ee107a086..a7e9857a7 100644 --- a/user-interface/src/login/Login.tsx +++ b/user-interface/src/login/Login.tsx @@ -64,8 +64,6 @@ export function Login(props: LoginProps): React.ReactNode { } } - window.name = `CAMS_WINDOW_${Math.floor(Date.now() / 1000)}`; - let providerComponent; switch (provider) { case 'okta': diff --git a/user-interface/src/my-cases/MyCasesScreen.tsx b/user-interface/src/my-cases/MyCasesScreen.tsx index 78ddd8ee6..47ea49415 100644 --- a/user-interface/src/my-cases/MyCasesScreen.tsx +++ b/user-interface/src/my-cases/MyCasesScreen.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { UswdsButtonStyle } from '@/lib/components/uswds/Button'; import Modal from '@/lib/components/uswds/modal/Modal'; import { ModalRefType } from '@/lib/components/uswds/modal/modal-refs'; @@ -16,13 +16,10 @@ import './MyCasesScreen.scss'; import ScreenInfoButton from '@/lib/components/cams/ScreenInfoButton'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; -import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export const MyCasesScreen = () => { const screenTitle = 'My Cases'; - const { updateLocation } = useLocationTracker(); - const infoModalRef = useRef(null); const infoModalId = 'info-modal'; const session = LocalStorage.getSession(); @@ -46,10 +43,6 @@ export const MyCasesScreen = () => { }, }; - useEffect(() => { - updateLocation(); - }, [location.pathname]); - return ( diff --git a/user-interface/src/search/SearchScreen.tsx b/user-interface/src/search/SearchScreen.tsx index c508cf002..0c05b8fbc 100644 --- a/user-interface/src/search/SearchScreen.tsx +++ b/user-interface/src/search/SearchScreen.tsx @@ -19,7 +19,6 @@ import { SearchResultsRow } from './SearchResultsRow'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; -import useLocationTracker from '@/lib/hooks/UseLocationTracker'; export default function SearchScreen() { const [searchPredicate, setSearchPredicate] = useState({ @@ -36,11 +35,6 @@ export default function SearchScreen() { const api = useApi2(); const globalAlert = useGlobalAlert(); - const { updateLocation } = useLocationTracker(); - - useEffect(() => { - updateLocation(); - }, [location.pathname]); function getChapters() { const chapterArray: ComboOption[] = []; diff --git a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx index 81144e40d..a3b05d0b8 100644 --- a/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx +++ b/user-interface/src/staff-assignment/screen/StaffAssignmentScreen.tsx @@ -8,7 +8,7 @@ import { DEFAULT_SEARCH_OFFSET, } from '@common/api/search'; import { CamsUser } from '@common/cams/users'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { SearchResults, SearchResultsRowProps } from '@/search-results/SearchResults'; import { StaffAssignmentHeader } from '../header/StaffAssignmentHeader'; import { StaffAssignmentRow } from '../row/StaffAssignmentRow'; @@ -19,7 +19,6 @@ import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; import ScreenInfoButton from '@/lib/components/cams/ScreenInfoButton'; import DocumentTitle from '@/lib/components/cams/DocumentTitle/DocumentTitle'; import { MainContent } from '@/lib/components/cams/MainContent/MainContent'; -import useLocationTracker from '@/lib/hooks/UseLocationTracker'; function getPredicateByUserContext(user: CamsUser): CasesSearchPredicate { const predicate: CasesSearchPredicate = { @@ -69,12 +68,6 @@ export const StaffAssignmentScreen = () => { }, }; - const { updateLocation } = useLocationTracker(); - - useEffect(() => { - updateLocation(); - }, [location.pathname]); - return ( From 395f89291e500740738a8eab53d638478b8a4b07 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Fri, 20 Sep 2024 12:16:33 -0600 Subject: [PATCH 16/20] Added clipboard copy tests Jira ticket: CAMS-428 --- .../src/case-detail/CaseDetailScreen.test.tsx | 2 +- .../panels/CaseDetailHeader.test.tsx | 40 ++++++++++++++++++- .../case-detail/panels/CaseDetailHeader.tsx | 36 ++++++++++++----- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/user-interface/src/case-detail/CaseDetailScreen.test.tsx b/user-interface/src/case-detail/CaseDetailScreen.test.tsx index 5265a6737..aded53818 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.test.tsx @@ -127,7 +127,7 @@ describe('Case Detail screen tests', () => { expect(title.innerHTML).toEqual(expectedTitle); const caseNumber = document.querySelector('.case-number'); - expect(caseNumber?.innerHTML).toEqual(caseId); + expect(caseNumber?.textContent?.trim()).toEqual(caseId); const dateFiled = screen.getByTestId('case-detail-filed-date'); expect(dateFiled).toHaveTextContent('Filed'); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index f59582566..4c197f66f 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -1,10 +1,11 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; -import CaseDetailHeader from './CaseDetailHeader'; +import CaseDetailHeader, { copyCaseNumber } from './CaseDetailHeader'; import CaseDetailScreen from '../CaseDetailScreen'; import { MockData } from '@common/cams/test-utilities/mock-data'; import { ResourceActions } from '@common/cams/actions'; import { CaseDetail } from '@common/cams/cases'; +import { MockInstance } from 'vitest'; function basicRender(caseDetail: ResourceActions, isLoading: boolean) { render( @@ -115,4 +116,41 @@ describe('Case Detail Header tests', () => { { timeout: 5000 }, ); }); + + describe('Testing the clipboard with caseId', () => { + let writeTextMock: MockInstance<(data: string) => Promise> = vi + .fn() + .mockResolvedValue(''); + + beforeEach(() => { + if (!navigator.clipboard) { + Object.assign(navigator, { + clipboard: { + writeText: writeTextMock, + }, + }); + } else { + writeTextMock = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(); + } + }); + + test('clicking copy button should write caseId to clipboard', async () => { + basicRender(testCaseDetail, false); + + const caseIdCopyButton = document.querySelector('#header-case-id'); + + fireEvent.click(caseIdCopyButton!); + + expect(writeTextMock).toHaveBeenCalledWith(testCaseDetail.caseId); + }); + + test('should only copy to clipboard if we have a valid case number', () => { + copyCaseNumber('abcdefg#!@#$%'); + expect(writeTextMock).not.toHaveBeenCalled(); + + copyCaseNumber(testCaseDetail.caseId); + expect(writeTextMock).toHaveBeenCalledWith(testCaseDetail.caseId); + expect(writeTextMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index ff82795b2..7b4bf7f75 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -3,6 +3,14 @@ import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; import { useEffect } from 'react'; import useFixedPosition from '@/lib/hooks/UseFixedPosition'; import { CaseDetail } from '@common/cams/cases'; +import Icon from '@/lib/components/uswds/Icon'; + +export function copyCaseNumber(caseId: string | undefined): void { + const CASE_ID_PATTERN = /^\d{3}-\d{2}-\d{5}$/; + if (caseId && CASE_ID_PATTERN.test(caseId)) { + navigator.clipboard.writeText(caseId); + } +} export interface CaseDetailHeaderProps { isLoading: boolean; @@ -33,6 +41,22 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { } }; + function printCaseIdHeader() { + return ( +

        + {props.caseId}{' '} + +

        + ); + } + useEffect(() => { if (!props.isLoading && appEl && camsHeader) { appEl.addEventListener('scroll', modifyHeader); @@ -116,11 +140,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { {props.isLoading && (
        -
        -

        - {props.caseId} -

        -
        +
        {printCaseIdHeader()}
        )} @@ -128,11 +148,7 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { {!props.isLoading && (
        -
        -

        - {props.caseId} -

        -
        +
        {printCaseIdHeader()}

        Date: Fri, 20 Sep 2024 14:47:42 -0400 Subject: [PATCH 17/20] moved copyCaseNumber to lib/utils and renamed formatCaseNumber Jira ticket: CAMS-428 Co-authored-by: Arthur Morrow <133667008+amorrow-flexion@users.noreply.github.com> Co-authored-by: Brian Posey <15091170+btposey@users.noreply.github.com>, --- .../src/case-detail/CaseDetailScreen.test.tsx | 2 +- .../panels/CaseDetailAssociatedCases.test.tsx | 2 +- .../panels/CaseDetailAssociatedCases.tsx | 2 +- .../panels/CaseDetailHeader.test.tsx | 3 +- .../case-detail/panels/CaseDetailHeader.tsx | 9 +-- .../panels/CaseDetailOverview.test.tsx | 2 +- .../case-detail/panels/CaseDetailOverview.tsx | 2 +- .../TransferOrderAccordion.test.tsx | 2 +- .../TransferOrderAccordion.tsx | 2 +- .../ConsolidationOrderAccordion.test.tsx | 2 +- .../ConsolidationOrderAccordionView.tsx | 2 +- .../ConsolidationOrderModal.test.tsx | 2 +- .../consolidation/ConsolidationOrderModal.tsx | 2 +- .../consolidationsUseCase.test.ts | 2 +- .../consolidation/consolidationsUseCase.ts | 2 +- .../transfer/PendingTransferOrder.test.tsx | 2 +- .../transfer/PendingTransferOrder.tsx | 2 +- .../transfer/SuggestedTransferCases.test.tsx | 2 +- .../TransferConfirmationModal.test.tsx | 2 +- .../transfer/TransferConfirmationModal.tsx | 2 +- .../src/lib/components/CaseNumber.tsx | 2 +- .../src/lib/utils/caseNumber.test.ts | 55 +++++++++++++++++++ .../{formatCaseNumber.ts => caseNumber.ts} | 7 +++ .../src/lib/utils/formatCaseNumber.test.ts | 24 -------- .../modal/AssignAttorneyModal.tsx | 2 +- .../row/StaffAssignmentRow.internal.ts | 2 +- .../row/StaffAssignmentRow.test.tsx | 2 +- 27 files changed, 87 insertions(+), 55 deletions(-) create mode 100644 user-interface/src/lib/utils/caseNumber.test.ts rename user-interface/src/lib/utils/{formatCaseNumber.ts => caseNumber.ts} (52%) delete mode 100644 user-interface/src/lib/utils/formatCaseNumber.test.ts diff --git a/user-interface/src/case-detail/CaseDetailScreen.test.tsx b/user-interface/src/case-detail/CaseDetailScreen.test.tsx index aded53818..43700b6fa 100644 --- a/user-interface/src/case-detail/CaseDetailScreen.test.tsx +++ b/user-interface/src/case-detail/CaseDetailScreen.test.tsx @@ -2,7 +2,7 @@ import { BrowserRouter, MemoryRouter } from 'react-router-dom'; import { describe } from 'vitest'; import { render, waitFor, screen, queryByTestId } from '@testing-library/react'; import CaseDetailScreen from './CaseDetailScreen'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { formatDate } from '@/lib/utils/datetime'; import { CaseDetail } from '@common/cams/cases'; import { Debtor, DebtorAttorney } from '@common/cams/parties'; diff --git a/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.test.tsx b/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.test.tsx index dc5800d7c..741d44438 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.test.tsx @@ -4,7 +4,7 @@ import CaseDetailAssociatedCases from './CaseDetailAssociatedCases'; import { ConsolidationFrom, ConsolidationTo, EventCaseReference } from '@common/cams/events'; import { ConsolidationType } from '@common/cams/orders'; import { BrowserRouter } from 'react-router-dom'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { formatDate } from '@/lib/utils/datetime'; function getAssociatedCasesMock(caseId: string, consolidationType: ConsolidationType) { diff --git a/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.tsx b/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.tsx index 42900cbed..43998b21a 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAssociatedCases.tsx @@ -4,7 +4,7 @@ import { CaseNumber } from '@/lib/components/CaseNumber'; import { formatDate } from '@/lib/utils/datetime'; import { consolidationTypeMap } from '@/lib/utils/labels'; import './CaseDetailAssociatedCases.scss'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import Alert, { UswdsAlertStyle } from '@/lib/components/uswds/Alert'; export interface CaseDetailAssociatedCasesProps { diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx index 4c197f66f..9c9765bfc 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.test.tsx @@ -1,11 +1,12 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; -import CaseDetailHeader, { copyCaseNumber } from './CaseDetailHeader'; +import CaseDetailHeader from './CaseDetailHeader'; import CaseDetailScreen from '../CaseDetailScreen'; import { MockData } from '@common/cams/test-utilities/mock-data'; import { ResourceActions } from '@common/cams/actions'; import { CaseDetail } from '@common/cams/cases'; import { MockInstance } from 'vitest'; +import { copyCaseNumber } from '@/lib/utils/caseNumber'; function basicRender(caseDetail: ResourceActions, isLoading: boolean) { render( diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index 7b4bf7f75..c9e4e3c68 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -1,16 +1,9 @@ import './CaseDetailHeader.scss'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; import { useEffect } from 'react'; import useFixedPosition from '@/lib/hooks/UseFixedPosition'; import { CaseDetail } from '@common/cams/cases'; import Icon from '@/lib/components/uswds/Icon'; - -export function copyCaseNumber(caseId: string | undefined): void { - const CASE_ID_PATTERN = /^\d{3}-\d{2}-\d{5}$/; - if (caseId && CASE_ID_PATTERN.test(caseId)) { - navigator.clipboard.writeText(caseId); - } -} +import { copyCaseNumber, getCaseNumber } from '@/lib/utils/caseNumber'; export interface CaseDetailHeaderProps { isLoading: boolean; diff --git a/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx index 798ca0c6d..1f2a8e86c 100644 --- a/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.test.tsx @@ -2,7 +2,7 @@ import { BrowserRouter } from 'react-router-dom'; import CaseDetailOverview, { CaseDetailOverviewProps } from './CaseDetailOverview'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { formatDate } from '@/lib/utils/datetime'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { Consolidation, Transfer } from '@common/cams/events'; import { CaseDetail } from '@common/cams/cases'; import { MockData } from '@common/cams/test-utilities/mock-data'; diff --git a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx index 19c7b119b..ca9cd630b 100644 --- a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx @@ -1,4 +1,4 @@ -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { formatDate, sortDatesReverse } from '@/lib/utils/datetime'; import { CaseNumber } from '@/lib/components/CaseNumber'; import { isJointAdministrationChildCase, Transfer } from '@common/cams/events'; diff --git a/user-interface/src/data-verification/TransferOrderAccordion.test.tsx b/user-interface/src/data-verification/TransferOrderAccordion.test.tsx index 6ee61b389..cde1b130f 100644 --- a/user-interface/src/data-verification/TransferOrderAccordion.test.tsx +++ b/user-interface/src/data-verification/TransferOrderAccordion.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import { formatDate } from '@/lib/utils/datetime'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { TransferOrderAccordion, TransferOrderAccordionProps } from './TransferOrderAccordion'; import { describe } from 'vitest'; import { orderType, orderStatusType } from '@/lib/utils/labels'; diff --git a/user-interface/src/data-verification/TransferOrderAccordion.tsx b/user-interface/src/data-verification/TransferOrderAccordion.tsx index 851994561..a4125986d 100644 --- a/user-interface/src/data-verification/TransferOrderAccordion.tsx +++ b/user-interface/src/data-verification/TransferOrderAccordion.tsx @@ -3,7 +3,7 @@ import { Accordion } from '@/lib/components/uswds/Accordion'; import { OfficeDetails } from '@common/cams/courts'; import { TransferOrder } from '@common/cams/orders'; import { formatDate } from '@/lib/utils/datetime'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { AlertDetails } from '@/lib/components/uswds/Alert'; import { PendingTransferOrder, diff --git a/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordion.test.tsx b/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordion.test.tsx index 461bc561b..5f4d540fe 100644 --- a/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordion.test.tsx +++ b/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordion.test.tsx @@ -10,7 +10,7 @@ import { MockData } from '@common/cams/test-utilities/mock-data'; import { OfficeDetails } from '@common/cams/courts'; import { formatDate } from '@/lib/utils/datetime'; import * as FeatureFlagHook from '@/lib/hooks/UseFeatureFlags'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { UswdsAlertStyle } from '@/lib/components/uswds/Alert'; import { FeatureFlagSet } from '@common/feature-flags'; import { selectItemInMockSelect } from '@/lib/components/CamsSelect.mock'; diff --git a/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordionView.tsx b/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordionView.tsx index 83776f72c..2754116ac 100644 --- a/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordionView.tsx +++ b/user-interface/src/data-verification/consolidation/ConsolidationOrderAccordionView.tsx @@ -13,7 +13,7 @@ import Button, { UswdsButtonStyle } from '@/lib/components/uswds/Button'; import { ConsolidationOrderModal } from '@/data-verification/consolidation/ConsolidationOrderModal'; import { CaseNumber } from '@/lib/components/CaseNumber'; import { ConsolidationViewModel } from '@/data-verification/consolidation/consolidationViewModel'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; export type ConsolidationOrderAccordionViewProps = { viewModel: ConsolidationViewModel; diff --git a/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.test.tsx b/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.test.tsx index 5d236fa72..23fff0db2 100644 --- a/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.test.tsx +++ b/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.test.tsx @@ -9,7 +9,7 @@ import { import { BrowserRouter } from 'react-router-dom'; import { MockData } from '@common/cams/test-utilities/mock-data'; import { CaseAssignment } from '@common/cams/assignments'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { CaseSummary } from '@common/cams/cases'; import { Consolidation } from '@common/cams/events'; import Api2 from '@/lib/models/api2'; diff --git a/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.tsx b/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.tsx index 20f6518e5..b4bc3da30 100644 --- a/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.tsx +++ b/user-interface/src/data-verification/consolidation/ConsolidationOrderModal.tsx @@ -2,7 +2,7 @@ import Modal from '@/lib/components/uswds/modal/Modal'; import { ModalRefType } from '@/lib/components/uswds/modal/modal-refs'; import { SubmitCancelBtnProps } from '@/lib/components/uswds/modal/SubmitCancelButtonGroup'; import useWindowSize from '@/lib/hooks/UseWindowSize'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { consolidationTypeMap } from '@/lib/utils/labels'; import { CaseAssignment } from '@common/cams/assignments'; import { ConsolidationOrderCase, ConsolidationType, OrderStatus } from '@common/cams/orders'; diff --git a/user-interface/src/data-verification/consolidation/consolidationsUseCase.test.ts b/user-interface/src/data-verification/consolidation/consolidationsUseCase.test.ts index 4ac034bbd..7b8079bb8 100644 --- a/user-interface/src/data-verification/consolidation/consolidationsUseCase.test.ts +++ b/user-interface/src/data-verification/consolidation/consolidationsUseCase.test.ts @@ -6,7 +6,7 @@ import { useConsolidationControlsMock } from '@/data-verification/consolidation/ import { ConsolidationControls } from './consolidationControls'; import { ConsolidationStore } from '@/data-verification/consolidation/consolidationStore'; import { ConsolidationOrderCase } from '@common/cams/orders'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import Api2 from '@/lib/models/api2'; import { ResponseBody } from '@common/api/response'; import { Consolidation } from '@common/cams/events'; diff --git a/user-interface/src/data-verification/consolidation/consolidationsUseCase.ts b/user-interface/src/data-verification/consolidation/consolidationsUseCase.ts index b10ca7eab..79a644d92 100644 --- a/user-interface/src/data-verification/consolidation/consolidationsUseCase.ts +++ b/user-interface/src/data-verification/consolidation/consolidationsUseCase.ts @@ -10,7 +10,7 @@ import { ConsolidationControls } from '@/data-verification/consolidation/consoli import { getCurrentLeadCaseId } from './consolidationOrderAccordionUtils'; import { useApi2 } from '@/lib/hooks/UseApi2'; import { CaseSummary } from '@common/cams/cases'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { CamsSelectOptionList, SearchableSelectOption } from '@/lib/components/CamsSelect'; import { ConfirmActionResults } from './ConsolidationOrderModal'; import { AlertDetails, UswdsAlertStyle } from '@/lib/components/uswds/Alert'; diff --git a/user-interface/src/data-verification/transfer/PendingTransferOrder.test.tsx b/user-interface/src/data-verification/transfer/PendingTransferOrder.test.tsx index 258237d76..c63c05983 100644 --- a/user-interface/src/data-verification/transfer/PendingTransferOrder.test.tsx +++ b/user-interface/src/data-verification/transfer/PendingTransferOrder.test.tsx @@ -9,7 +9,7 @@ import { import { BrowserRouter } from 'react-router-dom'; import { MockData } from '@common/cams/test-utilities/mock-data'; import { UswdsAlertStyle } from '@/lib/components/uswds/Alert'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import Api2 from '@/lib/models/api2'; const fromCaseSummary = MockData.getCaseSummary(); diff --git a/user-interface/src/data-verification/transfer/PendingTransferOrder.tsx b/user-interface/src/data-verification/transfer/PendingTransferOrder.tsx index e4057b6e1..aa7bfffad 100644 --- a/user-interface/src/data-verification/transfer/PendingTransferOrder.tsx +++ b/user-interface/src/data-verification/transfer/PendingTransferOrder.tsx @@ -12,7 +12,7 @@ import { TransferConfirmationModalImperative, } from './TransferConfirmationModal'; import Button, { ButtonRef, UswdsButtonStyle } from '@/lib/components/uswds/Button'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { OfficeDetails } from '@common/cams/courts'; import { SuggestedTransferCases, SuggestedTransferCasesImperative } from './SuggestedTransferCases'; import { FromCaseSummary } from './FromCaseSummary'; diff --git a/user-interface/src/data-verification/transfer/SuggestedTransferCases.test.tsx b/user-interface/src/data-verification/transfer/SuggestedTransferCases.test.tsx index f18de9305..8a8c0a3a7 100644 --- a/user-interface/src/data-verification/transfer/SuggestedTransferCases.test.tsx +++ b/user-interface/src/data-verification/transfer/SuggestedTransferCases.test.tsx @@ -12,7 +12,7 @@ import { render, waitFor, screen, fireEvent } from '@testing-library/react'; import { MockData } from '@common/cams/test-utilities/mock-data'; import { CaseDocketEntry, CaseSummary } from '@common/cams/cases'; import { UswdsAlertStyle } from '@/lib/components/uswds/Alert'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import Api2 from '@/lib/models/api2'; const testOffices: OfficeDetails[] = [ diff --git a/user-interface/src/data-verification/transfer/TransferConfirmationModal.test.tsx b/user-interface/src/data-verification/transfer/TransferConfirmationModal.test.tsx index 60a756efe..d8952c205 100644 --- a/user-interface/src/data-verification/transfer/TransferConfirmationModal.test.tsx +++ b/user-interface/src/data-verification/transfer/TransferConfirmationModal.test.tsx @@ -5,7 +5,7 @@ import { } from './TransferConfirmationModal'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MockData } from '@common/cams/test-utilities/mock-data'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import React from 'react'; import { TransferConfirmationModalImperative } from './TransferConfirmationModal'; diff --git a/user-interface/src/data-verification/transfer/TransferConfirmationModal.tsx b/user-interface/src/data-verification/transfer/TransferConfirmationModal.tsx index c2f94732a..5d3d441ab 100644 --- a/user-interface/src/data-verification/transfer/TransferConfirmationModal.tsx +++ b/user-interface/src/data-verification/transfer/TransferConfirmationModal.tsx @@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useRef, useState } from 'react'; import Modal from '@/lib/components/uswds/modal/Modal'; import { OrderStatus } from '@common/cams/orders'; import { ModalRefType } from '@/lib/components/uswds/modal/modal-refs'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; export interface TransferConfirmationModalProps { id: string; diff --git a/user-interface/src/lib/components/CaseNumber.tsx b/user-interface/src/lib/components/CaseNumber.tsx index 6c96bdd68..6e65cbd5b 100644 --- a/user-interface/src/lib/components/CaseNumber.tsx +++ b/user-interface/src/lib/components/CaseNumber.tsx @@ -1,5 +1,5 @@ import { Link } from 'react-router-dom'; -import { getCaseNumber } from '../utils/formatCaseNumber'; +import { getCaseNumber } from '../utils/caseNumber'; export type CaseNumberProps = JSX.IntrinsicElements['span'] & { caseId: string; diff --git a/user-interface/src/lib/utils/caseNumber.test.ts b/user-interface/src/lib/utils/caseNumber.test.ts new file mode 100644 index 000000000..96f42e305 --- /dev/null +++ b/user-interface/src/lib/utils/caseNumber.test.ts @@ -0,0 +1,55 @@ +import { MockInstance } from 'vitest'; +import { copyCaseNumber, getCaseNumber } from './caseNumber'; +import MockData from '@common/cams/test-utilities/mock-data'; + +describe('Formatting case id', () => { + it('Should get case number from case id', async () => { + const caseId = '081-11-22222'; + const caseNumber = '11-22222'; + + const actual = getCaseNumber(caseId); + expect(actual).toEqual(caseNumber); + }); + + it('Should get case number from case id when the division is not present', async () => { + const caseId = '11-22222'; + const caseNumber = '11-22222'; + + const actual = getCaseNumber(caseId); + expect(actual).toEqual(caseNumber); + }); + + it('Should handle undefined input', async () => { + const actual = getCaseNumber(undefined); + expect(actual).toEqual(''); + }); +}); +describe('Testing the clipboard with caseId', () => { + let writeTextMock: MockInstance<(data: string) => Promise> = vi.fn().mockResolvedValue(''); + const testCaseDetail = MockData.getCaseDetail(); + beforeEach(() => { + if (!navigator.clipboard) { + Object.assign(navigator, { + clipboard: { + writeText: writeTextMock, + }, + }); + } else { + writeTextMock = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(); + } + }); + + test('clicking copy button should write caseId to clipboard', async () => { + copyCaseNumber(testCaseDetail.caseId); + expect(writeTextMock).toHaveBeenCalledWith(testCaseDetail.caseId); + }); + + test('should only copy to clipboard if we have a valid case number', () => { + copyCaseNumber('abcdefg#!@#$%'); + expect(writeTextMock).not.toHaveBeenCalled(); + + copyCaseNumber(testCaseDetail.caseId); + expect(writeTextMock).toHaveBeenCalledWith(testCaseDetail.caseId); + expect(writeTextMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/user-interface/src/lib/utils/formatCaseNumber.ts b/user-interface/src/lib/utils/caseNumber.ts similarity index 52% rename from user-interface/src/lib/utils/formatCaseNumber.ts rename to user-interface/src/lib/utils/caseNumber.ts index 46927c909..8847a5328 100644 --- a/user-interface/src/lib/utils/formatCaseNumber.ts +++ b/user-interface/src/lib/utils/caseNumber.ts @@ -6,3 +6,10 @@ export function getCaseNumber(caseId: string | undefined): string { } return ''; } + +export function copyCaseNumber(caseId: string | undefined): void { + const CASE_ID_PATTERN = /^\d{3}-\d{2}-\d{5}$/; + if (caseId && CASE_ID_PATTERN.test(caseId)) { + navigator.clipboard.writeText(caseId); + } +} diff --git a/user-interface/src/lib/utils/formatCaseNumber.test.ts b/user-interface/src/lib/utils/formatCaseNumber.test.ts deleted file mode 100644 index 4da928871..000000000 --- a/user-interface/src/lib/utils/formatCaseNumber.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getCaseNumber } from './formatCaseNumber'; - -describe('Formatting case id', () => { - it('Should get case number from case id', async () => { - const caseId = '081-11-22222'; - const caseNumber = '11-22222'; - - const actual = getCaseNumber(caseId); - expect(actual).toEqual(caseNumber); - }); - - it('Should get case number from case id when the division is not present', async () => { - const caseId = '11-22222'; - const caseNumber = '11-22222'; - - const actual = getCaseNumber(caseId); - expect(actual).toEqual(caseNumber); - }); - - it('Should handle undefined input', async () => { - const actual = getCaseNumber(undefined); - expect(actual).toEqual(''); - }); -}); diff --git a/user-interface/src/staff-assignment/modal/AssignAttorneyModal.tsx b/user-interface/src/staff-assignment/modal/AssignAttorneyModal.tsx index 1cadb64df..8d9ba84ba 100644 --- a/user-interface/src/staff-assignment/modal/AssignAttorneyModal.tsx +++ b/user-interface/src/staff-assignment/modal/AssignAttorneyModal.tsx @@ -1,6 +1,6 @@ import './AssignAttorneyModal.scss'; import { forwardRef, RefObject, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { LoadingSpinner } from '@/lib/components/LoadingSpinner'; import Alert, { AlertDetails } from '@/lib/components/uswds/Alert'; import { CaseBasics } from '@common/cams/cases'; diff --git a/user-interface/src/staff-assignment/row/StaffAssignmentRow.internal.ts b/user-interface/src/staff-assignment/row/StaffAssignmentRow.internal.ts index 18d73b568..7aca47162 100644 --- a/user-interface/src/staff-assignment/row/StaffAssignmentRow.internal.ts +++ b/user-interface/src/staff-assignment/row/StaffAssignmentRow.internal.ts @@ -2,7 +2,7 @@ import { useApi2 } from '@/lib/hooks/UseApi2'; import Actions from '@common/cams/actions'; import { CallbackProps } from '../modal/AssignAttorneyModal'; import { useGlobalAlert } from '@/lib/hooks/UseGlobalAlert'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { AttorneyUser } from '@common/cams/users'; import { CaseBasics } from '@common/cams/cases'; import { useState } from '@/lib/hooks/UseState'; diff --git a/user-interface/src/staff-assignment/row/StaffAssignmentRow.test.tsx b/user-interface/src/staff-assignment/row/StaffAssignmentRow.test.tsx index 4bf651699..cfeb4f84e 100644 --- a/user-interface/src/staff-assignment/row/StaffAssignmentRow.test.tsx +++ b/user-interface/src/staff-assignment/row/StaffAssignmentRow.test.tsx @@ -6,7 +6,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import Api2 from '@/lib/models/api2'; import { TRIAL_ATTORNEYS } from '@common/cams/test-utilities/attorneys.mock'; import { BrowserRouter } from 'react-router-dom'; -import { getCaseNumber } from '@/lib/utils/formatCaseNumber'; +import { getCaseNumber } from '@/lib/utils/caseNumber'; import { formatDate } from '@/lib/utils/datetime'; import { UswdsButtonStyle } from '@/lib/components/uswds/Button'; import { CaseBasics } from '@common/cams/cases'; From ce2c5739561f644500ed42c3cc498245f3b56aa9 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Fri, 20 Sep 2024 13:06:58 -0600 Subject: [PATCH 18/20] Added period to caseDetail alert message Jira ticket: CAMS-428 --- .../src/case-detail/panels/CaseDetailAuditHistory.test.tsx | 2 +- .../src/case-detail/panels/CaseDetailAuditHistory.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx index 955d9e902..8f7d47a68 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.test.tsx @@ -133,7 +133,7 @@ describe('audit history tests', () => { render(); const emptyAssignments = await screen.findByTestId('empty-assignments-test-id'); - expect(emptyAssignments).toHaveTextContent('No changes have been made to this case'); + expect(emptyAssignments).toHaveTextContent('No changes have been made to this case.'); const historyTable = screen.queryByTestId('history-table'); expect(historyTable).not.toBeInTheDocument(); diff --git a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx index c0dcf1f23..8e4d21f4b 100644 --- a/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailAuditHistory.tsx @@ -117,7 +117,7 @@ export default function CaseDetailAuditHistory(props: CaseDetailAuditHistoryProp {caseHistory.length < 1 && (
        Date: Fri, 20 Sep 2024 13:38:06 -0600 Subject: [PATCH 19/20] Added IconButton and CopyButton components Jira ticket: CAMS-428 --- .../case-detail/panels/CaseDetailHeader.tsx | 9 +++------ .../src/lib/components/IconButton.tsx | 20 +++++++++++++++++++ .../src/lib/components/cams/CopyButton.tsx | 13 ++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 user-interface/src/lib/components/IconButton.tsx create mode 100644 user-interface/src/lib/components/cams/CopyButton.tsx diff --git a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx index c9e4e3c68..8d7b66834 100644 --- a/user-interface/src/case-detail/panels/CaseDetailHeader.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailHeader.tsx @@ -2,8 +2,8 @@ import './CaseDetailHeader.scss'; import { useEffect } from 'react'; import useFixedPosition from '@/lib/hooks/UseFixedPosition'; import { CaseDetail } from '@common/cams/cases'; -import Icon from '@/lib/components/uswds/Icon'; import { copyCaseNumber, getCaseNumber } from '@/lib/utils/caseNumber'; +import CopyButton from '@/lib/components/cams/CopyButton'; export interface CaseDetailHeaderProps { isLoading: boolean; @@ -38,14 +38,11 @@ export default function CaseDetailHeader(props: CaseDetailHeaderProps) { return (

        {props.caseId}{' '} - + />

        ); } diff --git a/user-interface/src/lib/components/IconButton.tsx b/user-interface/src/lib/components/IconButton.tsx new file mode 100644 index 000000000..329cf186c --- /dev/null +++ b/user-interface/src/lib/components/IconButton.tsx @@ -0,0 +1,20 @@ +import Icon from '@/lib/components/uswds/Icon'; +import Button, { ButtonRef, UswdsButtonStyle } from './uswds/Button'; +import { forwardRef } from 'react'; + +export type IconButtonProps = JSX.IntrinsicElements['button'] & { + disabled?: boolean; + icon: string; +}; + +function _IconButton(props: IconButtonProps, ref: React.Ref) { + return ( + + ); +} + +const IconButton = forwardRef(_IconButton); + +export default IconButton; diff --git a/user-interface/src/lib/components/cams/CopyButton.tsx b/user-interface/src/lib/components/cams/CopyButton.tsx new file mode 100644 index 000000000..8b94c650f --- /dev/null +++ b/user-interface/src/lib/components/cams/CopyButton.tsx @@ -0,0 +1,13 @@ +import { forwardRef } from 'react'; +import IconButton from '../IconButton'; +import { ButtonRef } from '../uswds/Button'; + +export type CopyButtonProps = JSX.IntrinsicElements['button']; + +function _CopyButton(props: CopyButtonProps, ref: React.Ref) { + return ; +} + +const CopyButton = forwardRef(_CopyButton); + +export default CopyButton; From c9603fa56e264442e74aed2b1408d735cf3d0fc3 Mon Sep 17 00:00:00 2001 From: Fritz Madden Date: Fri, 20 Sep 2024 14:00:51 -0600 Subject: [PATCH 20/20] Removed stale comment Jira ticket: CAMS-428 --- user-interface/src/case-detail/panels/CaseDetailOverview.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx index ca9cd630b..3f2f4550b 100644 --- a/user-interface/src/case-detail/panels/CaseDetailOverview.tsx +++ b/user-interface/src/case-detail/panels/CaseDetailOverview.tsx @@ -323,7 +323,6 @@ export default function CaseDetailOverview(props: CaseDetailOverviewProps) { className="case-detail-item-value" data-testid={`case-detail-consolidation-order`} > - {/* This order date is not likely the correct one. Clarification from Phoenix has been requested */} {formatDate(caseDetail.consolidation[0].orderDate)}