Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: tips feature #426

Merged
merged 5 commits into from
Nov 11, 2024
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
63 changes: 37 additions & 26 deletions app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
showModal = true
}
"
@withdraw="withdraw()"
:withdrawLoading="withdrawLoading"
@getBalance="getBalance()"
:balance="balance ? balance : '0'"
@withdraw="
withdraw({
abi: TIPS_ABI,
address: TIPS_ADDRESS as Address,
functionName: 'withdraw'
})
"
:withdrawLoading="withdrawLoading && isConfirmingWithdraw"
@getBalance="refetchBalance()"
:balance="balance ? formatEther(balance as bigint).toString() : '0'"
:balanceLoading="balanceLoading"
/>

Expand All @@ -40,6 +46,7 @@
<!-- Overlay -->
<div
v-if="toggleSide"
data-test="drawer"
class="fixed inset-0 bg-black bg-opacity-50 z-10 lg:hidden"
@click="toggleSide = false"
></div>
Expand Down Expand Up @@ -83,8 +90,11 @@ import NavBar from '@/components/NavBar.vue'
import ToastContainer from '@/components/ToastContainer.vue'
import ModalComponent from '@/components/ModalComponent.vue'
import EditUserForm from '@/components/forms/EditUserForm.vue'
import { useTipsBalance, useWithdrawTips } from './composables/tips'
import { useCustomFetch } from './composables/useCustomFetch'
import { useReadContract, useWaitForTransactionReceipt, useWriteContract } from '@wagmi/vue'
import TIPS_ABI from '@/artifacts/abi/tips.json'
import { TIPS_ADDRESS } from './constant'
import { formatEther, type Address } from 'viem'

const { addErrorToast, addSuccessToast } = useToastStore()
const toggleSide = ref(true)
Expand All @@ -95,20 +105,31 @@ function handleChange() {
}

const {
isSuccess: withdrawSuccess,
isLoading: withdrawLoading,
isPending: withdrawLoading,
error: withdrawError,
execute: withdraw
} = useWithdrawTips()
writeContract: withdraw,
data: withdrawHash
} = useWriteContract()

const { isPending: isConfirmingWithdraw, isSuccess: isSuccessConfirmed } =
useWaitForTransactionReceipt({
hash: withdrawHash
})

const userStore = useUserDataStore()
const { name, address } = storeToRefs(userStore)

const {
data: balance,
isLoading: balanceLoading,
error: balanceError,
execute: getBalance
} = useTipsBalance()

const userStore = useUserDataStore()
const { name, address } = storeToRefs(userStore)
refetch: refetchBalance
} = useReadContract({
abi: TIPS_ABI,
address: TIPS_ADDRESS as Address,
functionName: 'getBalance',
args: [address.value as Address]
})

const updateUserInput = ref({
name: name.value,
Expand Down Expand Up @@ -149,16 +170,6 @@ watch([() => userIsUpdating.value, () => userUpdateError.value], () => {
}
})

watch(
() => userStore.isAuth,
(isAuth) => {
if (isAuth === true) {
getBalance()
}
},
{ immediate: true }
)

watch(balanceError, () => {
if (balanceError.value) {
addErrorToast('Failed to Get balance')
Expand All @@ -168,8 +179,8 @@ watch(withdrawError, () => {
addErrorToast('Failed to withdraw tips')
})

watch(withdrawSuccess, () => {
if (withdrawSuccess.value) {
watch(isConfirmingWithdraw, (isConfirming, wasConfirming) => {
if (!isConfirming && wasConfirming && isSuccessConfirmed.value) {
addSuccessToast('Tips withdrawn successfully')
}
})
Expand Down
171 changes: 95 additions & 76 deletions app/src/__tests__/App.spec.ts
Original file line number Diff line number Diff line change
@@ -1,109 +1,128 @@
// tests/App.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import { setActivePinia, createPinia } from 'pinia'
import { ref } from 'vue'
import App from '@/App.vue'
import { useToastStore } from '@/stores/useToastStore'
import { useTipsBalance, useWithdrawTips } from '@/composables/tips'
import { createTestingPinia } from '@pinia/testing'
import { useToastStore } from '@/stores/__mocks__/useToastStore'
import TheDrawer from '@/components/TheDrawer.vue'
import { useUserDataStore } from '@/stores/__mocks__/user'
import ModalComponent from '@/components/ModalComponent.vue'

// Mock the composables
vi.mock('@/composables/tips', () => {
vi.mock('@/stores/useToastStore')
vi.mock('@/stores/user')

const mockUseReadContract = {
data: ref<string | null>(null),
isLoading: ref(false),
error: ref<unknown>(null),
refetch: vi.fn()
}

const mockUseWriteContract = {
writeContract: vi.fn(),
error: ref<unknown>(null),
isPending: ref(false),
data: ref(null)
}

const mockUseWaitForTransactionReceipt = {
isPending: ref(false),
isSuccess: ref(false)
}

vi.mock('@wagmi/vue', async (importOriginal) => {
const actual: Object = await importOriginal()
return {
useTipsBalance: vi.fn(),
useWithdrawTips: vi.fn()
}
})

vi.mock('@/stores/useToastStore', () => {
return {
useToastStore: vi.fn()
...actual,
useReadContract: vi.fn(() => mockUseReadContract),
useWriteContract: vi.fn(() => mockUseWriteContract),
useWaitForTransactionReceipt: vi.fn(() => mockUseWaitForTransactionReceipt)
}
})

describe('App.vue', () => {
describe('Toast', () => {
// addSuccessToast,
// addInfoToast,
// addWarningToast,
// addErrorToast
let addSuccessToast: ReturnType<typeof vi.fn>
let addInfoToast: ReturnType<typeof vi.fn>
let addWarningToast: ReturnType<typeof vi.fn>
let addErrorToast: ReturnType<typeof vi.fn>
let addToast: ReturnType<typeof vi.fn>
let balanceError: ReturnType<typeof ref>
let withdrawError: ReturnType<typeof ref>
let withdrawSuccess: ReturnType<typeof ref>

beforeEach(() => {
setActivePinia(createPinia())

// Mock the useToastStore
addToast = vi.fn()
addSuccessToast = vi.fn()
addInfoToast = vi.fn()
addWarningToast = vi.fn()
addErrorToast = vi.fn()
const useToastStoreMock = useToastStore as unknown as ReturnType<typeof vi.fn>
useToastStoreMock.mockReturnValue({
addToast,
addSuccessToast,
addInfoToast,
addWarningToast,
addErrorToast
it('should add toast on balanceError', async () => {
const wrapper = shallowMount(App, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})

// Define reactive variables
balanceError = ref(null)
withdrawError = ref(null)
withdrawSuccess = ref(null)
const { addErrorToast } = useToastStore()
mockUseReadContract.error.value = new Error('Balance error')
await wrapper.vm.$nextTick()

// Mock the return values of the composables
const useTipsBalanceMock = useTipsBalance as ReturnType<typeof vi.fn>
const useWithdrawTipsMock = useWithdrawTips as ReturnType<typeof vi.fn>
expect(addErrorToast).toHaveBeenCalled()
})

useTipsBalanceMock.mockReturnValue({
data: ref(null),
isLoading: ref(false),
error: balanceError,
execute: vi.fn()
it('should add toast on withdrawError', async () => {
const wrapper = shallowMount(App, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})

useWithdrawTipsMock.mockReturnValue({
isSuccess: withdrawSuccess,
isLoading: ref(false),
error: withdrawError,
execute: vi.fn()
})
const { addErrorToast } = useToastStore()
mockUseWriteContract.error.value = new Error('Balance error')
await wrapper.vm.$nextTick()

expect(addErrorToast).toHaveBeenCalledWith('Failed to withdraw tips')
})

it('should add toast on balanceError', async () => {
shallowMount(App)
it('should add toast on withdrawSuccess', async () => {
const wrapper = shallowMount(App, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})
mockUseWaitForTransactionReceipt.isPending.value = true
await wrapper.vm.$nextTick()

balanceError.value = { reason: 'New balance error' }
await new Promise((resolve) => setTimeout(resolve, 0)) // wait for the next tick
const { addSuccessToast } = useToastStore()
mockUseWriteContract.error.value = null
mockUseWaitForTransactionReceipt.isPending.value = false
mockUseWaitForTransactionReceipt.isSuccess.value = true
await wrapper.vm.$nextTick()

expect(addErrorToast).toHaveBeenCalled
expect(addSuccessToast).toHaveBeenCalledWith('Tips withdrawn successfully')
})
})

it('should add toast on withdrawError', async () => {
shallowMount(App)
describe('Actions', () => {
it('should close drawer if close icon clicked', async () => {
const wrapper = shallowMount(App, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})
const { isAuth } = useUserDataStore()
// set drawer to be open
isAuth.value = true
await wrapper.vm.$nextTick()

withdrawError.value = { reason: 'New withdraw error' }
await new Promise((resolve) => setTimeout(resolve, 0)) // wait for the next tick
expect(wrapper.findComponent(TheDrawer).exists()).toBeTruthy()

expect(addErrorToast).toHaveBeenCalled
await wrapper.find('div[data-test="drawer"]').trigger('click')
await wrapper.vm.$nextTick()

expect(wrapper.findComponent(TheDrawer).exists()).toBeFalsy()
})
})

it('should add toast on withdrawSuccess', async () => {
shallowMount(App)
describe('Render', () => {
it('renders ModalComponent if showModal is true', async () => {
const wrapper = shallowMount(App, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})

withdrawSuccess.value = true
withdrawError.value = { reason: 'Withdraw success message' }
await new Promise((resolve) => setTimeout(resolve, 0)) // wait for the next tick
await wrapper.setValue({ showModal: true })

expect(addSuccessToast).toHaveBeenCalledWith('Tips withdrawn successfully')
expect(wrapper.findComponent(ModalComponent).exists()).toBeTruthy()
})
})
})
49 changes: 0 additions & 49 deletions app/src/composables/tips.ts

This file was deleted.

29 changes: 0 additions & 29 deletions app/src/services/tipsService.ts

This file was deleted.

Loading