120 lines
5.0 KiB
TypeScript
120 lines
5.0 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|||
|
|
import { mount } from '@vue/test-utils'
|
||
|
|
import BalanceAmount from '../BalanceAmount.vue'
|
||
|
|
import HomeWalletCard from '@/views/home/HomeWalletCard.vue'
|
||
|
|
import i18n from '@/i18n'
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The distinction this whole component exists to protect: `0` is a balance,
|
||
|
|
* `null` is the absence of one. Rendering the first when you mean the second
|
||
|
|
* tells someone their money is gone, in the wallet's own typeface. Every case
|
||
|
|
* below is really one assertion — that the two never get confused.
|
||
|
|
*/
|
||
|
|
describe('BalanceAmount', () => {
|
||
|
|
it('shows the pixel readout when the balance is not known yet', () => {
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: null, label: 'on-chain balance' } })
|
||
|
|
expect(w.find('.balance-pixels').exists()).toBe(true)
|
||
|
|
expect(w.text()).not.toContain('0')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('treats undefined the same as null', () => {
|
||
|
|
// Optional props (`arkBalance?.spendable_sats`) arrive as undefined, not
|
||
|
|
// null, and must not fall through to a figure.
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: undefined } })
|
||
|
|
expect(w.find('.balance-pixels').exists()).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('shows a genuine zero as a figure, not as loading', () => {
|
||
|
|
// The inverse mistake: a node that really has no coins must be told so
|
||
|
|
// plainly, not left shimmering forever.
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: 0 } })
|
||
|
|
expect(w.find('.balance-pixels').exists()).toBe(false)
|
||
|
|
expect(w.text()).toBe('0 sats')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('formats a real balance with thousands separators', () => {
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: 9922 } })
|
||
|
|
expect(w.text()).toBe('9,922 sats')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('can drop the unit for bare figures', () => {
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: 21, suffix: '' } })
|
||
|
|
expect(w.text()).toBe('21')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('announces what is loading instead of being silently empty', () => {
|
||
|
|
// A shimmering box with no text is nothing at all to a screen reader.
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: null, label: 'Cashu balance' } })
|
||
|
|
const el = w.find('.balance-pixels')
|
||
|
|
expect(el.attributes('role')).toBe('status')
|
||
|
|
expect(el.attributes('aria-label')).toBe('Loading Cashu balance')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('inherits the rail colour rather than hard-coding one', () => {
|
||
|
|
// The pixels are painted with currentColor, which is what makes the
|
||
|
|
// on-chain row orange and the Cashu row purple with no colour table to
|
||
|
|
// keep in sync. Guard the mechanism: a literal colour here would drift.
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: null } })
|
||
|
|
expect(w.find('.balance-pixel').exists()).toBe(true)
|
||
|
|
expect(w.html()).not.toMatch(/background:\s*#|rgb\(/)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('renders enough cells to read as a matrix', () => {
|
||
|
|
const w = mount(BalanceAmount, { props: { sats: null } })
|
||
|
|
expect(w.findAll('.balance-pixel').length).toBe(28)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('HomeWalletCard balances', () => {
|
||
|
|
const base = {
|
||
|
|
animate: false,
|
||
|
|
walletConnected: true,
|
||
|
|
walletOnchain: null,
|
||
|
|
walletLightning: null,
|
||
|
|
walletEcash: null,
|
||
|
|
walletFedimint: null,
|
||
|
|
walletArk: null,
|
||
|
|
walletTransactions: [],
|
||
|
|
isDev: false,
|
||
|
|
}
|
||
|
|
|
||
|
|
const mountCard = (props: Record<string, unknown>) =>
|
||
|
|
mount(HomeWalletCard, { props: { ...base, ...props }, global: { plugins: [i18n] } })
|
||
|
|
|
||
|
|
it('shows no figures at all before anything has loaded', () => {
|
||
|
|
const w = mountCard({})
|
||
|
|
// Six rows could be showing 0 sats here; none of them may.
|
||
|
|
expect(w.findAll('.balance-pixels').length).toBeGreaterThan(0)
|
||
|
|
expect(w.text()).not.toMatch(/\b0 sats\b/)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('withholds the total until every rail it sums is known', () => {
|
||
|
|
// A total computed with nulls as 0 would read *lower* than the rails
|
||
|
|
// beneath it — worse than showing nothing, because it looks authoritative.
|
||
|
|
const w = mountCard({ walletOnchain: 5000, walletLightning: null, walletEcash: 0, walletFedimint: 0 })
|
||
|
|
expect(w.text()).not.toContain('5,000 sats\n')
|
||
|
|
expect(w.findAll('.balance-pixels').length).toBeGreaterThan(0)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('sums the total once every rail has reported', () => {
|
||
|
|
const w = mountCard({ walletOnchain: 9000, walletLightning: 900, walletEcash: 22, walletFedimint: 0 })
|
||
|
|
expect(w.text()).toContain('9,922 sats')
|
||
|
|
expect(w.findAll('.balance-pixels').length).toBe(0)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('shows an empty wallet as zero rather than as loading', () => {
|
||
|
|
const w = mountCard({ walletOnchain: 0, walletLightning: 0, walletEcash: 0, walletFedimint: 0 })
|
||
|
|
expect(w.findAll('.balance-pixels').length).toBe(0)
|
||
|
|
expect(w.text()).toContain('0 sats')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('keeps the Ark row hidden while its balance is unknown', () => {
|
||
|
|
// Ark only appears once barkd reports something; "unknown" must not be
|
||
|
|
// read as "> 0" and conjure a row on the many nodes with no Ark sidecar.
|
||
|
|
const loaded = { walletOnchain: 1, walletLightning: 0, walletEcash: 0, walletFedimint: 0 }
|
||
|
|
expect(mountCard({ ...loaded, walletArk: null }).text()).not.toContain('Ark')
|
||
|
|
expect(mountCard({ ...loaded, walletArk: 0 }).text()).not.toContain('Ark')
|
||
|
|
expect(mountCard({ ...loaded, walletArk: 7 }).text()).toContain('Ark')
|
||
|
|
})
|
||
|
|
})
|