Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a0d05c1
Initial AI version
milosh86 Aug 26, 2025
8e7deb4
Initial AI version
milosh86 Aug 26, 2025
45c03e2
Implement MD breakpoint
milosh86 Aug 26, 2025
bd6795d
Fix label
milosh86 Aug 26, 2025
85296c3
Wrap in DataListItem
milosh86 Aug 26, 2025
7783cb1
Add copy
milosh86 Aug 26, 2025
cf40399
Refactor status props
milosh86 Aug 26, 2025
e6aed69
Add error message
milosh86 Aug 26, 2025
68da41f
Add isSimulatable flag
milosh86 Aug 26, 2025
c63951b
Disable button if no tenderly url
milosh86 Aug 26, 2025
a3ebe2a
Cleanup
milosh86 Aug 26, 2025
33eb4c4
Cleanup stories
milosh86 Aug 26, 2025
25aa7d2
Fix tests
milosh86 Aug 26, 2025
54cc47d
Add changeset
milosh86 Aug 26, 2025
54cd5c0
Fix lint
milosh86 Aug 26, 2025
a2cf1f7
Update error story
milosh86 Aug 27, 2025
31308a5
Update story folder position
milosh86 Aug 27, 2025
769de3f
Add variant prop to Dropdown
milosh86 Aug 27, 2025
2caf8f6
Refactor props structure
milosh86 Aug 27, 2025
1cc6e66
Rename props
milosh86 Aug 27, 2025
97648f5
Remove custom date formatting
milosh86 Aug 27, 2025
a977bf4
Update dropdownItem to support submit button
milosh86 Aug 28, 2025
e9fcb6d
Align Tenderly button to the left when not simulatable
milosh86 Aug 28, 2025
86e5c42
Refactor module structure - add action module
milosh86 Aug 28, 2025
de79dae
Update changeset
milosh86 Aug 28, 2025
656198b
Include action module
milosh86 Aug 28, 2025
ed94713
p -> div to avoid hydration error
milosh86 Aug 29, 2025
304690b
Merge branch 'main' into APP-4058-tenderly
cgero-eth Aug 29, 2025
356ad24
Create IActionSimulationRun interface
milosh86 Aug 29, 2025
cc8fca0
Update tests to use copy
milosh86 Aug 29, 2025
a784306
Address PR commets
milosh86 Aug 29, 2025
0b69f28
Update exports
milosh86 Aug 29, 2025
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
5 changes: 5 additions & 0 deletions .changeset/young-flies-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aragon/gov-ui-kit': minor
---

Implement `ProposalActionSimulationStructure` component
Comment thread
milosh86 marked this conversation as resolved.
Outdated
16 changes: 16 additions & 0 deletions src/modules/assets/copy/modulesCopy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ export const modulesCopy = {
totalTokenSupplyTerm: 'Total token supply',
totalHoldersTerm: 'Total token holders',
},
proposalActionSimulationStructure: {
action: 'action',
actions: 'actions',
totalActionsTerm: 'Total actions',
lastSimulationTerm: 'Last simulation',
never: 'Never',
executableTerm: 'Executable',
simulateAgain: 'Simulate again',
simulate: 'Simulate',
simulating: 'Simulating',
viewOnTenderly: 'View on Tenderly',
likelyToSucceed: 'Likely to succeed',
likelyToFail: 'Likely to fail',
unknown: 'Unknown',
now: 'Now',
},
proposalDataListItemStatus: {
voted: 'Voted',
ago: 'ago',
Expand Down
1 change: 1 addition & 0 deletions src/modules/components/proposal/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './proposalActions';
export * from './proposalActionSimulationStructure';
export * from './proposalDataListItem';
export * from './proposalUtils';
export * from './proposalVoting';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ProposalActionSimulationStructure } from './proposalActionSimulationStructure';
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { DateTime } from 'luxon';
import { ProposalActionSimulationStructure } from './proposalActionSimulationStructure';

const meta: Meta<typeof ProposalActionSimulationStructure> = {
title: 'Modules/Components/Proposal/ProposalActionSimulation/ProposalActionSimulationStructure',
component: ProposalActionSimulationStructure,
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/design/ISSDryshtEpB7SUSdNqAcw/Governance-UI-Kit?node-id=30069-34747&t=tQiF5klPD9cjUit6-4',
},
},
args: {
className: 'flex-1',
},
};

type Story = StoryObj<typeof ProposalActionSimulationStructure>;

/**
* Completed simulation with positive outcome
*/
export const Success: Story = {
args: {
totalActions: 3,
lastSimulation: DateTime.now().minus({ seconds: 10 }),
status: 'success',
tenderlyUrl: 'https://dashboard.tenderly.co/simulation/12345',
},
};

/**
* Completed simulation with negative outcome (can't be executed)
*/
export const Failure: Story = {
args: {
totalActions: 2,
lastSimulation: DateTime.now().minus({ seconds: 10 }),
status: 'failure',
tenderlyUrl: 'https://dashboard.tenderly.co/simulation/12345',
},
};

/**
* Loading state while simulation is running
*/
export const Loading: Story = {
args: {
totalActions: 5,
status: 'unknown',
isSimulating: true,
},
};

/**
* No simulation has been run yet
*/
export const NoPreviousSimulation: Story = {
args: {
totalActions: 1,
status: 'unknown',
},
};

/**
* With error message displayed
*/
export const WithErrorMessage: Story = {
args: {
totalActions: 2,
lastSimulation: DateTime.now().minus({ weeks: 2 }),
status: 'success',
error: 'Simulation failed to run. Please try again.',
},
};

/**
* Recent simulation (shows "Now")
*/
export const RecentSimulation: Story = {
args: {
totalActions: 7,
lastSimulation: DateTime.now().minus({ minutes: 5 }),
status: 'success',
},
};

/**
* Old simulation (shows formatted date)
*/
export const OldSimulation: Story = {
args: {
totalActions: 2,
lastSimulation: DateTime.now().minus({ weeks: 2 }),
status: 'success',
},
};

/**
* Not simulatable - only shows Tenderly link
*/
export const NotSimulatable: Story = {
args: {
totalActions: 3,
lastSimulation: DateTime.now().minus({ hours: 2 }),
status: 'success',
isSimulatable: false,
tenderlyUrl: 'https://dashboard.tenderly.co/simulation/12345',
},
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { render, screen } from '@testing-library/react';
import { DateTime } from 'luxon';
import { GukCoreProvider } from '../../../../core';
import type { IProposalActionSimulationStructureProps } from './proposalActionSimulationStructure';
import { ProposalActionSimulationStructure } from './proposalActionSimulationStructure';

describe('<ProposalActionSimulationStructure /> component', () => {
const createTestComponent = (props?: Partial<IProposalActionSimulationStructureProps>) => {
const completeProps: IProposalActionSimulationStructureProps = {
totalActions: 3,
status: 'success',
...props,
};

return (
<GukCoreProvider>
<ProposalActionSimulationStructure {...completeProps} />
</GukCoreProvider>
);
};

it('renders all definition list items', () => {
render(createTestComponent());

expect(screen.getByText('Total actions')).toBeInTheDocument();
expect(screen.getByText('Last simulation')).toBeInTheDocument();
expect(screen.getByText('Executable')).toBeInTheDocument();
});

it('renders the total actions count', () => {
render(createTestComponent({ totalActions: 5 }));
expect(screen.getByText('5 actions')).toBeInTheDocument();
});

it('renders singular action text for one action', () => {
render(createTestComponent({ totalActions: 1 }));
expect(screen.getByText('1 action')).toBeInTheDocument();
});

it('renders "Never" when no last simulation is provided', () => {
render(createTestComponent());
expect(screen.getByText('Never')).toBeInTheDocument();
});

it('renders relative time for recent simulation', () => {
const recentTime = DateTime.now().minus({ days: 2 });
render(createTestComponent({ lastSimulation: recentTime }));
expect(screen.getByText('2 days ago')).toBeInTheDocument();
});

it('renders "Now" for very recent simulation', () => {
const veryRecentTime = DateTime.now().minus({ seconds: 30 });
render(createTestComponent({ lastSimulation: veryRecentTime }));
expect(screen.getByText('Now')).toBeInTheDocument();
});

it('renders the execution status label', () => {
render(
createTestComponent({
status: 'success',
}),
);
expect(screen.getByText('Likely to succeed')).toBeInTheDocument();
});

it('renders loading state when execution status is loading', () => {
render(
createTestComponent({
status: 'unknown',
isSimulating: true,
}),
);

expect(screen.getAllByText('Simulating')).toHaveLength(3);
});

it('shows simulate button by default when isSimulatable is not specified', () => {
render(createTestComponent());

expect(screen.getByText(/simulate/i)).toBeInTheDocument();
});

it('hides simulate button when isSimulatable is false', () => {
render(createTestComponent({ isSimulatable: false }));

expect(screen.queryByText(/simulate/i)).toBeNull();
});
});
Loading