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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions components/LegislatorProfile/LegislatorTabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import "@testing-library/jest-dom"
import { fireEvent, render, screen } from "@testing-library/react"

import { LegislatorTabs } from "./LegislatorTabs"

const push = jest.fn().mockResolvedValue(true)
let mockRouter = {
asPath: "/legislators/194/ABC1",
isReady: true,
push
}

jest.mock("next/router", () => ({
useRouter: () => mockRouter
}))

jest.mock("next-i18next", () => ({
useTranslation: () => ({
t: (key: string) =>
({
"tabs.priorities": "Priorities",
"tabs.bills": "Bills",
"tabs.elections": "Elections",
"tabs.finance": "Campaign Finance",
"tabs.district": "District",
"tabs.testimony": "Testimony",
"tabs.votes": "Votes"
}[key] ?? key)
})
}))

jest.mock("./TabComponents/PrioritiesTab", () => ({
PrioritiesTab: () => null
}))
jest.mock("./TabComponents/BillsTab", () => ({
BillsTab: () => null
}))
jest.mock("./TabComponents/ElectionsTab", () => ({
ElectionsTab: () => null
}))
jest.mock("./TabComponents/FinanceTab", () => ({
FinanceTab: () => null
}))
jest.mock("./TabComponents/DistrictTab", () => ({
DistrictTab: () => null
}))
jest.mock("./TabComponents/TestimonyTab", () => ({
TestimonyTab: () => null
}))
jest.mock("./TabComponents/VotesTab", () => ({
VotesTab: () => null
}))

const renderTabs = () =>
render(<LegislatorTabs legislatorId="legislator-id" name="Test Legislator" />)

describe("LegislatorTabs", () => {
beforeEach(() => {
push.mockClear()
mockRouter = {
asPath: "/legislators/194/ABC1",
isReady: true,
push
}
})

it("selects the tab from the URL hash after the router is ready", () => {
mockRouter = {
...mockRouter,
asPath: "/legislators/194/ABC1#elections",
isReady: false
}
const { rerender } = renderTabs()

expect(screen.getByRole("tab", { name: "Priorities" })).toHaveAttribute(
"aria-selected",
"true"
)

mockRouter = { ...mockRouter, isReady: true }
rerender(
<LegislatorTabs legislatorId="legislator-id" name="Test Legislator" />
)

expect(screen.getByRole("tab", { name: "Elections" })).toHaveAttribute(
"aria-selected",
"true"
)
})

it.each([
"/legislators/194/ABC1",
"/legislators/194/ABC1#not-a-legislator-tab"
])("falls back to priorities for %s", asPath => {
mockRouter = { ...mockRouter, asPath }
renderTabs()

expect(screen.getByRole("tab", { name: "Priorities" })).toHaveAttribute(
"aria-selected",
"true"
)
})

it("adds the selected tab to the URL hash", () => {
renderTabs()

fireEvent.click(screen.getByRole("tab", { name: "Campaign Finance" }))

expect(
screen.getByRole("tab", { name: "Campaign Finance" })
).toHaveAttribute("aria-selected", "true")
expect(push).toHaveBeenCalledWith(
"/legislators/194/ABC1#finance",
undefined,
{ shallow: true, scroll: false }
)
})

it("updates the selected tab when browser history changes the hash", () => {
mockRouter = {
...mockRouter,
asPath: "/legislators/194/ABC1#elections"
}
const { rerender } = renderTabs()

expect(screen.getByRole("tab", { name: "Elections" })).toHaveAttribute(
"aria-selected",
"true"
)

mockRouter = { ...mockRouter, asPath: "/legislators/194/ABC1#bills" }
rerender(
<LegislatorTabs legislatorId="legislator-id" name="Test Legislator" />
)

expect(screen.getByRole("tab", { name: "Bills" })).toHaveAttribute(
"aria-selected",
"true"
)
})
})
70 changes: 50 additions & 20 deletions components/LegislatorProfile/LegislatorTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useTranslation } from "next-i18next"
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
import { TabPane } from "react-bootstrap"
import TabContainer from "react-bootstrap/TabContainer"
import styled from "styled-components"
Expand All @@ -21,16 +23,24 @@ import {
} from "components/EditProfilePage/StyledEditProfileComponents"
import { MembersFinance } from "components/db/membersFinance"

const tabCategory = [
"priorities",
"bills",
"elections",
"finance",
"district",
"testimony",
"votes"
]
type TabCategories = (typeof tabCategory)[number]
const tabCategories = {
priorities: "priorities",
bills: "bills",
elections: "elections",
finance: "finance",
district: "district",
testimony: "testimony",
votes: "votes"
} as const
type TabCategory = (typeof tabCategories)[keyof typeof tabCategories]

const isTabCategory = (value?: string | null): value is TabCategory =>
Object.values(tabCategories).some(category => category === value)

const tabCategoryFromPath = (path: string): TabCategory => {
const hash = path.split("#", 2)[1]
return isTabCategory(hash) ? hash : tabCategories.priorities
}

const TabNavLink = styled(Nav.Link).attrs(props => ({
className: `rounded-top m-0 p-0 ${props.className}`
Expand Down Expand Up @@ -69,59 +79,79 @@ export function LegislatorTabs({
districtLoading,
legislatorId,
name,
tabCategory,
finance
}: {
district?: District | undefined
districtLoading?: boolean
legislatorId: string
name: string
tabCategory?: TabCategories
finance?: MembersFinance
}) {
const router = useRouter()
const { t } = useTranslation("legislators")
const [activeTab, setActiveTab] = useState<TabCategory>(
tabCategories.priorities
)

useEffect(() => {
if (router.isReady) setActiveTab(tabCategoryFromPath(router.asPath))
}, [router.asPath, router.isReady])

const handleTabSelect = (nextTab: string | null) => {
if (!isTabCategory(nextTab)) return

setActiveTab(nextTab)

const [path, currentHash] = router.asPath.split("#", 2)
if (currentHash === nextTab) return

void router.push(`${path}#${nextTab}`, undefined, {
shallow: true,
scroll: false
})
}

const tabs = [
{
title: t("tabs.priorities"),
eventKey: "priorities",
eventKey: tabCategories.priorities,
content: <PrioritiesTab />
},
{
title: t("tabs.bills"),
eventKey: "bills",
eventKey: tabCategories.bills,
content: <BillsTab />
},
{
title: t("tabs.elections"),
eventKey: "elections",
eventKey: tabCategories.elections,
content: <ElectionsTab />
},
{
title: t("tabs.finance"),
eventKey: "finance",
eventKey: tabCategories.finance,
content: <FinanceTab finance={finance} />
},
{
title: t("tabs.district"),
eventKey: "district",
eventKey: tabCategories.district,
content: <DistrictTab district={district} loading={districtLoading} />
},
{
title: t("tabs.testimony"),
eventKey: "testimony",
eventKey: tabCategories.testimony,
content: <TestimonyTab legislatorId={legislatorId} name={name} />
},
{
title: t("tabs.votes"),
eventKey: "votes",
eventKey: tabCategories.votes,
content: <VotesTab />
}
]

return (
<Container className={`p-0`}>
<TabContainer defaultActiveKey="priorities" activeKey={tabCategory}>
<TabContainer activeKey={activeTab} onSelect={handleTabSelect}>
<TabNavWrapper>
{tabs.map((t, i) => (
<TabNavItem key={i} tab={t} i={i} />
Expand Down