Skip to content

Commit

Permalink
refactor: improve tests and clarity
Browse files Browse the repository at this point in the history
  • Loading branch information
acezard committed Dec 30, 2023
1 parent c15fabf commit 05e2377
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 168 deletions.
15 changes: 15 additions & 0 deletions src/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,18 @@ declare module 'cozy-ui/transpiled/react/deprecated/Alerter' {

export default Alerter
}

declare module 'cozy-ui/transpiled/react/providers/Alert' {
export const useAlert: () => {
showAlert: (
message: string,
severity?:
| 'primary'
| 'secondary'
| 'success'
| 'error'
| 'warning'
| 'info'
) => void
}
}
4 changes: 2 additions & 2 deletions src/drive/web/modules/drive/AddMenu/AddMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import UploadItem from 'drive/web/modules/drive/Toolbar/components/UploadItem'
import { ScannerMenuItem } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerMenuItem'
import CreateOnlyOfficeItem from 'drive/web/modules/drive/Toolbar/components/CreateOnlyOfficeItem'
import { isOfficeEditingEnabled } from 'drive/web/modules/views/OnlyOffice/helpers'
import { ScannerContext } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerContext'
import { useScannerContext } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerProvider'

export const ActionMenuContent = ({
isDisabled,
Expand All @@ -29,7 +29,7 @@ export const ActionMenuContent = ({
}) => {
const { t } = useI18n()
const { isMobile, isDesktop } = useBreakpoints()
const { hasScanner } = React.useContext(ScannerContext)
const { hasScanner } = useScannerContext()

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ import { render, fireEvent, waitFor } from '@testing-library/react'
import { useWebviewIntent } from 'cozy-intent'
import { createMockClient } from 'cozy-client'

import { ScannerProvider } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerProvider'
import { ScannerMenuItem } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerMenuItem'

// @ts-expect-error Component is not typed
import AppLike from 'test/components/AppLike'

// @ts-expect-error Don't care about the mocked client here
const client = createMockClient({})
import { ScannerProvider } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerProvider'
import { ScannerMenuItem } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerMenuItem'
import { uploadFiles } from 'drive/web/modules/navigation/duck'

const MockApp = ({ id = 'test' }): JSX.Element => (
<AppLike client={client}>
<AppLike client={createMockClient()}>
<ScannerProvider displayedFolder={{ id }}>
<ScannerMenuItem />
</ScannerProvider>
Expand All @@ -31,83 +28,137 @@ jest.mock('cozy-intent', () => ({
useWebviewIntent: jest.fn()
}))

const mockDispatch = jest.fn()
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: (): jest.Mock => mockDispatch() as jest.Mock
const mockUploadFiles = uploadFiles as jest.Mock
jest.mock('drive/web/modules/navigation/duck', () => ({
uploadFiles: jest
.fn()
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
.mockImplementation(arg => ({ type: 'test', payload: arg }))
}))

jest.spyOn(console, 'log').mockImplementation(() => jest.fn())

// Test suite for the Scanner functionality
describe('Scanner', () => {
// Before each test, clear all mocks to ensure a clean state
beforeEach(() => {
jest.clearAllMocks()
})

// Test case: Ensure that nothing is rendered if the scanner is not available
it('renders nothing if the scanner is not available', () => {
// Mock the useWebviewIntent hook to always return false for scanner availability
mockUseWebviewIntent.mockReturnValue({
call: jest.fn().mockResolvedValue(false)
})

// Render the component under test
const { queryByTestId } = render(<MockApp />)

// Assert that the scan-doc element is not present in the DOM
expect(queryByTestId('scan-doc')).toBeNull()
})

// Test case: Check if an ActionMenuItem is rendered when the scanner is available
it('renders an ActionMenuItem if the folder is available', async () => {
// Mock the useWebviewIntent hook to simulate scanner availability
mockUseWebviewIntent.mockReturnValue({
call: jest.fn((method, arg) => {
if (method === 'isAvailable' && arg === 'scanner') {
return Promise.resolve(true)
}

return Promise.resolve(false)
})
})

// Render the component under test
const { queryByTestId } = render(<MockApp />)

// Wait for the scanner to become available and assert that the scan-doc element is present
await waitFor(() => {
expect(queryByTestId('scan-doc')).not.toBeNull()
})
})

// Test case: Simulate a click event and verify the startScanner function is called
it('calls the startScanner function on click', async () => {
// Mock the useWebviewIntent hook with custom logic for scanner availability and document scanning
mockUseWebviewIntent.mockReturnValue({
call: jest.fn((method, arg) => {
if (method === 'isAvailable' && arg === 'scanner') {
return Promise.resolve(true)
}

if (method === 'scanDocument') {
return Promise.resolve('base64jpeg')
}

return Promise.resolve(false)
})
})

// Render the component under test
const { queryByTestId } = render(<MockApp />)

// Wait for the scan-doc element to be clickable and then simulate a click event
await waitFor(() => {
queryByTestId('scan-doc') as HTMLButtonElement
fireEvent.click(
queryByTestId('scan-doc')?.firstChild as HTMLButtonElement
)
})

fireEvent.click(queryByTestId('scan-doc')?.firstChild as HTMLButtonElement)

expect(mockDispatch).toHaveBeenCalled()
// Assert that the mockUploadFiles function was called with the expected arguments
expect((mockUploadFiles.mock.calls[0] as [unknown])[0]).toEqual(
expect.arrayContaining([
expect.objectContaining({
file: expect.any(Object) as Record<string, unknown>,
isDirectory: false,
name: expect.stringMatching(/\.jpg$/) as string
})
])
)
})

// Test case: Handle unexpected errors gracefully
it('handles unexpected errors', async () => {
const mockConsoleError = jest
.spyOn(console, 'log')
.mockImplementation(() => {
// noop
})

// Mock the useWebviewIntent hook to throw an error
mockUseWebviewIntent.mockReturnValue({
call: jest.fn(() => {
throw new Error('Unexpected error')
call: jest.fn((method, arg) => {
if (method === 'isAvailable' && arg === 'scanner') {
return Promise.resolve(true)
}

if (method === 'scanDocument') {
return Promise.reject(new Error('test error'))
}

return Promise.resolve(false)
})
})

// Render the component under test
const { queryByTestId } = render(<MockApp />)

// Wait for the scan-doc element to be clickable and then simulate a click event
await waitFor(() => {
queryByTestId('scan-doc') as HTMLButtonElement
fireEvent.click(
queryByTestId('scan-doc')?.firstChild as HTMLButtonElement
)
})

// Wait for the component to react to the error and assert that the scan-doc element is not present
await waitFor(() => {
expect(queryByTestId('scan-doc')).toBeNull()
expect(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
mockConsoleError.mock.calls.some(call => call[0].includes('test error'))
).toBe(true)
expect(queryByTestId('scan-doc')).not.toBeNull()
})
})
})

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { ActionMenuItem } from 'cozy-ui/transpiled/react/deprecated/ActionMenu'
import Icon from 'cozy-ui/transpiled/react/Icon'
import CameraIcon from 'cozy-ui/transpiled/react/Icons/Camera'

import { useScannerContext } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerContext'
import { useScannerContext } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerProvider'

/**
* Renders a scanner menu item.
* @returns The JSX element representing the scanner menu item.
*/
export const ScannerMenuItem = (): JSX.Element | null => {
const { t } = useI18n()
const scannerContext = useScannerContext()
const { hasScanner, startScanner } = useScannerContext()

return scannerContext.hasScanner ? (
return hasScanner ? (
<div data-testid="scan-doc">
<ActionMenuItem
onClick={scannerContext.startScanner}
onClick={startScanner}
left={<Icon icon={CameraIcon} />}
data-testid="scan-doc"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import React from 'react'
import React, { useContext } from 'react'

import { ScannerContext } from 'drive/web/modules/drive/Toolbar/components/Scanner/ScannerContext'
import { useStartScanner } from 'drive/web/modules/drive/Toolbar/components/Scanner/useStartScanner'
import { useScanner } from 'drive/web/modules/drive/Toolbar/components/Scanner/useScanner'
import { useScannerService } from 'drive/web/modules/drive/Toolbar/components/Scanner/useScannerService'

interface ScannerContextValue {
startScanner?: () => Promise<void>
hasScanner: boolean
}

interface ScannerProviderProps {
children: React.ReactNode
displayedFolder: { id: string }
}

/**
* Context object for the Scanner component.
*/
export const ScannerContext = React.createContext<ScannerContextValue>({
startScanner: undefined,
hasScanner: false
})

export const useScannerContext = (): ScannerContextValue =>
useContext(ScannerContext)

/**
* Provides the scanner functionality.
*
Expand All @@ -19,13 +33,10 @@ export const ScannerProvider = ({
children,
displayedFolder
}: ScannerProviderProps): JSX.Element => {
const scanner = useScanner()
const startScanner = useStartScanner(displayedFolder)
const scanner = useScannerService(displayedFolder)

return (
<ScannerContext.Provider
value={{ startScanner, hasScanner: scanner.hasScanner }}
>
<ScannerContext.Provider value={scanner}>
{children}
</ScannerContext.Provider>
)
Expand Down

This file was deleted.

Loading

0 comments on commit 05e2377

Please sign in to comment.