Skip to content

Commit

Permalink
feat: Users can search for a transaction by id (#4)
Browse files Browse the repository at this point in the history
* feat: adding search input

* fix: transaction does not exist page

* chore: fixing some lint issues

* test: add unit tests

* fix: fix a test and enter key for searching

* test: fix a test name

Co-authored-by: Neil Campbell <[email protected]>

---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
negar-abbasi and neilcampbell authored Apr 8, 2024
1 parent 1d25f11 commit ee32e8b
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 3 deletions.
3 changes: 0 additions & 3 deletions src/features/layout/components/connect-wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { Button } from '@/features/common/components/button'
import { Input } from '@/features/common/components/input'
import { cn } from '@/features/common/utils'

export function ConnectWallet() {
return (
<div className={cn('flex gap-2')}>
{/* TODO: add search icon */}
<Input className={cn('w-96')} />
<Button>Connect wallet</Button>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions src/features/layout/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cn } from '@/features/common/utils'
import { ThemeToggle } from '@/features/theme/components/theme-toggle'
import { NetworkSelect } from './network-select'
import { ConnectWallet } from './connect-wallet'
import { Search } from './search'

type Props = {
className?: string
Expand All @@ -10,6 +11,7 @@ type Props = {
export function Header({ className }: Props) {
return (
<div className={cn('bg-card flex h-20 flex-row items-end justify-end gap-8 pb-2 pr-4', className)}>
<Search />
<ConnectWallet />
<NetworkSelect />
<ThemeToggle />
Expand Down
63 changes: 63 additions & 0 deletions src/features/layout/components/search.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Search } from './search'
import { describe, it, expect, vi } from 'vitest'
import { render, waitFor } from '@/tests/testing-library'
import { executeComponentTest } from '@/tests/test-component'
import { useNavigate } from 'react-router-dom'

vi.mock('react-router-dom', () => ({
...vi.importActual('react-router-dom'),
useNavigate: vi.fn(),
}))

describe('search', () => {
it('should render search input and button', () => {
return executeComponentTest(
() => render(<Search />),
async (component) => {
await waitFor(() => {
expect(component.getByPlaceholderText('Search')).not.toBeNull()
expect(component.getByRole('button', { name: 'search' })).not.toBeNull()
})
}
)
})

it('should call navigate when search button is clicked', () => {
const mockNavigate = vi.fn()
vi.mocked(useNavigate).mockReturnValue(mockNavigate)

return executeComponentTest(
() => render(<Search />),
async (component, user) => {
await waitFor(async () => {
const input = component.getByPlaceholderText('Search')
const button = component.getByRole('button', { name: 'search' })

await user.type(input, '123456')
await user.click(button)

expect(mockNavigate).toHaveBeenCalledWith('/explore/transaction/123456')
expect(input).toHaveProperty('value', '')
})
}
)
})

it('should not call navigate when search button is clicked and search query is empty', () => {
const mockNavigate = vi.fn()
vi.mocked(useNavigate).mockReturnValue(mockNavigate)

return executeComponentTest(
() => render(<Search />),
async (component, user) => {
await waitFor(async () => {
const button = component.getByRole('button', { name: 'search' })

await user.click(button)

expect(mockNavigate).not.toHaveBeenCalled()
})
}
)
})
})
39 changes: 39 additions & 0 deletions src/features/layout/components/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Button } from '@/features/common/components/button'
import { Input } from '@/features/common/components/input'
import { cn } from '@/features/common/utils'
import { Urls } from '@/routes/urls'
import { useCallback, useState } from 'react'
import { useNavigate } from 'react-router-dom'

export function Search() {
const [searchQuery, setSearchQuery] = useState<string>('')
const navigate = useNavigate()

const handleInput = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value)
}, [])

const doSearch = useCallback(() => {
if (!searchQuery) {
return
}
navigate(Urls.Explore.Transaction.ById.build({ transactionId: searchQuery }))
setSearchQuery('')
}, [navigate, searchQuery])

const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
doSearch()
}
},
[doSearch]
)

return (
<div className={cn('flex gap-2')}>
<Input className={cn('w-96')} placeholder="Search" value={searchQuery} onChange={handleInput} onKeyDown={handleKeyDown} />
<Button onClick={doSearch}>search</Button>
</div>
)
}
19 changes: 19 additions & 0 deletions src/features/transactions/pages/transaction-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, it, expect, vi } from 'vitest'
import { render } from '@/tests/testing-library'
import { executeComponentTest } from '@/tests/test-component'
import { TransactionPage } from '@/features/transactions/pages/transaction-page'

vi.mock('react-router-dom', () => ({
...vi.importActual('react-router-dom'),
useParams: () => ({ transactionId: 'invalid-id' }),
}))
describe('transaction', () => {
it('should show "Transaction does not exist" for an invalid transaction ID', () => {
return executeComponentTest(
() => render(<TransactionPage />),
async (component) => {
expect(component.getByText('Transaction does not exist')).toBeTruthy()
}
)
})
})
5 changes: 5 additions & 0 deletions src/features/transactions/pages/transaction-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import { useRequiredParam } from '../../common/hooks/use-required-param'
import { Transaction } from '../components/transaction'
import { PaymentTransactionModel, TransactionType } from '../models/models'

export const isValidTransactionId = (transactionId: string) => transactionId.length === 52

export function TransactionPage() {
const { transactionId } = useRequiredParam(UrlParams.TransactionId)
if (!isValidTransactionId(transactionId)) {
return <div>Transaction does not exist</div>
}
const sampleTransaction = getSampleTransaction(transactionId)

return <Transaction transaction={sampleTransaction} />
Expand Down

0 comments on commit ee32e8b

Please sign in to comment.