-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(APP-3659): Update ProposalVoting module component to support mul…
…ti-body stages (#352)
- Loading branch information
Showing
22 changed files
with
864 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
298 changes: 244 additions & 54 deletions
298
src/modules/components/proposal/proposalVoting/proposalVoting.stories.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
src/modules/components/proposal/proposalVoting/proposalVotingBodyContent/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { ProposalVotingBodyContent, type IProposalVotingBodyContentProps } from './proposalVotingBodyContent'; |
152 changes: 152 additions & 0 deletions
152
...ents/proposal/proposalVoting/proposalVotingBodyContent/proposalVotingBodyContent.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { userEvent } from '@testing-library/user-event'; | ||
import { ProposalVotingStatus } from '../../proposalUtils'; | ||
import { ProposalVotingStageContextProvider, type IProposalVotingStageContext } from '../proposalVotingStageContext'; | ||
import { ProposalVotingBodyContent, type IProposalVotingBodyContentProps } from './proposalVotingBodyContent'; | ||
|
||
describe('<ProposalVotingBodyContent /> component', () => { | ||
const createTestComponent = ( | ||
props?: Partial<IProposalVotingBodyContentProps>, | ||
contextValues?: Partial<IProposalVotingStageContext>, | ||
) => { | ||
const completeProps: IProposalVotingBodyContentProps = { | ||
status: ProposalVotingStatus.PENDING, | ||
name: 'Test Stage', | ||
bodyId: 'body1', | ||
...props, | ||
}; | ||
const completeContextValues: IProposalVotingStageContext = { | ||
startDate: 0, | ||
endDate: 0, | ||
...contextValues, | ||
}; | ||
|
||
return ( | ||
<ProposalVotingStageContextProvider value={completeContextValues}> | ||
<ProposalVotingBodyContent {...completeProps} /> | ||
</ProposalVotingStageContextProvider> | ||
); | ||
}; | ||
|
||
it('renders null when bodyId does not match activeBody', () => { | ||
const bodyId = 'body1'; | ||
|
||
const { container } = render(createTestComponent({ bodyId })); | ||
expect(container).toBeEmptyDOMElement(); | ||
}); | ||
|
||
it('renders content when bodyId matches activeBody', () => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const contextValues = { | ||
activeBody: activeBody, | ||
}; | ||
|
||
const children = 'Test Children'; | ||
|
||
render(createTestComponent({ bodyId, children }, contextValues)); | ||
|
||
expect(screen.getByText(children)).toBeInTheDocument(); | ||
expect(screen.getByRole('tablist')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders back button and name when bodyList has more than one element', () => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const name = 'Test Stage'; | ||
const contextValues = { | ||
bodyList: ['body1', 'body2'], | ||
activeBody: activeBody, | ||
}; | ||
|
||
render(createTestComponent({ bodyId, name }, contextValues)); | ||
|
||
expect(screen.getByRole('button', { name: 'All bodies' })).toBeInTheDocument(); | ||
expect(screen.getByText(name)).toBeInTheDocument(); | ||
}); | ||
|
||
it('does not render back button and name when bodyList has one or fewer elements', () => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const contextValues = { | ||
activeBody: activeBody, | ||
}; | ||
|
||
render(createTestComponent({ bodyId }, contextValues)); | ||
|
||
expect(screen.queryByRole('button', { name: 'All bodies' })).not.toBeInTheDocument(); | ||
expect(screen.queryByText('Test Stage')).not.toBeInTheDocument(); | ||
}); | ||
|
||
test.each([ | ||
{ status: ProposalVotingStatus.PENDING, expectedTab: 'Details' }, | ||
{ status: ProposalVotingStatus.UNREACHED, expectedTab: 'Details' }, | ||
{ status: ProposalVotingStatus.ACTIVE, expectedTab: 'Breakdown' }, | ||
{ status: ProposalVotingStatus.ACCEPTED, expectedTab: 'Breakdown' }, | ||
{ status: ProposalVotingStatus.REJECTED, expectedTab: 'Breakdown' }, | ||
])('sets initial activeTab based on status', ({ status, expectedTab }) => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const contextValues = { | ||
activeBody: activeBody, | ||
}; | ||
|
||
render(createTestComponent({ bodyId, status }, contextValues)); | ||
|
||
const selectedTab = screen.getByRole('tab', { selected: true }); | ||
expect(selectedTab).toHaveTextContent(expectedTab); | ||
}); | ||
|
||
it('updates activeTab when status changes', () => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const contextValues = { | ||
activeBody: activeBody, | ||
}; | ||
|
||
const { rerender } = render( | ||
createTestComponent({ bodyId, status: ProposalVotingStatus.PENDING }, contextValues), | ||
); | ||
|
||
let selectedTab = screen.getByRole('tab', { selected: true }); | ||
expect(selectedTab).toHaveTextContent('Details'); | ||
|
||
rerender(createTestComponent({ bodyId, status: ProposalVotingStatus.ACTIVE }, contextValues)); | ||
|
||
selectedTab = screen.getByRole('tab', { selected: true }); | ||
expect(selectedTab).toHaveTextContent('Breakdown'); | ||
}); | ||
|
||
it('clicking back button calls setActiveBody with undefined', async () => { | ||
const user = userEvent.setup(); | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const setActiveBodyMock = jest.fn(); | ||
const contextValues = { | ||
bodyList: ['body1', 'body2'], | ||
activeBody: activeBody, | ||
setActiveBody: setActiveBodyMock, | ||
}; | ||
|
||
render(createTestComponent({ bodyId }, contextValues)); | ||
|
||
const backButton = screen.getByRole('button', { name: 'All bodies' }); | ||
await user.click(backButton); | ||
|
||
expect(setActiveBodyMock).toHaveBeenCalledWith(undefined); | ||
}); | ||
|
||
it('does not render back button and name when bodyList is undefined', () => { | ||
const bodyId = 'body1'; | ||
const activeBody = 'body1'; | ||
const contextValues = { | ||
bodyList: undefined, | ||
activeBody: activeBody, | ||
}; | ||
|
||
render(createTestComponent({ bodyId }, contextValues)); | ||
|
||
expect(screen.queryByRole('button', { name: 'All bodies' })).not.toBeInTheDocument(); | ||
expect(screen.queryByText('Test Stage')).not.toBeInTheDocument(); | ||
}); | ||
}); |
66 changes: 66 additions & 0 deletions
66
...omponents/proposal/proposalVoting/proposalVotingBodyContent/proposalVotingBodyContent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import classNames from 'classnames'; | ||
import { useEffect, useState, type ComponentProps } from 'react'; | ||
import { Button, IconType } from '../../../../../core'; | ||
import { useGukModulesContext } from '../../../gukModulesProvider'; | ||
import { ProposalVotingStatus } from '../../proposalUtils'; | ||
import { ProposalVotingTab } from '../proposalVotingDefinitions'; | ||
import { useProposalVotingStageContext } from '../proposalVotingStageContext'; | ||
import { ProposalVotingTabs } from '../proposalVotingTabs'; | ||
|
||
export interface IProposalVotingBodyContentProps extends ComponentProps<'div'> { | ||
/** | ||
* Status of the stage. | ||
*/ | ||
status: ProposalVotingStatus; | ||
/** | ||
* Name of the proposal stage displayed for multi-stage proposals. | ||
*/ | ||
name?: string; | ||
/** | ||
* plugin address of the body used to determine if the content should be rendered or not. | ||
*/ | ||
bodyId?: string; | ||
} | ||
|
||
export const ProposalVotingBodyContent: React.FC<IProposalVotingBodyContentProps> = (props) => { | ||
const { bodyId, children, name, status, className, ...otherProps } = props; | ||
|
||
const { copy } = useGukModulesContext(); | ||
|
||
const { bodyList, setActiveBody, activeBody } = useProposalVotingStageContext(); | ||
|
||
const futureStatuses = [ProposalVotingStatus.PENDING, ProposalVotingStatus.UNREACHED]; | ||
|
||
const stateActiveTab = futureStatuses.includes(status) ? ProposalVotingTab.DETAILS : ProposalVotingTab.BREAKDOWN; | ||
|
||
const [activeTab, setActiveTab] = useState<string | undefined>(stateActiveTab); | ||
|
||
// Update active tab when stage status changes (e.g from PENDING to UNREACHED) | ||
useEffect(() => setActiveTab(stateActiveTab), [stateActiveTab]); | ||
|
||
if (bodyId !== activeBody) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className={classNames('flex w-full flex-col gap-3', className)} {...otherProps}> | ||
{bodyList && bodyList.length > 1 && ( | ||
<> | ||
<Button | ||
className="w-fit" | ||
iconLeft={IconType.CHEVRON_LEFT} | ||
variant="tertiary" | ||
onClick={() => setActiveBody?.(undefined)} | ||
size="sm" | ||
> | ||
{copy.proposalVotingBodyContent.back} | ||
</Button> | ||
<p className="text-neutral-500">{name}</p> | ||
</> | ||
)} | ||
<ProposalVotingTabs value={activeTab} onValueChange={setActiveTab} status={status}> | ||
{children} | ||
</ProposalVotingTabs> | ||
</div> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
src/modules/components/proposal/proposalVoting/proposalVotingBodySummary/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { ProposalVotingBodySummary, type IProposalVotingBodySummaryProps } from './proposalVotingBodySummary'; |
38 changes: 38 additions & 0 deletions
38
...ents/proposal/proposalVoting/proposalVotingBodySummary/proposalVotingBodySummary.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { type IProposalVotingStageContext, ProposalVotingStageContextProvider } from '../proposalVotingStageContext'; | ||
import { type IProposalVotingBodySummaryProps, ProposalVotingBodySummary } from './proposalVotingBodySummary'; | ||
|
||
describe('<ProposalVotingBodySummary /> component', () => { | ||
const createTestComponent = ( | ||
props?: Partial<IProposalVotingBodySummaryProps>, | ||
contextValues?: Partial<IProposalVotingStageContext>, | ||
) => { | ||
const completeProps: IProposalVotingBodySummaryProps = { | ||
children: 'Test Content', | ||
...props, | ||
}; | ||
|
||
const completeContextValues: IProposalVotingStageContext = { | ||
startDate: 0, | ||
endDate: 0, | ||
...contextValues, | ||
}; | ||
|
||
return ( | ||
<ProposalVotingStageContextProvider value={completeContextValues}> | ||
<ProposalVotingBodySummary {...completeProps} /> | ||
</ProposalVotingStageContextProvider> | ||
); | ||
}; | ||
|
||
it('renders null when activeBody is set', () => { | ||
const contextValues = { activeBody: 'body1' }; | ||
const { container } = render(createTestComponent(undefined, contextValues)); | ||
expect(container).toBeEmptyDOMElement(); | ||
}); | ||
|
||
it('renders children when activeBody is undefined', () => { | ||
render(createTestComponent({ children: 'Some content' })); | ||
expect(screen.getByText('Some content')).toBeInTheDocument(); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
...omponents/proposal/proposalVoting/proposalVotingBodySummary/proposalVotingBodySummary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import classNames from 'classnames'; | ||
import type { ComponentProps, PropsWithChildren } from 'react'; | ||
import { useProposalVotingStageContext } from '../proposalVotingStageContext'; | ||
|
||
export interface IProposalVotingBodySummaryProps extends ComponentProps<'div'> {} | ||
|
||
export const ProposalVotingBodySummary: React.FC<PropsWithChildren<IProposalVotingBodySummaryProps>> = (props) => { | ||
const { children, className, ...otherProps } = props; | ||
|
||
const { activeBody } = useProposalVotingStageContext(); | ||
|
||
if (activeBody) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className={classNames('flex w-full flex-col gap-3', className)} {...otherProps}> | ||
{children} | ||
</div> | ||
); | ||
}; |
4 changes: 4 additions & 0 deletions
4
src/modules/components/proposal/proposalVoting/proposalVotingBodySummaryList/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { | ||
ProposalVotingBodySummaryList, | ||
type IProposalVotingBodySummaryListProps, | ||
} from './proposalVotingBodySummaryList'; |
21 changes: 21 additions & 0 deletions
21
...posal/proposalVoting/proposalVotingBodySummaryList/proposalVotingBodySummaryList.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { | ||
ProposalVotingBodySummaryList, | ||
type IProposalVotingBodySummaryListProps, | ||
} from './proposalVotingBodySummaryList'; | ||
|
||
describe('<ProposalVotingBodySummaryList /> component', () => { | ||
const createTestComponent = (props?: Partial<IProposalVotingBodySummaryListProps>) => { | ||
const completeProps: IProposalVotingBodySummaryListProps = { | ||
children: 'Test Body', | ||
...props, | ||
}; | ||
|
||
return <ProposalVotingBodySummaryList {...completeProps} />; | ||
}; | ||
|
||
it('renders children', () => { | ||
render(createTestComponent({ children: 'Test Content' })); | ||
expect(screen.getByText('Test Content')).toBeInTheDocument(); | ||
}); | ||
}); |
16 changes: 16 additions & 0 deletions
16
...s/proposal/proposalVoting/proposalVotingBodySummaryList/proposalVotingBodySummaryList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import classNames from 'classnames'; | ||
import type { ComponentProps, PropsWithChildren } from 'react'; | ||
|
||
export interface IProposalVotingBodySummaryListProps extends ComponentProps<'div'> {} | ||
|
||
export const ProposalVotingBodySummaryList: React.FC<PropsWithChildren<IProposalVotingBodySummaryListProps>> = ( | ||
props, | ||
) => { | ||
const { children, className, ...otherProps } = props; | ||
|
||
return ( | ||
<div className={classNames('flex w-full flex-col gap-3', className)} {...otherProps}> | ||
{children} | ||
</div> | ||
); | ||
}; |
4 changes: 4 additions & 0 deletions
4
src/modules/components/proposal/proposalVoting/proposalVotingBodySummaryListItem/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { | ||
ProposalVotingBodySummaryListItem, | ||
type IProposalVotingBodySummaryListItemProps, | ||
} from './proposalVotingBodySummaryListItem'; |
Oops, something went wrong.