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
107 changes: 107 additions & 0 deletions __tests__/components/streams/qr-share-dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { QrShareDialog } from '@/components/streams/qr-share-dialog'
import type { StreamData } from '@/types/stream'

const mockToastSuccess = vi.fn()
const mockWriteText = vi.fn()

vi.mock('sonner', () => ({
toast: {
success: mockToastSuccess,
},
}))

vi.mock('qrcode.react', () => ({
QRCodeSVG: ({ value, ...props }: any) => (
<svg data-testid="qr-svg" data-value={value} {...props} />
),
QRCodeCanvas: ({ value, ...props }: any) => (
<canvas data-testid="qr-canvas" data-value={value} {...props} />
),
}))

const STREAM_URL = 'https://flowstar.app/stream/stream-123'

function makeStream(overrides?: Partial<StreamData>): StreamData {
return {
id: 'stream-123',
sender: 'GABC1111111111111111111111111111111111111111111111111111',
recipient: 'GDEF2222222222222222222222222222222222222222222222222222',
token: { address: 'CCUSDC', symbol: 'USDC', decimals: 7 },
depositedAmount: 100_000_000n,
withdrawnAmount: 0n,
startTime: 1_700_000_000n,
endTime: 1_700_500_000n,
cliffTime: 1_700_000_100n,
cliffAmount: 0n,
amountPerSecond: 100n,
linearAmount: 100_000_000n,
duration: 600n,
cancelled: false,
metadata: { name: 'Q2 Salary', category: 'payroll', memo: 'memo' },
...overrides,
}
}

describe('QrShareDialog', () => {
beforeEach(() => {
vi.clearAllMocks()
Object.defineProperty(window.navigator, 'clipboard', {
value: { writeText: mockWriteText },
configurable: true,
})
})

it('renders a QR code for the given stream URL', () => {
const { container } = render(
<QrShareDialog
open
onOpenChange={vi.fn()}
streamUrl={STREAM_URL}
stream={makeStream()}
status="streaming"
/>,
)

expect(screen.getByText('Share stream')).toBeInTheDocument()
expect(screen.getByText(STREAM_URL)).toBeInTheDocument()

const qrSvg = container.querySelector('[data-testid="qr-svg"]')
expect(qrSvg).toBeInTheDocument()
expect(qrSvg).toHaveAttribute('data-value', STREAM_URL)
expect(container.querySelector('[data-testid="qr-canvas"]')).toHaveAttribute(
'data-value',
STREAM_URL,
)
})

it('copies the stream URL and downloads the QR image when actions are triggered', () => {
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
const toDataURLSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toDataURL').mockImplementation(() => {
return 'data:image/png;base64,abc123'
})

render(
<QrShareDialog
open
onOpenChange={vi.fn()}
streamUrl={STREAM_URL}
stream={makeStream({ id: 'stream-456' })}
status="scheduled"
/>,
)

fireEvent.click(screen.getByRole('button', { name: /copy stream link/i }))
expect(mockWriteText).toHaveBeenCalledWith(STREAM_URL)
expect(mockToastSuccess).toHaveBeenCalledWith('Link copied to clipboard')

fireEvent.click(screen.getByRole('button', { name: /download qr/i }))
expect(toDataURLSpy).toHaveBeenCalledWith('image/png')
expect(clickSpy).toHaveBeenCalledTimes(1)
expect(mockToastSuccess).toHaveBeenCalledWith('QR code downloaded')

clickSpy.mockRestore()
toDataURLSpy.mockRestore()
})
})
73 changes: 73 additions & 0 deletions __tests__/components/streams/stream-gantt-view.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { StreamGanttView } from '@/components/streams/stream-gantt-view'
import type { StreamData } from '@/types/stream'

vi.mock('@/hooks/use-wallet', () => ({
useWallet: vi.fn(() => ({ address: 'GOUTGOING1111111111111111111111111111111111111111111' })),
}))

const TOKEN = { address: 'CCUSDC', symbol: 'USDC', decimals: 7 }

function makeStream(overrides?: Partial<StreamData>): StreamData {
return {
id: 'stream-1',
sender: 'GOUTGOING1111111111111111111111111111111111111111111111',
recipient: 'GRECEIVING2222222222222222222222222222222222222222222222',
token: TOKEN,
depositedAmount: 10_000_000n,
withdrawnAmount: 0n,
startTime: 1_000n,
endTime: 6_000n,
cliffTime: 1_500n,
cliffAmount: 0n,
amountPerSecond: 2_000n,
linearAmount: 10_000_000n,
duration: 5_000n,
cancelled: false,
metadata: { name: 'Alpha', category: 'payroll', memo: 'memo' },
...overrides,
}
}

describe('StreamGanttView', () => {
it('renders one bar per stream and positions each bar based on its start and end times', () => {
const streams = [
makeStream({ id: 'stream-1', startTime: 1_000n, endTime: 3_000n, cliffTime: 1_500n }),
makeStream({
id: 'stream-2',
sender: 'GOTHER1111111111111111111111111111111111111111111111',
recipient: 'GOUTGOING1111111111111111111111111111111111111111111111',
startTime: 2_000n,
endTime: 8_000n,
cliffTime: 3_000n,
}),
]

const nowSeconds = 4_000
const { container } = render(<StreamGanttView streams={streams} nowSeconds={nowSeconds} />)

const bars = container.querySelectorAll('.group\\/bar')
expect(bars).toHaveLength(2)

const rawMin = Math.min(...streams.map((s) => Number(s.startTime)), nowSeconds)
const rawMax = Math.max(...streams.map((s) => Number(s.endTime)), nowSeconds)
const pad = Math.max((rawMax - rawMin) * 0.05, 3600)
const rangeStart = rawMin - pad
const rangeEnd = rawMax + pad
const rangeSpan = Math.max(rangeEnd - rangeStart, 1)
const pct = (value: number) => ((value - rangeStart) / rangeSpan) * 100

const [first, second] = Array.from(bars)

expect(first).toHaveStyle({ left: `${pct(Number(streams[0].startTime))}%` })
expect(first).toHaveStyle({ width: `${pct(Number(streams[0].endTime)) - pct(Number(streams[0].startTime))}%` })

expect(second).toHaveStyle({ left: `${pct(Number(streams[1].startTime))}%` })
expect(second).toHaveStyle({ width: `${pct(Number(streams[1].endTime)) - pct(Number(streams[1].startTime))}%` })

expect(screen.getByText('Today')).toBeInTheDocument()
expect(screen.getByText('Alpha')).toBeInTheDocument()
expect(screen.getByText('Streaming')).toBeInTheDocument()
})
})