Skip to content
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
92 changes: 92 additions & 0 deletions src/components/Escrow/EscrowList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { EscrowList } from './EscrowList'
import type { Escrow } from '@/types'

const BENEFICIARY = 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN'

function makeEscrow(overrides: Partial<Escrow> = {}): Escrow {
return {
id: 'esc_1',
depositorPublicKey: 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
beneficiaryPublicKey: BENEFICIARY,
arbiterPublicKey: null,
assetCode: 'XLM',
assetIssuer: null,
amount: '100',
unlockTime: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
status: 'funded',
createdAt: new Date().toISOString(),
...overrides,
}
}

const noop = vi.fn()

describe('EscrowList per-row pending state (#52)', () => {
it('shows a row as releasing only when its own id is in releasingIds', () => {
const escrows = [
makeEscrow({ id: 'escrowA', amount: '10' }),
makeEscrow({ id: 'escrowB', amount: '20' }),
]
render(
<EscrowList
escrows={escrows}
isLoading={false}
isError={false}
onRetry={noop}
currentPublicKey={BENEFICIARY}
onRelease={noop}
onRefund={noop}
releasingIds={new Set(['escrowB'])}
/>,
)

const releaseButtons = screen.getAllByRole('button', { name: /release/i })
expect(releaseButtons).toHaveLength(2)
// escrowA (10 XLM) not releasing, escrowB (20 XLM) is.
expect(releaseButtons[0]).not.toBeDisabled()
expect(releaseButtons[1]).toBeDisabled()
})

it('shows two different rows as releasing simultaneously without clobbering each other (#52)', () => {
const escrows = [
makeEscrow({ id: 'escrowA', amount: '10' }),
makeEscrow({ id: 'escrowB', amount: '20' }),
makeEscrow({ id: 'escrowC', amount: '30' }),
]
render(
<EscrowList
escrows={escrows}
isLoading={false}
isError={false}
onRetry={noop}
currentPublicKey={BENEFICIARY}
onRelease={noop}
onRefund={noop}
releasingIds={new Set(['escrowA', 'escrowC'])}
/>,
)

const releaseButtons = screen.getAllByRole('button', { name: /release/i })
expect(releaseButtons[0]).toBeDisabled() // escrowA
expect(releaseButtons[1]).not.toBeDisabled() // escrowB
expect(releaseButtons[2]).toBeDisabled() // escrowC
})

it('treats an undefined releasingIds as nothing pending', () => {
render(
<EscrowList
escrows={[makeEscrow()]}
isLoading={false}
isError={false}
onRetry={noop}
currentPublicKey={BENEFICIARY}
onRelease={noop}
onRefund={noop}
/>,
)

expect(screen.getByRole('button', { name: /release/i })).not.toBeDisabled()
})
})
14 changes: 8 additions & 6 deletions src/components/Escrow/EscrowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ interface EscrowListProps {
currentPublicKey: string | null
onRelease: (id: string) => void
onRefund: (id: string) => void
releasingId?: string | null
refundingId?: string | null
// A Set of every currently in-flight id, not a single scalar — two
// different escrows can be releasing/refunding at once (#52).
releasingIds?: Set<string>
refundingIds?: Set<string>
}

export function EscrowList({
Expand All @@ -26,8 +28,8 @@ export function EscrowList({
currentPublicKey,
onRelease,
onRefund,
releasingId,
refundingId,
releasingIds,
refundingIds,
}: EscrowListProps) {
return (
<Card>
Expand Down Expand Up @@ -63,8 +65,8 @@ export function EscrowList({
currentPublicKey={currentPublicKey}
onRelease={onRelease}
onRefund={onRefund}
isReleasing={releasingId === e.id}
isRefunding={refundingId === e.id}
isReleasing={releasingIds?.has(e.id) ?? false}
isRefunding={refundingIds?.has(e.id) ?? false}
/>
))}
</div>
Expand Down
84 changes: 84 additions & 0 deletions src/components/Subscriptions/SubscriptionList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { SubscriptionList } from './SubscriptionList'
import type { Subscription } from '@/types'

function makeSubscription(overrides: Partial<Subscription> = {}): Subscription {
return {
id: 'sub_1',
sourcePublicKey: 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
destinationAddress: 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
asset: { code: 'XLM', issuer: null, name: 'Stellar Lumens', decimals: 7 },
amount: '10',
interval: 'monthly',
startDate: new Date().toISOString(),
nextRunAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
status: 'active',
createdAt: new Date().toISOString(),
runCount: 0,
...overrides,
}
}

const noop = vi.fn()

describe('SubscriptionList per-row pending state (#52)', () => {
it('shows a row as cancelling only when its own id is in cancellingIds', () => {
const subscriptions = [
makeSubscription({ id: 'subA', amount: '10' }),
makeSubscription({ id: 'subB', amount: '20' }),
]
render(
<SubscriptionList
subscriptions={subscriptions}
isLoading={false}
isError={false}
onRetry={noop}
onCancel={noop}
cancellingIds={new Set(['subB'])}
/>,
)

const cancelButtons = screen.getAllByRole('button', { name: /cancel/i })
expect(cancelButtons).toHaveLength(2)
expect(cancelButtons[0]).not.toBeDisabled()
expect(cancelButtons[1]).toBeDisabled()
})

it('shows two different rows as cancelling simultaneously without clobbering each other (#52)', () => {
const subscriptions = [
makeSubscription({ id: 'subA', amount: '10' }),
makeSubscription({ id: 'subB', amount: '20' }),
makeSubscription({ id: 'subC', amount: '30' }),
]
render(
<SubscriptionList
subscriptions={subscriptions}
isLoading={false}
isError={false}
onRetry={noop}
onCancel={noop}
cancellingIds={new Set(['subA', 'subC'])}
/>,
)

const cancelButtons = screen.getAllByRole('button', { name: /cancel/i })
expect(cancelButtons[0]).toBeDisabled() // subA
expect(cancelButtons[1]).not.toBeDisabled() // subB
expect(cancelButtons[2]).toBeDisabled() // subC
})

it('treats an undefined cancellingIds as nothing pending', () => {
render(
<SubscriptionList
subscriptions={[makeSubscription()]}
isLoading={false}
isError={false}
onRetry={noop}
onCancel={noop}
/>,
)

expect(screen.getByRole('button', { name: /cancel/i })).not.toBeDisabled()
})
})
8 changes: 5 additions & 3 deletions src/components/Subscriptions/SubscriptionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ interface SubscriptionListProps {
isError: boolean
onRetry: () => void
onCancel: (id: string) => void
cancellingId?: string | null
// A Set of every currently in-flight id, not a single scalar — two
// different subscriptions can be cancelling at once (#52).
cancellingIds?: Set<string>
}

export function SubscriptionList({
Expand All @@ -21,7 +23,7 @@ export function SubscriptionList({
isError,
onRetry,
onCancel,
cancellingId,
cancellingIds,
}: SubscriptionListProps) {
return (
<Card>
Expand Down Expand Up @@ -53,7 +55,7 @@ export function SubscriptionList({
key={s.id}
subscription={s}
onCancel={onCancel}
isCancelling={cancellingId === s.id}
isCancelling={cancellingIds?.has(s.id) ?? false}
/>
))}
</div>
Expand Down
Loading
Loading