diff --git a/packages/apps/public/locales/en/app-bounties.json b/packages/apps/public/locales/en/app-bounties.json index 04070171b2..f32febc7ed 100644 --- a/packages/apps/public/locales/en/app-bounties.json +++ b/packages/apps/public/locales/en/app-bounties.json @@ -101,6 +101,7 @@ "Title too long": "Title too long", "Unassign curator": "Unassign curator", "Unassign curator under voting": "Unassign curator under voting", + "Update overdue": "Update overdue", "Voting": "Voting", "Voting results": "Voting results", "Waiting for Bounty Funding": "Waiting for Bounty Funding", diff --git a/packages/page-bounties/src/Bounties.spec.ts b/packages/page-bounties/src/Bounties.spec.ts index c6a064793d..46135d4841 100644 --- a/packages/page-bounties/src/Bounties.spec.ts +++ b/packages/page-bounties/src/Bounties.spec.ts @@ -21,7 +21,7 @@ import { keyring } from '@polkadot/ui-keyring'; import { defaultBountyUpdatePeriod, mockBountyHooks } from '../test/hooks/defaults'; import { BountiesPage } from '../test/pages/bountiesPage'; -import { BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING } from './BountyInfos'; +import { BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING } from './BountyNextActionInfo/BountyActionMessage'; jest.mock('@polkadot/react-hooks/useTreasury', () => ({ useTreasury: () => mockHooks.treasury @@ -100,7 +100,7 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty, proposals); - await bountiesPage.expectText('Curator proposal under voting'); + await bountiesPage.expectVotingDescription('Curator proposal under voting'); }); it('when voting on bounty approval', async () => { @@ -109,7 +109,7 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty, proposals); - await bountiesPage.expectText('Bounty approval under voting'); + await bountiesPage.expectVotingDescription('Bounty approval under voting'); }); it('when simultaneous close and approve motions exist, show approved', async () => { @@ -121,7 +121,7 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty, proposals); - await bountiesPage.expectText('Bounty approval under voting'); + await bountiesPage.expectVotingDescription('Bounty approval under voting'); }); it('when voting on close bounty', async () => { @@ -130,7 +130,7 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty, proposals); - await bountiesPage.expectText('Bounty rejection under voting'); + await bountiesPage.expectVotingDescription('Bounty rejection under voting'); }); it('when voting on unassign curator', async () => { @@ -139,7 +139,7 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty, proposals); - await bountiesPage.expectText('Curator slash under voting'); + await bountiesPage.expectVotingDescription('Curator slash under voting'); }); it('when a motion exists that would fail on execution, show nothing', async () => { @@ -148,14 +148,7 @@ describe('Bounties', () => { const { findByTestId } = bountiesPage.renderOne(bounty, proposals); - await expect(findByTestId('extendedVotingStatus')).rejects.toThrow(); - }); - - it('when bounty is claimable', async () => { - const bounty = bountyWith({ status: 'PendingPayout' }); - const { findByTestId } = bountiesPage.renderOne(bounty); - - expect((await findByTestId('extendedActionStatus')).textContent).toEqual('Claimable'); + await expect(findByTestId('voting-description')).rejects.toThrow(); }); }); @@ -215,7 +208,7 @@ describe('Bounties', () => { const { findByTestId } = bountiesPage.renderOne(bounty, proposals); - expect((await findByTestId('voting-summary')).textContent).toEqual('Aye 2/4Nay 0/0Voting results'); + expect((await findByTestId('voting-summary')).textContent).toEqual('Aye 2/4Nay 0/0Voting'); }); it('is not displayed when not voting', async () => { @@ -457,16 +450,28 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty); - await bountiesPage.expectText('Warning'); await bountiesPage.expectText('Close deadline'); }); + it('warning when update time is overdue', async () => { + const bounty = aBounty({ status: bountyStatusWith( + { + curator: alice, + status: 'Active', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + updateDue: mockBountyHooks.bountyApi.bestNumber!.toNumber() + }) }); + + bountiesPage.renderOne(bounty); + + await bountiesPage.expectText('Update overdue'); + }); + it('info when waiting for bounty funding', async () => { const bounty = bountyWith({ status: 'Approved' }); bountiesPage.renderOne(bounty); - await bountiesPage.expectText('Info'); await bountiesPage.expectText('Waiting for Bounty Funding'); }); @@ -475,10 +480,17 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty); - await bountiesPage.expectText('Info'); await bountiesPage.expectText('Waiting for Curator\'s acceptance'); }); + it('info when bounty is claimable', async () => { + const bounty = bountyWith({ status: 'PendingPayout' }); + + bountiesPage.renderOne(bounty); + + await bountiesPage.expectText('Waiting for implementer to claim'); + }); + it('no warning or info when requirements are not met', async () => { const bounty = aBounty({ status: bountyStatusWith({ curator: alice, @@ -489,11 +501,11 @@ describe('Bounties', () => { bountiesPage.renderOne(bounty); await bountiesPage.rendered(); - bountiesPage.expectTextAbsent('Warning'); bountiesPage.expectTextAbsent('Close deadline'); - bountiesPage.expectTextAbsent('Info'); + bountiesPage.expectTextAbsent('Update overdue'); bountiesPage.expectTextAbsent('Waiting for Bounty Funding'); bountiesPage.expectTextAbsent("Waiting for Curator's acceptance"); + bountiesPage.expectTextAbsent('Waiting for implementer to claim'); }); }); }); diff --git a/packages/page-bounties/src/Bounty.tsx b/packages/page-bounties/src/Bounty.tsx index 11eed43361..0385a65bdc 100644 --- a/packages/page-bounties/src/Bounty.tsx +++ b/packages/page-bounties/src/Bounty.tsx @@ -13,8 +13,8 @@ import { ThemeProps } from '@polkadot/react-components/types'; import { BlockToTime, FormatBalance } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; +import BountyActionMessage from './BountyNextActionInfo/BountyActionMessage'; import { getProposalToDisplay } from './helpers/extendedStatuses'; -import VotersColumn from './Voting/VotersColumn'; import { BountyActions } from './BountyActions'; import BountyExtraActions from './BountyExtraActions'; import BountyInfos from './BountyInfos'; @@ -23,6 +23,7 @@ import Curator from './Curator'; import { useBountyStatus } from './hooks'; import { bountyBorderColor, bountyLabelColor } from './theme'; import { useTranslation } from './translate'; +import VotersColumn from './VotersColumn'; interface Props { bestNumber: BlockNumber; @@ -71,12 +72,7 @@ function Bounty ({ bestNumber, bounty, className = '', description, index, propo <> - + : ''} + - +
@@ -240,7 +240,10 @@ export default React.memo(styled(Bounty)(({ theme }: ThemeProps) => ` display: inline-flex; } - & .fast-actions { + & td.fast-actions { + padding-left: 0.2rem; + width: 1%; + .fast-actions-row { display: flex; align-items: center; @@ -296,17 +299,24 @@ export default React.memo(styled(Bounty)(({ theme }: ThemeProps) => ` padding: 0 0 1.3rem; } + & .td-info-action-row { + padding-right: 0; + } + .td-row { display: flex; justify-content: space-between; align-items: center; + + & :only-child { + margin-left: auto; + } } .bounty-action-row { display: flex; justify-content: flex-end; align-items: center; - margin-left: auto; & > * + * { margin-left: 0.6rem; @@ -314,9 +324,8 @@ export default React.memo(styled(Bounty)(({ theme }: ThemeProps) => ` } .block-to-time { - margin-top: 0.28rem; font-size: 0.7rem; - line-height: 0.85rem; + line-height: 1.5rem; color: ${bountyLabelColor[theme.theme]}; } diff --git a/packages/page-bounties/src/Voting/ExtendedStatus.tsx b/packages/page-bounties/src/BountyInfos/VotingDescriptionInfo.tsx similarity index 53% rename from packages/page-bounties/src/Voting/ExtendedStatus.tsx rename to packages/page-bounties/src/BountyInfos/VotingDescriptionInfo.tsx index e4bb1fb9c5..971078a0e8 100644 --- a/packages/page-bounties/src/Voting/ExtendedStatus.tsx +++ b/packages/page-bounties/src/BountyInfos/VotingDescriptionInfo.tsx @@ -4,24 +4,22 @@ import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types'; import type { BountyStatus } from '@polkadot/types/interfaces'; -import BN from 'bn.js'; import React, { useRef } from 'react'; +import styled from 'styled-components'; -import { BN_ZERO } from '@polkadot/util'; +import { LabelHelp } from '@polkadot/react-components'; -import Description from '../Description'; import { proposalNameToDisplay } from '../helpers/extendedStatuses'; import { useTranslation } from '../translate'; interface Props { - blocksUntilPayout?: BN ; className?: string; - proposals?: DeriveCollectiveProposal[]; + proposal: DeriveCollectiveProposal; status: BountyStatus; } -function ExtendedStatus ({ blocksUntilPayout, className = '', proposals, status }: Props): React.ReactElement { - const bestProposalName = proposals ? proposalNameToDisplay(proposals, status) : null; +function VotingDescriptionInfo ({ className, proposal, status }: Props): React.ReactElement { + const bestProposalName = proposalNameToDisplay(proposal, status); const { t } = useTranslation(); const votingDescriptions = useRef>({ approveBounty: t('Bounty approval under voting'), @@ -32,23 +30,17 @@ function ExtendedStatus ({ blocksUntilPayout, className = '', proposals, status }); return ( - <> +
{bestProposalName && votingDescriptions.current[bestProposalName] && - + } - {blocksUntilPayout?.lt(BN_ZERO) && - ('Claimable')} - /> - } - +
); } -export default React.memo(ExtendedStatus); +export default React.memo(styled(VotingDescriptionInfo)` + margin-left: 0.2rem; +`); diff --git a/packages/page-bounties/src/Voting/VotingLink.tsx b/packages/page-bounties/src/BountyInfos/VotingLink.tsx similarity index 65% rename from packages/page-bounties/src/Voting/VotingLink.tsx rename to packages/page-bounties/src/BountyInfos/VotingLink.tsx index bc60d1f7ce..399c5b58d3 100644 --- a/packages/page-bounties/src/Voting/VotingLink.tsx +++ b/packages/page-bounties/src/BountyInfos/VotingLink.tsx @@ -4,7 +4,6 @@ import React from 'react'; import styled from 'styled-components'; -import Description from '../Description'; import { useTranslation } from '../translate'; interface Props { @@ -15,13 +14,15 @@ function VotingLink ({ className }: Props): React.ReactElement { const { t } = useTranslation(); return ( -
- {t('Voting')} - ('Go to motions panel')} /> -
+ + {t('Voting')} + ); } export default React.memo(styled(VotingLink)` - margin: 0 1rem 0 0 + line-height: 0.85rem; + font-size: 0.7rem; + text-decoration: underline; `); diff --git a/packages/page-bounties/src/BountyInfos/VotingSummary.tsx b/packages/page-bounties/src/BountyInfos/VotingSummary.tsx index 1c13bfd69d..9e1a7a3bf7 100644 --- a/packages/page-bounties/src/BountyInfos/VotingSummary.tsx +++ b/packages/page-bounties/src/BountyInfos/VotingSummary.tsx @@ -2,23 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types'; +import type { BountyStatus } from '@polkadot/types/interfaces'; import React, { useMemo } from 'react'; import styled from 'styled-components'; +import VotingDescriptionInfo from '@polkadot/app-bounties/BountyInfos/VotingDescriptionInfo'; import { ThemeProps } from '@polkadot/react-components/types'; import { useMembers } from '@polkadot/react-hooks'; -import Description from '../Description'; import { bountyLabelColor } from '../theme'; import { useTranslation } from '../translate'; +import VotingLink from './VotingLink'; interface Props { className?: string; proposal: DeriveCollectiveProposal; + status: BountyStatus; } -function VotingSummary ({ className, proposal }: Props): JSX.Element { +function VotingSummary ({ className, proposal, status }: Props): JSX.Element { const { members } = useMembers(); const { t } = useTranslation(); const ayes = useMemo(() => proposal?.votes?.ayes?.length, [proposal]); @@ -33,10 +36,14 @@ function VotingSummary ({ className, proposal }: Props): JSX.Element { className={className} data-testid='voting-summary' > -
-

{t('Aye')} {ayes}/{threshold}

-

{t('Nay')} {nays}/{nayThreshold}

- ('Voting results')} /> +
{t('Aye')} {ayes}/{threshold}
+
{t('Nay')} {nays}/{nayThreshold}
+
+ +
)} @@ -45,11 +52,9 @@ function VotingSummary ({ className, proposal }: Props): JSX.Element { } export default React.memo(styled(VotingSummary)(({ theme }: ThemeProps) => ` - margin: 0 1rem 0 0; - .voting-summary-text { font-size: 0.85rem; - line-height: 0.5rem; + line-height: 1.5rem; color: ${bountyLabelColor[theme.theme]} span { @@ -57,4 +62,11 @@ export default React.memo(styled(VotingSummary)(({ theme }: ThemeProps) => ` margin-right: 0.5rem; } } + + .link-info { + display: flex; + justify-content: space-between; + align-items: center; + line-height: 1.5rem; + } `)); diff --git a/packages/page-bounties/src/BountyInfos/index.tsx b/packages/page-bounties/src/BountyInfos/index.tsx index 1472b8c135..8f89bbdda9 100644 --- a/packages/page-bounties/src/BountyInfos/index.tsx +++ b/packages/page-bounties/src/BountyInfos/index.tsx @@ -4,75 +4,42 @@ import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types'; import type { AccountId, BountyStatus } from '@polkadot/types/interfaces'; -import BN from 'bn.js'; import React, { useMemo } from 'react'; -import styled from 'styled-components'; import { AddressSmall } from '@polkadot/react-components'; -import { BN_HUNDRED } from '@polkadot/util'; import Description from '../Description'; import { getProposalToDisplay } from '../helpers/extendedStatuses'; -import { useBounties } from '../hooks'; import { useTranslation } from '../translate'; -import VotingLink from '../Voting/VotingLink'; -import BountyInfo from './BountyInfo'; import VotingSummary from './VotingSummary'; interface Props { beneficiary?: AccountId; - blocksUntilUpdate?: BN; - className?: string; proposals?: DeriveCollectiveProposal[]; status: BountyStatus; } -export const BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING = 10; -const BLOCKS_LEFT_TO_SHOW_WARNING = new BN('10000'); - -function BountyInfos ({ beneficiary, blocksUntilUpdate, className, proposals, status }: Props): JSX.Element { +function BountyInfos ({ beneficiary, proposals, status }: Props): JSX.Element { const { t } = useTranslation(); - const { bountyUpdatePeriod } = useBounties(); - - const blocksPercentageLeftToShowWarning = bountyUpdatePeriod?.muln(BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING).div(BN_HUNDRED); - const blocksToShowWarning = blocksPercentageLeftToShowWarning ?? BLOCKS_LEFT_TO_SHOW_WARNING; const proposalToDisplay = useMemo(() => proposals && getProposalToDisplay(proposals, status), [proposals, status]); return ( -
- {proposalToDisplay && } - {proposalToDisplay && } + <> + {proposalToDisplay && + + } {beneficiary && (
('Beneficiary')} />
)} - {blocksUntilUpdate?.lt(blocksToShowWarning) && ( - ('Close deadline')} - type='warning' - /> - )} - {status.isApproved && ( - ('Waiting for Bounty Funding')} - type='info' - /> - )} - {status.isCuratorProposed && ( - ("Waiting for Curator's acceptance")} - type='info' - /> - )} -
+ ); } -export default React.memo(styled(BountyInfos)` - display: flex; - justify-content: space-between; - align-items: center; -`); +export default React.memo(BountyInfos); diff --git a/packages/page-bounties/src/BountyNextActionInfo/BountyActionMessage.tsx b/packages/page-bounties/src/BountyNextActionInfo/BountyActionMessage.tsx new file mode 100644 index 0000000000..575b22a58f --- /dev/null +++ b/packages/page-bounties/src/BountyNextActionInfo/BountyActionMessage.tsx @@ -0,0 +1,71 @@ +// Copyright 2017-2021 @polkadot/app-bounties authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BlockNumber, BountyStatus } from '@polkadot/types/interfaces'; + +import BN from 'bn.js'; +import React, { useMemo } from 'react'; + +import { useBountyStatus } from '@polkadot/app-bounties/hooks'; +import { BN_HUNDRED, BN_ZERO } from '@polkadot/util'; + +import { useBounties } from '../hooks'; +import { useTranslation } from '../translate'; +import BountyInfo from './BountyInfo'; + +interface Props { + bestNumber: BlockNumber; + blocksUntilUpdate?: BN; + status: BountyStatus; +} + +export const BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING = 10; +const BLOCKS_LEFT_TO_SHOW_WARNING = new BN('10000'); + +function BountyActionMessage ({ bestNumber, blocksUntilUpdate, status }: Props): JSX.Element { + const { t } = useTranslation(); + const { unlockAt } = useBountyStatus(status); + const { bountyUpdatePeriod } = useBounties(); + + const blocksUntilPayout = useMemo(() => unlockAt?.sub(bestNumber), [bestNumber, unlockAt]); + + const blocksPercentageLeftToShowWarning = bountyUpdatePeriod?.muln(BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING).div(BN_HUNDRED); + const blocksToShowWarning = blocksPercentageLeftToShowWarning ?? BLOCKS_LEFT_TO_SHOW_WARNING; + + return ( +
+ {blocksUntilUpdate?.lte(BN_ZERO) && ( + ('Update overdue')} + type='warning' + /> + )} + {blocksUntilUpdate?.lt(blocksToShowWarning) && blocksUntilUpdate?.gt(BN_ZERO) && ( + ('Close deadline')} + type='warning' + /> + )} + {status.isApproved && ( + ('Waiting for Bounty Funding')} + type='info' + /> + )} + {status.isCuratorProposed && ( + ("Waiting for Curator's acceptance")} + type='info' + /> + )} + {blocksUntilPayout?.lt(BN_ZERO) && + ('Waiting for implementer to claim')} + type='info' + /> + } +
+ ); +} + +export default React.memo(BountyActionMessage); diff --git a/packages/page-bounties/src/BountyInfos/BountyInfo.tsx b/packages/page-bounties/src/BountyNextActionInfo/BountyInfo.tsx similarity index 77% rename from packages/page-bounties/src/BountyInfos/BountyInfo.tsx rename to packages/page-bounties/src/BountyNextActionInfo/BountyInfo.tsx index 6f5427c4c8..709271a778 100644 --- a/packages/page-bounties/src/BountyInfos/BountyInfo.tsx +++ b/packages/page-bounties/src/BountyNextActionInfo/BountyInfo.tsx @@ -8,7 +8,6 @@ import { Icon } from '@polkadot/react-components'; import { ThemeProps } from '@polkadot/react-components/types'; import { bountyColor, bountyLabelColor, bountySvgColor } from '../theme'; -import { useTranslation } from '../translate'; interface Props { className: ''; @@ -17,13 +16,13 @@ interface Props { } function BountyInfo ({ className = '', description, type = 'info' }: Props): React.ReactElement { - const { t } = useTranslation(); - return (
-
- { type === 'warning' && } {type === 'info' ? t('Info') : t('Warning')} -
+ {type === 'warning' && ( +
+ +
+ )}
{description}
@@ -32,13 +31,15 @@ function BountyInfo ({ className = '', description, type = 'info' }: Props): Rea } export default React.memo(styled(BountyInfo)(({ theme }: ThemeProps) => ` + display: flex; + align-items: center; font-weight: 500; font-size: 0.857rem; - line-height: 1.714rem; + line-height: 1.5rem; color: ${bountyColor[theme.theme]}; - .title{ - margin-bottom: 0.285rem; + .info-icon{ + margin-right: 0.2rem; svg { color: ${bountySvgColor[theme.theme]}; } @@ -49,5 +50,6 @@ export default React.memo(styled(BountyInfo)(({ theme }: ThemeProps) => ` font-size: 0.714rem; line-height: 0.864rem; color: ${bountyLabelColor[theme.theme]}; + word-wrap: break-word; } `)); diff --git a/packages/page-bounties/src/BountyStatusView.tsx b/packages/page-bounties/src/BountyStatusView.tsx index 603ad9a80d..adb68f624a 100644 --- a/packages/page-bounties/src/BountyStatusView.tsx +++ b/packages/page-bounties/src/BountyStatusView.tsx @@ -1,29 +1,21 @@ // Copyright 2017-2021 @polkadot/app-bounties authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types'; -import type { BountyStatus } from '@polkadot/types/interfaces'; - -import BN from 'bn.js'; import React, { useRef } from 'react'; import styled from 'styled-components'; import { LabelHelp } from '@polkadot/react-components'; -import ExtendedStatus from './Voting/ExtendedStatus'; import { insertSpaceBeforeCapitalLetter } from './helpers'; import { useTranslation } from './translate'; import { HelpMessages, StatusName } from './types'; interface Props { - blocksUntilPayout?: BN bountyStatus: StatusName; className?: string; - proposals?: DeriveCollectiveProposal[]; - status: BountyStatus; } -function BountyStatusView ({ blocksUntilPayout, bountyStatus, className = '', proposals, status }: Props): React.ReactElement { +function BountyStatusView ({ bountyStatus, className = '' }: Props): React.ReactElement { const { t } = useTranslation(); const statusHelpMessages = useRef({ @@ -36,30 +28,19 @@ function BountyStatusView ({ blocksUntilPayout, bountyStatus, className = '', pr }); return ( -
-
- {insertSpaceBeforeCapitalLetter(bountyStatus)} - -
- {proposals && ( -
- -
- )} +
+ {insertSpaceBeforeCapitalLetter(bountyStatus)} +
); } export default React.memo(styled(BountyStatusView)` - .bountyStatus { - display: flex; - align-items: center; - } + display: flex; + align-items: center; `); diff --git a/packages/page-bounties/src/Voting/VotersColumn.tsx b/packages/page-bounties/src/VotersColumn.tsx similarity index 93% rename from packages/page-bounties/src/Voting/VotersColumn.tsx rename to packages/page-bounties/src/VotersColumn.tsx index a81ea104c1..4501f4e53a 100644 --- a/packages/page-bounties/src/Voting/VotersColumn.tsx +++ b/packages/page-bounties/src/VotersColumn.tsx @@ -10,9 +10,9 @@ import styled from 'styled-components'; import { AddressSmall, Icon } from '@polkadot/react-components'; import { ThemeProps } from '@polkadot/react-components/types'; -import { getProposalToDisplay } from '../helpers/extendedStatuses'; -import { bountyLabelColor } from '../theme'; -import { useTranslation } from '../translate'; +import { getProposalToDisplay } from './helpers/extendedStatuses'; +import { bountyLabelColor } from './theme'; +import { useTranslation } from './translate'; interface Props { className?: string; diff --git a/packages/page-bounties/src/helpers/extendedStatuses.ts b/packages/page-bounties/src/helpers/extendedStatuses.ts index c20cd90c5d..6c8803f3d4 100644 --- a/packages/page-bounties/src/helpers/extendedStatuses.ts +++ b/packages/page-bounties/src/helpers/extendedStatuses.ts @@ -29,10 +29,8 @@ function bestValidProposalName (bountyProposals: DeriveCollectiveProposal[], sta return validMethods(status).find((method) => methods.includes(method)); } -export function proposalNameToDisplay (bountyProposals: DeriveCollectiveProposal[], status: BountyStatus): string | undefined { - const bestProposalName = bestValidProposalName(bountyProposals, status); - - if (bestProposalName !== 'unassignCurator') { return bestProposalName; } +export function proposalNameToDisplay (bountyProposal: DeriveCollectiveProposal, status: BountyStatus): string | undefined { + if (bountyProposal.proposal.method !== 'unassignCurator') { return bountyProposal.proposal.method; } return status.isCuratorProposed ? 'unassignCurator' : 'slashCurator'; } diff --git a/packages/page-bounties/test/pages/bountiesPage.tsx b/packages/page-bounties/test/pages/bountiesPage.tsx index d6c4bb7377..f3b4df5d0b 100644 --- a/packages/page-bounties/test/pages/bountiesPage.tsx +++ b/packages/page-bounties/test/pages/bountiesPage.tsx @@ -114,17 +114,20 @@ export class BountiesPage { } as QueueProps; return render( - - - - - - - - - - - + <> +
+ + + + + + + + + + + + ); } @@ -305,4 +308,13 @@ export class BountiesPage { fireEvent.change(beneficiaryAccountInput, { target: { value: beneficiary } }); fireEvent.keyDown(beneficiaryAccountInput, { code: 'Enter', key: 'Enter' }); } + + async expectVotingDescription (description: string): Promise { + this.assertRendered(); + const votingInfo = await this.findByTestId('voting-description'); + const icon = await within(votingInfo).findByTestId('question-circle'); + + fireEvent.mouseEnter(icon); + expect(await this.findByText(description)).toBeVisible(); + } } diff --git a/packages/react-components/src/Icon.tsx b/packages/react-components/src/Icon.tsx index 849cce2063..48de0da38d 100644 --- a/packages/react-components/src/Icon.tsx +++ b/packages/react-components/src/Icon.tsx @@ -24,7 +24,7 @@ library.add(fas); function Icon ({ className = '', color = 'normal', icon, isSpinning, onClick, size = '1x', tooltip }: Props): React.ReactElement { const extraProps = tooltip - ? { 'data-for': tooltip, 'data-tip': true } + ? { 'data-for': tooltip, 'data-testid': icon, 'data-tip': true } : {}; return ( diff --git a/packages/react-components/src/Tooltip.tsx b/packages/react-components/src/Tooltip.tsx index 98298c66d5..f4603d5352 100644 --- a/packages/react-components/src/Tooltip.tsx +++ b/packages/react-components/src/Tooltip.tsx @@ -6,9 +6,11 @@ import ReactDOM from 'react-dom'; import ReactTooltip from 'react-tooltip'; import styled from 'styled-components'; -const rootElement = typeof document === 'undefined' - ? null // This hack is required for server side rendering - : document.getElementById('tooltips'); +function rootElement () { + return typeof document === 'undefined' + ? null // This hack is required for server side rendering + : document.getElementById('tooltips'); +} interface Props { className?: string; @@ -33,10 +35,12 @@ function Tooltip ({ className = '', effect = 'solid', offset, place = 'top', tex ); useEffect((): () => void => { - rootElement && rootElement.appendChild(tooltipContainer); + const root = rootElement(); + + root && root.appendChild(tooltipContainer); return (): void => { - rootElement && rootElement.removeChild(tooltipContainer); + root && root.removeChild(tooltipContainer); }; }, [tooltipContainer]);