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 all 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()
})
})
})
})
87 changes: 87 additions & 0 deletions src/__tests__/billing/hooks/useAddPricingItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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', async () => {
const expectedPricingItemImaging = {
...expectedPricingItem,
category: 'imaging',
} as PricingItem

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

expect(PricingItemRepository.save).toHaveBeenCalledTimes(1)
expect(PricingItemRepository.save).toHaveBeenCalledWith({
...expectedPricingItemImaging,
type: 'Imaging Procedure',
})
})

it('should fill type if its not filled when category is lab', async () => {
const expectedPricingItemLab = {
...expectedPricingItem,
category: 'lab',
} as PricingItem

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

expect(PricingItemRepository.save).toHaveBeenCalledTimes(1)
expect(PricingItemRepository.save).toHaveBeenCalledWith({
...expectedPricingItemLab,
type: 'Lab Procedure',
})
})

it('should throw errors if validation fails', async () => {
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 throw error if validation fails', async () => {
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()
}
})
})