Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension): clear transaction when needed on route update #570

Merged
merged 2 commits into from
Jan 17, 2025
Merged
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
33 changes: 33 additions & 0 deletions deployables/extension/src/panel/pages/ClearTransactionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,36 @@ export const ClearTransactionsModal = ({
</Modal>
)
}

type FutureClearTransactionsModalProps = {
open: boolean

onCancel: () => void
onAccept: () => void
}

export const FutureClearTransactionsModal = ({
open,
onCancel,
onAccept,
}: FutureClearTransactionsModalProps) => {
return (
<Modal
open={open}
closeLabel="Cancel"
title="Clear transactions"
description="Switching the Piloted Safe will empty your current transaction bundle."
onClose={onCancel}
>
<Modal.Actions>
<GhostButton style="contrast" onClick={onCancel}>
Cancel
</GhostButton>

<PrimaryButton style="contrast" onClick={onAccept}>
Clear transactions
</PrimaryButton>
</Modal.Actions>
</Modal>
)
}
213 changes: 132 additions & 81 deletions deployables/extension/src/panel/pages/Root.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,11 @@ import type { ExecutionRoute } from '@/types'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CompanionAppMessageType } from '@zodiac/messages'
import {
expectRouteToBe,
randomAddress,
randomPrefixedAddress,
} from '@zodiac/test-utils'
import { parsePrefixedAddress } from 'ser-kit'
import { expectRouteToBe, randomPrefixedAddress } from '@zodiac/test-utils'
import { describe, expect, it, vi } from 'vitest'
import { loader, Root } from './Root'

const mockSave = async (route: ExecutionRoute) => {
const mockIncomingRouteUpdate = async (route: ExecutionRoute) => {
await callListeners(
chromeMock.runtime.onMessage,
{
Expand All @@ -38,29 +33,28 @@ describe('Root', () => {

const route = createMockRoute()

await mockSave(route)
await mockIncomingRouteUpdate(route)

await expect(getRoute(route.id)).resolves.toEqual(route)
})

describe.skip('Clearing transactions', () => {
describe('Clearing transactions', () => {
it('warns about clearing transactions when the avatars differ', async () => {
const currentAvatar = randomPrefixedAddress()
const newAvatar = randomAddress()

const selectedRoute = createMockRoute({
const currentRoute = createMockRoute({
id: 'firstRoute',
avatar: currentAvatar,
})

await mockRoutes(selectedRoute)
await saveLastUsedRouteId(selectedRoute.id)
await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render(
'/routes/edit/firstRoute',
'/',
[
{
path: '/routes/edit/:routeId',
path: '/',
Component: Root,
loader,
},
Expand All @@ -70,40 +64,30 @@ describe('Root', () => {
},
)

await userEvent.click(
screen.getByRole('button', { name: 'Clear piloted Safe' }),
)
await userEvent.type(
screen.getByRole('textbox', { name: 'Piloted Safe' }),
newAvatar,
)

await userEvent.click(
screen.getByRole('button', { name: 'Save & Launch' }),
)
await mockIncomingRouteUpdate({
...currentRoute,
avatar: randomPrefixedAddress(),
})

expect(
screen.getByRole('dialog', { name: 'Clear transactions' }),
await screen.findByRole('dialog', { name: 'Clear transactions' }),
).toBeInTheDocument()
})

it('does not warn about clearing transactions when the avatars stay the same', async () => {
const avatar = randomPrefixedAddress()

const selectedRoute = createMockRoute({
const currentRoute = createMockRoute({
id: 'firstRoute',
label: 'First route',
avatar,
avatar: randomPrefixedAddress(),
})

await mockRoutes(selectedRoute)
await saveLastUsedRouteId(selectedRoute.id)
await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render(
'/routes/edit/firstRoute',
'/',
[
{
path: '/routes/edit/:routeId',
path: '/',
Component: Root,
loader,
},
Expand All @@ -113,78 +97,118 @@ describe('Root', () => {
},
)

await userEvent.click(
screen.getByRole('button', { name: 'Clear piloted Safe' }),
)

await userEvent.type(
screen.getByRole('textbox', { name: 'Piloted Safe' }),
parsePrefixedAddress(avatar),
)

await userEvent.click(
screen.getByRole('button', { name: 'Save & Launch' }),
)
await mockIncomingRouteUpdate({
...currentRoute,
label: 'New label',
})

expect(
screen.queryByRole('dialog', { name: 'Clear transactions' }),
).not.toBeInTheDocument()
})

it('should not warn about clearing transactions when there are none', async () => {
const selectedRoute = createMockRoute({
it('does not warn when the route differs from the currently active one', async () => {
const currentRoute = createMockRoute({
id: 'firstRoute',
label: 'First route',
avatar: randomPrefixedAddress(),
})

await mockRoutes(selectedRoute)
await saveLastUsedRouteId(selectedRoute.id)
await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render('/routes/edit/firstRoute', [
await render(
'/',
[
{
path: '/',
Component: Root,
loader,
},
],
{
path: '/routes/edit/:routeId',
Component: Root,
loader,
initialState: [createTransaction()],
},
])
)

await userEvent.click(
screen.getByRole('button', { name: 'Clear piloted Safe' }),
await mockIncomingRouteUpdate(
createMockRoute({
id: 'another-route',
avatar: randomPrefixedAddress(),
}),
)

await userEvent.type(
screen.getByRole('textbox', { name: 'Piloted Safe' }),
randomAddress(),
expect(
screen.queryByRole('dialog', { name: 'Clear transactions' }),
).not.toBeInTheDocument()
})

it('does not warn when no route is currently selected', async () => {
await saveLastUsedRouteId(null)

await render(
'/',
[
{
path: '/',
Component: Root,
loader,
},
],
{
initialState: [createTransaction()],
},
)

await userEvent.click(
screen.getByRole('button', { name: 'Save & Launch' }),
await mockIncomingRouteUpdate(
createMockRoute({
id: 'another-route',
avatar: randomPrefixedAddress(),
}),
)

expect(
screen.queryByRole('dialog', { name: 'Clear transactions' }),
).not.toBeInTheDocument()
})

it('is possible to launch a new route and clear transactions', async () => {
const selectedRoute = createMockRoute({
it('should not warn about clearing transactions when there are none', async () => {
const currentRoute = createMockRoute({
id: 'firstRoute',
label: 'First route',
avatar: randomPrefixedAddress(),
})

await mockRoutes(selectedRoute, {
id: 'another-route',
await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render('/', [
{
path: '/',
Component: Root,
loader,
},
])

await mockIncomingRouteUpdate({
...currentRoute,
avatar: randomPrefixedAddress(),
})
await saveLastUsedRouteId('another-route')

expect(
screen.queryByRole('dialog', { name: 'Clear transactions' }),
).not.toBeInTheDocument()
})

it('is saves the incoming route when the user accepts to clear transactions', async () => {
const currentRoute = createMockRoute()

await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render(
'/routes/edit/firstRoute',
'/',
[
{
path: '/routes/edit/:routeId',
path: '/',
Component: Root,
loader,
},
Expand All @@ -197,24 +221,51 @@ describe('Root', () => {
},
)

const updatedRoute = { ...currentRoute, avatar: randomPrefixedAddress() }

await mockIncomingRouteUpdate(updatedRoute)

await userEvent.click(
screen.getByRole('button', { name: 'Clear piloted Safe' }),
screen.getByRole('button', { name: 'Clear transactions' }),
)

await userEvent.type(
screen.getByRole('textbox', { name: 'Piloted Safe' }),
randomAddress(),
)
await expect(getRoute(currentRoute.id)).resolves.toEqual(updatedRoute)
})

await userEvent.click(
screen.getByRole('button', { name: 'Save & Launch' }),
it('clears transactions', async () => {
const currentRoute = createMockRoute()

await mockRoutes(currentRoute)
await saveLastUsedRouteId(currentRoute.id)

await render(
'/',
[
{
path: '/',
Component: Root,
loader,
},
],
{
initialState: [createTransaction()],
inspectRoutes: [
'/:activeRouteId/clear-transactions/:newActiveRouteId',
],
},
)

const updatedRoute = { ...currentRoute, avatar: randomPrefixedAddress() }

await mockIncomingRouteUpdate(updatedRoute)

await userEvent.click(
screen.getByRole('button', { name: 'Clear transactions' }),
)

await expectRouteToBe('/another-route/clear-transactions/firstRoute')
await expectRouteToBe(
`/${currentRoute.id}/clear-transactions/${currentRoute.id}`,
)
})
})
})
Loading
Loading