expo-popcore-app/tests/stores/userBalanceStore.test.ts

177 lines
4.2 KiB
TypeScript

import { act, renderHook } from '@testing-library/react-native'
import { useUserBalanceStore } from '@/stores/userBalanceStore'
import { subscription } from '@/lib/auth'
// Mock subscription API
jest.mock('@/lib/auth', () => ({
subscription: {
list: jest.fn(),
},
}))
// Mock AppState
jest.mock('react-native', () => ({
AppState: {
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
},
}))
describe('userBalanceStore', () => {
beforeEach(() => {
// Reset store state before each test
const { reset } = useUserBalanceStore.getState()
reset()
jest.clearAllMocks()
})
describe('load', () => {
it('should load balance from API', async () => {
const mockData = [
{
type: 'metered',
creditBalance: {
remainingTokenBalance: 1000,
},
},
]
;(subscription.list as jest.Mock).mockResolvedValue({ data: mockData })
const { load } = useUserBalanceStore.getState()
await act(async () => {
await load(true)
})
const { balance } = useUserBalanceStore.getState()
expect(balance).toBe(1000)
})
it('should handle API error', async () => {
const mockError = { message: 'API Error', status: 500, statusText: 'Internal Server Error' }
;(subscription.list as jest.Mock).mockResolvedValue({ error: mockError })
const { load } = useUserBalanceStore.getState()
await act(async () => {
await load(true)
})
const { error } = useUserBalanceStore.getState()
expect(error).toEqual(mockError)
})
it('should debounce requests within 5 seconds', async () => {
const mockData = [
{
type: 'metered',
creditBalance: {
remainingTokenBalance: 500,
},
},
]
;(subscription.list as jest.Mock).mockResolvedValue({ data: mockData })
const { load } = useUserBalanceStore.getState()
// First call
await act(async () => {
await load(false)
})
// Second call within 5 seconds (should be skipped)
await act(async () => {
await load(false)
})
// API should only be called once
expect(subscription.list).toHaveBeenCalledTimes(1)
})
it('should force load when force=true', async () => {
const mockData = [
{
type: 'metered',
creditBalance: {
remainingTokenBalance: 500,
},
},
]
;(subscription.list as jest.Mock).mockResolvedValue({ data: mockData })
const { load } = useUserBalanceStore.getState()
// First call
await act(async () => {
await load(true)
})
// Second call with force=true (should not be skipped)
await act(async () => {
await load(true)
})
// API should be called twice
expect(subscription.list).toHaveBeenCalledTimes(2)
})
})
describe('setBalance', () => {
it('should set balance directly', () => {
const { setBalance } = useUserBalanceStore.getState()
act(() => {
setBalance(2000)
})
const { balance } = useUserBalanceStore.getState()
expect(balance).toBe(2000)
})
})
describe('deductBalance', () => {
it('should deduct balance', () => {
const { setBalance, deductBalance } = useUserBalanceStore.getState()
act(() => {
setBalance(1000)
deductBalance(300)
})
const { balance } = useUserBalanceStore.getState()
expect(balance).toBe(700)
})
it('should not go below zero', () => {
const { setBalance, deductBalance } = useUserBalanceStore.getState()
act(() => {
setBalance(100)
deductBalance(500)
})
const { balance } = useUserBalanceStore.getState()
expect(balance).toBe(0)
})
})
describe('reset', () => {
it('should reset all state', () => {
const { setBalance, reset } = useUserBalanceStore.getState()
act(() => {
setBalance(5000)
})
expect(useUserBalanceStore.getState().balance).toBe(5000)
act(() => {
reset()
})
const state = useUserBalanceStore.getState()
expect(state.balance).toBe(0)
expect(state.loading).toBe(false)
expect(state.error).toBe(null)
})
})
})