Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

feat(billing): create a basic billing module #2528

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/HospitalRun.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react'
import { useSelector } from 'react-redux'
import { Route, Switch } from 'react-router-dom'

import Billing from './billing/Billing'
import Dashboard from './dashboard/Dashboard'
import Imagings from './imagings/Imagings'
import Incidents from './incidents/Incidents'
Expand Down Expand Up @@ -55,6 +56,7 @@ const HospitalRun = () => {
<Route path="/incidents" component={Incidents} />
<Route path="/settings" component={Settings} />
<Route path="/imaging" component={Imagings} />
<Route path="/billing" component={Billing} />
</Switch>
</div>
<Toaster autoClose={5000} hideProgressBar draggable />
Expand Down
90 changes: 90 additions & 0 deletions src/__tests__/billing/Billing.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { act } from '@testing-library/react'
import { mount, ReactWrapper } from 'enzyme'
import React from 'react'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'
import createMockStore from 'redux-mock-store'

import Billing from '../../billing/Billing'
import AddPricingItem from '../../billing/new/AddPricingItem'
import ViewPricingItem from '../../billing/view/ViewPricingItem'
import ViewPricingItems from '../../billing/view/ViewPricingItems'
import { TitleProvider } from '../../page-header/title/TitleContext'
import Permissions from '../../shared/model/Permissions'
import { RootState } from '../../shared/store'

const mockStore = createMockStore<RootState, any>()
describe('Billing', () => {
const setup = (route: string, permissions: Permissions[] = []) => {
const store = mockStore({
user: {
permissions,
},
} as any)

let wrapper: any
act(() => {
wrapper = mount(
<Provider store={store}>
<MemoryRouter initialEntries={[route]}>
<TitleProvider>
<Billing />
</TitleProvider>
</MemoryRouter>
</Provider>,
)
})

return { wrapper: wrapper as ReactWrapper }
}

describe('routing', () => {
describe('/billing/new', () => {
it('should render the AddPricingItem screen when /billing/new is accessed', () => {
const { wrapper } = setup('/billing/new', [Permissions.AddPricingItems])

const addPricingItemView = wrapper.find(AddPricingItem)
expect(addPricingItemView.exists()).toBeTruthy()
})

it('should not render the AddPricingItem screen if user does not have permissions', () => {
const { wrapper } = setup('/billing/new')

const addPricingItemView = wrapper.find(AddPricingItem)
expect(addPricingItemView.exists()).toBeFalsy()
})
})

describe('/billing/:id', () => {
it('should render the ViewPricingItem screen when /billing/:id is accessed', () => {
const { wrapper } = setup('/billing/123', [Permissions.ViewPricingItems])

const viewPricingItem = wrapper.find(ViewPricingItem)
expect(viewPricingItem.exists()).toBeTruthy()
})

it('should not render the ViewPricingItem if user does not have permissions', () => {
const { wrapper } = setup('/billing/123')

const viewPricingItem = wrapper.find(ViewPricingItem)
expect(viewPricingItem.exists()).toBeFalsy()
})
})

describe('/billing', () => {
it('should render the ViewPricingItems screen when /billing/ is accessed', () => {
const { wrapper } = setup('/billing', [Permissions.ViewPricingItems])

const viewPricingItems = wrapper.find(ViewPricingItems)
expect(viewPricingItems.exists()).toBeTruthy()
})

it('should not render the ViewPricingItems when user does not have permissions', () => {
const { wrapper } = setup('/billing')

const viewPricingItems = wrapper.find(ViewPricingItems)
expect(viewPricingItems.exists()).toBeFalsy()
})
})
})
})
80 changes: 80 additions & 0 deletions src/__tests__/billing/hooks/useAddPricingItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { act } from '@testing-library/react-hooks'

import useAddPricingItem from '../../../billing/hooks/useAddPricingItem'
import { PricingItemError } from '../../../billing/utils/validate-pricingItem'
import * as validatePricingItem from '../../../billing/utils/validate-pricingItem'
import PricingItemRepository from '../../../shared/db/PricingItemRepository'
import { PricingItem } from '../../../shared/model/PricingItem'
import executeMutation from '../../test-utils/use-mutation.util'

describe('Use Add Pricing Item', () => {
const expectedPricingItem = {
name: 'pricing item',
price: 10,
category: 'ward',
} as PricingItem

jest.spyOn(PricingItemRepository, 'save').mockResolvedValue(expectedPricingItem)

afterEach(() => {
jest.clearAllMocks()
})

it('should save a new pricing item', async () => {
let actualData: any
await act(async () => {
actualData = await executeMutation(() => useAddPricingItem(), expectedPricingItem)
})

expect(PricingItemRepository.save).toHaveBeenCalledTimes(1)
expect(PricingItemRepository.save).toHaveBeenCalledWith(expectedPricingItem)
expect(actualData).toEqual(expectedPricingItem)
})

it('should fill type if its not filled when category is imaging or lab', async () => {
rsousaj marked this conversation as resolved.
Show resolved Hide resolved
const expectedPricingItem1 = {
...expectedPricingItem,
category: 'lab',
} as PricingItem

const expectedPricingItem2 = {
...expectedPricingItem,
category: 'imaging',
} as PricingItem

await act(async () => {
await executeMutation(() => useAddPricingItem(), expectedPricingItem1)
await executeMutation(() => useAddPricingItem(), expectedPricingItem2)
})

expect(PricingItemRepository.save).toHaveBeenCalledTimes(2)
expect(PricingItemRepository.save).toHaveBeenNthCalledWith(1, {
...expectedPricingItem1,
type: 'Lab Procedure',
})
expect(PricingItemRepository.save).toHaveBeenNthCalledWith(2, {
...expectedPricingItem2,
type: 'Imaging Procedure',
})
})

it('should return errors', async () => {
rsousaj marked this conversation as resolved.
Show resolved Hide resolved
expect.hasAssertions()

const expectedErrors = {
message: 'error message',
name: 'error name',
price: 'error price',
category: 'error category',
} as PricingItemError

jest.spyOn(validatePricingItem, 'validateNewPricingItem').mockReturnValue(expectedErrors)

try {
await executeMutation(() => useAddPricingItem(), expectedPricingItem)
} catch (e) {
expect(e).toEqual(expectedErrors)
expect(PricingItemRepository.save).not.toHaveBeenCalled()
}
})
})
29 changes: 29 additions & 0 deletions src/__tests__/billing/hooks/usePricingItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { act, renderHook } from '@testing-library/react-hooks'

import usePricingItem from '../../../billing/hooks/usePricingItem'
import PricingItemRepository from '../../../shared/db/PricingItemRepository'
import { PricingItem } from '../../../shared/model/PricingItem'
import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util'

describe('Use Pricing Item', () => {
const expectedPricingItemId = 'pricing item id'
const expectedPricingItem = {
id: expectedPricingItemId,
} as PricingItem

jest.spyOn(PricingItemRepository, 'find').mockResolvedValue(expectedPricingItem)

it('should return a pricing item by id', async () => {
let actualData: any
await act(async () => {
const renderHookResult = renderHook(() => usePricingItem(expectedPricingItemId))
const { result } = renderHookResult
await waitUntilQueryIsSuccessful(renderHookResult)
actualData = result.current.data
})

expect(PricingItemRepository.find).toHaveBeenCalledTimes(1)
expect(PricingItemRepository.find).toHaveBeenCalledWith(expectedPricingItemId)
expect(actualData).toEqual(expectedPricingItem)
})
})
61 changes: 61 additions & 0 deletions src/__tests__/billing/hooks/usePricingItemsSearch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { act, renderHook } from '@testing-library/react-hooks'

import usePricingItemsSearch from '../../../billing/hooks/usePricingItemsSearch'
import PricingItemSearchRequests from '../../../billing/model/PricingItemSearchRequest'
import PricingItemRepository from '../../../shared/db/PricingItemRepository'
import { PricingItem } from '../../../shared/model/PricingItem'
import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util'

describe('Use Pricing Items Search', () => {
const expectedPricingItem = [
{
name: 'pricing item',
},
] as PricingItem[]

jest.spyOn(PricingItemRepository, 'findAll').mockResolvedValue(expectedPricingItem)
jest.spyOn(PricingItemRepository, 'search').mockResolvedValue(expectedPricingItem)

afterEach(() => {
jest.clearAllMocks()
})

it('should find all if no criteria is provided', async () => {
const pricingItemsSearchRequest = {
text: '',
category: 'all',
} as PricingItemSearchRequests

let actualData: any
await act(async () => {
const renderHookResult = renderHook(() => usePricingItemsSearch(pricingItemsSearchRequest))
const { result } = renderHookResult
await waitUntilQueryIsSuccessful(renderHookResult)
actualData = result.current.data
})

expect(PricingItemRepository.search).not.toHaveBeenCalled()
expect(PricingItemRepository.findAll).toHaveBeenCalledTimes(1)
expect(actualData).toEqual(expectedPricingItem)
})

it('should search for pricing items', async () => {
const pricingItemsSearchRequest = {
text: 'pricing item',
} as PricingItemSearchRequests

let actualData: any
await act(async () => {
const renderHookResult = renderHook(() => usePricingItemsSearch(pricingItemsSearchRequest))
const { result } = renderHookResult
await waitUntilQueryIsSuccessful(renderHookResult)
actualData = result.current.data
})

expect(PricingItemRepository.findAll).not.toHaveBeenCalled()
expect(PricingItemRepository.search).toHaveBeenCalledWith(
expect.objectContaining(pricingItemsSearchRequest),
)
expect(actualData).toEqual(expectedPricingItem)
})
})
54 changes: 54 additions & 0 deletions src/__tests__/billing/hooks/useUpdatePricingItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { act } from '@testing-library/react-hooks'

import useUpdatePricingItem from '../../../billing/hooks/useUpdatePricingItem'
import { PricingItemError } from '../../../billing/utils/validate-pricingItem'
import * as validatePricingItem from '../../../billing/utils/validate-pricingItem'
import PricingItemRepository from '../../../shared/db/PricingItemRepository'
import { PricingItem } from '../../../shared/model/PricingItem'
import executeMutation from '../../test-utils/use-mutation.util'

describe('Use Update Pricing Item', () => {
const expectedPricingItem = {
name: 'pricing item',
category: 'imaging',
price: 100,
} as PricingItem

jest.spyOn(PricingItemRepository, 'saveOrUpdate').mockResolvedValue(expectedPricingItem)

beforeEach(() => {
jest.clearAllMocks()
})

it('should update pricing item', async () => {
let actualData: any

await act(async () => {
actualData = await executeMutation(() => useUpdatePricingItem(), expectedPricingItem)
})

expect(PricingItemRepository.saveOrUpdate).toHaveBeenCalledTimes(1)
expect(PricingItemRepository.saveOrUpdate).toHaveBeenCalledWith(expectedPricingItem)
expect(actualData).toEqual(expectedPricingItem)
})

it('should return errors', async () => {
rsousaj marked this conversation as resolved.
Show resolved Hide resolved
expect.hasAssertions()

const expectedErrors = {
message: 'error message',
name: 'error name',
price: 'error price',
category: 'error category',
} as PricingItemError

jest.spyOn(validatePricingItem, 'validateUpdatePricingItem').mockReturnValue(expectedErrors)

try {
await executeMutation(() => useUpdatePricingItem(), expectedPricingItem)
} catch (e) {
expect(e).toEqual(expectedErrors)
expect(PricingItemRepository.saveOrUpdate).not.toHaveBeenCalled()
}
})
})