import { describe, it, expect, vi, beforeEach } from 'vitest' import { useTransactionHistory } from '../use-transaction-history' import { mockConfig } from '../../../tests/setup' import type { TransactionHistoriesResponseData } from '@kira-dancer/nadal' import type { TransactionParams, TransactionHistoriesResponseDataExtend, } from '#lib/types' import { INVOICE_STATUS, TRANSACTION_ACTION, WALLET } from '#lib/constants' import { DepositMethodEnum } from '#lib/enums' const { MAIN_CREDIT_UNIT, AGENCY_CREDIT_UNIT } = mockConfig.public const mockNadal = { transaction: { histories: vi.fn(), }, } vi.mock('#imports', () => ({ persistedState: vi.fn().mockReturnValue({ localStorage: {}, }), })) vi.mock('#lib/composables', () => ({ useNadal: () => mockNadal, })) vi.mock('nuxt/app', () => ({ useRuntimeConfig: vi.fn(() => ({ public: { SITE_DOMAIN: 'https://your-site.com/', FLAT_CURRENCY_UNIT: 'VNĐ', MAIN_CREDIT_UNIT: 'K', AGENCY_CREDIT_UNIT: 'Đ', }, })), })) describe('use-transaction-history', () => { let composable: ReturnType beforeEach(() => { composable = useTransactionHistory() }) describe('function fetchTransaction', () => { it('should fetch transaction history and update state', async () => { const { isLoading, historyList, totalPost, fetchTransaction } = composable const payload = { limit: 5 } const mockResponse = { data: [{ id: 1 }], total: 10, } mockNadal.transaction.histories = vi.fn().mockResolvedValue(mockResponse) const result = await fetchTransaction(payload) expect(isLoading.value).toBe(false) expect(totalPost.value).toBe(10) expect(historyList.value).toEqual(mockResponse.data) expect(result).toEqual(mockResponse.data) }) it('should set isLoading to true during the request', async () => { const { isLoading, fetchTransaction } = composable const payload: TransactionParams = { limit: 5 } const mockResponse = { data: [{ id: 1 }], total: 10, } mockNadal.transaction.histories.mockResolvedValue(mockResponse) const transactionPromise = fetchTransaction(payload) expect(isLoading.value).toBe(true) await transactionPromise expect(isLoading.value).toBe(false) }) it('should return an empty array if response data is not present', async () => { const { isLoading, historyList, fetchTransaction } = composable const payload: TransactionParams = { limit: 5 } const mockResponse = null mockNadal.transaction.histories.mockResolvedValue(mockResponse) const result = await fetchTransaction(payload) expect(isLoading.value).toBe(false) expect(historyList.value).toEqual([]) expect(result).toEqual([]) }) it('should handle the case where response.total is not a number', async () => { const { isLoading, historyList, fetchTransaction, totalPost } = composable const payload: TransactionParams = { limit: 5 } const mockResponse = { data: [{ id: 1 }], total: 'not-a-number', } mockNadal.transaction.histories.mockResolvedValue(mockResponse) const result = await fetchTransaction(payload) expect(isLoading.value).toBe(false) expect(totalPost.value).toBeNaN() expect(historyList.value).toEqual(mockResponse.data) expect(result).toEqual(mockResponse.data) }) }) describe('function onChangePagination', () => { it('should update currentPage and call fetchTransaction with the correct payload', async () => { const { currentPage, onChangePagination } = composable const newPage = 2 const payload: TransactionParams = { limit: 5 } const mockResponse = { data: [{ id: 2 }], total: 10, } mockNadal.transaction.histories.mockResolvedValue(mockResponse) await onChangePagination(newPage, payload) expect(currentPage.value).toBe(newPage) expect(mockNadal.transaction.histories).toHaveBeenCalledWith(payload) }) }) describe('function getAmount', () => { it('should return formatted amount with MAIN_CREDIT_UNIT for MAIN_WALLET and non-PhoneCard method', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.MAIN_WALLET, method: DepositMethodEnum.IBanking, status: INVOICE_STATUS.FINISHED, amount: 10000, card_amount: 0, creditRate: 1, } const result = composable.getAmount(transactionItem) expect(result).toBe(`10,000 ${MAIN_CREDIT_UNIT}`) }) it('should return formatted amount with MAIN_CREDIT_UNIT for MAIN_WALLET and non-PhoneCard method', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.MAIN_WALLET, method: DepositMethodEnum.IBanking, status: INVOICE_STATUS.FINISHED, amount: 10000, card_amount: 0, creditRate: 1, } const result = composable.getAmount(transactionItem) expect(result).toBe(`10,000 ${MAIN_CREDIT_UNIT}`) }) it('should return formatted card amount with MAIN_CREDIT_UNIT for PhoneCard method and PROCESSING status', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.MAIN_WALLET, method: DepositMethodEnum.PhoneCard, status: INVOICE_STATUS.PROCESSING, amount: 0, card_amount: 10000, creditRate: 1, } const result = composable.getAmount(transactionItem) expect(result).toBe(`10,000 ${MAIN_CREDIT_UNIT}`) }) it('should return formatted amount with AGENCY_CREDIT_UNIT for AGENCY_WALLET', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.AGENCY_WALLET, method: DepositMethodEnum.IBanking, status: INVOICE_STATUS.FINISHED, amount: 200, card_amount: 0, creditRate: 1, } const result = composable.getAmount(transactionItem) expect(result).toBe(`200 ${AGENCY_CREDIT_UNIT}`) }) it('should return amount divided by creditRate with MAIN_CREDIT_UNIT when creditRate is present', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.USER_WALLET, method: DepositMethodEnum.IBanking, status: INVOICE_STATUS.FINISHED, amount: 200, card_amount: 0, creditRate: 2, } const result = composable.getAmount(transactionItem) expect(result).toBe(`100 ${MAIN_CREDIT_UNIT}`) }) it('should return "-" if the amount is zero', () => { const transactionItem: TransactionHistoriesResponseDataExtend = { wallet: WALLET.USER_WALLET, method: DepositMethodEnum.IBanking, status: INVOICE_STATUS.FINISHED, amount: 0, card_amount: 0, creditRate: 1, } const result = composable.getAmount(transactionItem) expect(result).toBe('-') }) }) describe('function getNote', () => { it('should return parsed card_serial when method is PhoneCard, action is WITHDRAW, and card_serial is present', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: TRANSACTION_ACTION.WITHDRAW, card_serial: '[{"id":1,"serial":"123456"}]', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([{ id: 1, serial: '123456' }]) }) it('should return an empty array when method is PhoneCard but action is not WITHDRAW', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: TRANSACTION_ACTION.DEPOSIT, card_serial: '[{"id":1,"serial":"123456"}]', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([]) }) it('should return an empty array when method is not PhoneCard', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.IBanking, action: TRANSACTION_ACTION.WITHDRAW, card_serial: '[{"id":1,"serial":"123456"}]', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([]) }) it('should return an empty array when method is PhoneCard, action is WITHDRAW but card_serial is not present', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: TRANSACTION_ACTION.WITHDRAW, amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([]) }) it('should return an empty array when card_serial is an empty string', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: TRANSACTION_ACTION.WITHDRAW, card_serial: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([]) }) it('should return an empty array when card_serial is not a valid JSON string', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: TRANSACTION_ACTION.WITHDRAW, card_serial: 'invalid_json', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getNote(transactionItem) expect(result).toEqual([]) }) }) describe('function getToBankCode', () => { it('should return mapped and uppercased to_bank_code when method is SmartPay', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.SmartPay, to_bank_code: 'vcb', action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('VCB') }) it('should return uppercased card_provider when method is PhoneCard', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, card_provider: 'provider', action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('PROVIDER') }) it('should return uppercased to_bank_code for other methods', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.IBanking, to_bank_code: '003', action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('003') }) it('should return an empty string for unmapped to_bank_code when method is SmartPay', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.SmartPay, to_bank_code: '999', action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('') }) it('should return an empty string when card_provider is not provided for PhoneCard', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.PhoneCard, action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('') }) it('should return an empty string when to_bank_code is not provided for other methods', () => { const transactionItem: TransactionHistoriesResponseData = { method: DepositMethodEnum.IBanking, action: '', amount: 0, card_amount: 0, code: '', created_time: '', id: 0, status: '', type: '', } const result = composable.getToBankCode(transactionItem) expect(result).toEqual('') }) }) })