Skip to content

Commit

Permalink
Nav: added tests (#340)
Browse files Browse the repository at this point in the history
Also added more helpers for tests:
- added new isSelected helper that returns true if an element has primary class
- configures mobx without readonly checks in jest env so that we can spy on actions
- now always create the "/virtual" memory volume when running jest tests
  • Loading branch information
warpdesign authored Dec 12, 2022
1 parent 7d07d9a commit 8d1f465
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 27 deletions.
1 change: 0 additions & 1 deletion src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const Nav = observer(() => {
}

const onToggleSplitView = (): void => {
debugger
if (appState.isExplorer) {
appState.toggleSplitViewMode()
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/__tests__/HamburgerMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react'
import { screen, render, setup, t } from 'rtl'
import { HamburgerMenu } from '../HamburgerMenu'

describe('Badge', () => {
describe('HamburgerMenu', () => {
const PROPS = {
onOpenPrefs: jest.fn(),
onOpenShortcuts: jest.fn(),
Expand Down
94 changes: 94 additions & 0 deletions src/components/__tests__/Nav.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @jest-environment jsdom
*/
import React from 'react'
import { screen, render, setup, t, isSelected } from 'rtl'

import { Nav } from '../Nav'
import { AppState } from '$src/state/appState'
import { Classes } from '@blueprintjs/core'

describe('Nav', () => {
const options = {
providerProps: {
appState: new AppState(),
},
}

beforeEach(async () => {
const appState = new AppState()
options.providerProps.appState = appState
await options.providerProps.appState.loadSettingsAndPrepareViews()

jest.spyOn(appState, 'showDownloadsTab')
jest.spyOn(appState, 'showExplorerTab')
jest.spyOn(appState, 'toggleSplitViewMode')

jest.clearAllMocks()
})

it('should display nav', () => {
const { container } = render(<Nav />, options)

expect(screen.getByText(t('APP_MENUS.ABOUT_TITLE'))).toBeInTheDocument()

const explorerButton = screen.getByRole('button', { name: t('NAV.EXPLORER') })
expect(explorerButton).toBeInTheDocument()
expect(isSelected(explorerButton)).toBe(true)

const downloadsButton = screen.getByRole('button', { name: t('NAV.TRANSFERS') })
expect(downloadsButton).toBeInTheDocument()
expect(isSelected(downloadsButton)).toBe(false)

const splitViewButton = container.querySelector('[data-icon="panel-stats"]')
expect(splitViewButton).toBeInTheDocument()
expect(splitViewButton.classList.contains(Classes.INTENT_PRIMARY)).toBe(
options.providerProps.appState.winStates[0].splitView,
)
})

it('should render badge', () => {
// simulate 10 running transfers so that the badge is displayed
jest.spyOn(options.providerProps.appState.transferListState, 'getRunningTransfers').mockReturnValue(10)
render(<Nav />, options)

expect(screen.getByText('10')).toBeInTheDocument()
})

describe('actions', () => {
it('should open hamburger menu when clicking on hamburger', async () => {
const { user } = setup(<Nav />, options)

await user.click(screen.getByRole('button', { name: t('NAV.TRANSFERS') }))

expect(options.providerProps.appState.showDownloadsTab).toHaveBeenCalled()
})

it('should show downloads when clicking on downloads button and show explorer when clicking on explorer button', async () => {
const { user } = setup(<Nav />, options)

const transfersButton = screen.getByRole('button', { name: t('NAV.TRANSFERS') })
const explorerButton = screen.getByRole('button', { name: t('NAV.EXPLORER') })

await user.click(transfersButton)

expect(options.providerProps.appState.showDownloadsTab).toHaveBeenCalled()
expect(isSelected(explorerButton)).toBe(false)
expect(isSelected(transfersButton)).toBe(true)

await user.click(explorerButton)
expect(options.providerProps.appState.showExplorerTab).toHaveBeenCalled()
expect(isSelected(explorerButton)).toBe(true)
expect(isSelected(transfersButton)).toBe(false)
})

it('should toggle splitview when clicking on splitview button', async () => {
const { user, container } = setup(<Nav />, options)

const splitViewButton = container.querySelector('[data-icon="panel-stats"]')
await user.click(splitViewButton)

expect(options.providerProps.appState.toggleSplitViewMode).toHaveBeenCalled()
})
})
})
10 changes: 0 additions & 10 deletions src/components/__tests__/Statusbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@ import { ViewState } from '$src/state/viewState'
import { vol } from 'memfs'

describe('Statusbar', () => {
vol.fromJSON(
{
dir1: null,
foo1: '',
foo2: '',
'.hidden': '',
},
'/virtual',
)

const options = {
providerProps: {
viewState: new ViewState(0),
Expand Down
4 changes: 1 addition & 3 deletions src/components/__tests__/TabList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import React from 'react'
import { within } from '@testing-library/dom'

import { screen, setup, render, t } from 'rtl'
import { screen, setup, render, t, isSelected } from 'rtl'
import { ViewState } from '$src/state/viewState'
import { ipcRenderer } from 'electron'
import { vol } from 'memfs'
Expand Down Expand Up @@ -34,8 +34,6 @@ describe('TabList', () => {
},
}

const isSelected = (element: HTMLElement) => element.classList.contains(Classes.INTENT_PRIMARY)

beforeEach(async () => {
options.providerProps.viewState = new ViewState(0)
const cache = options.providerProps.viewState.addCache('/virtual', -1, true)
Expand Down
2 changes: 2 additions & 0 deletions src/components/dialogs/PrefsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ const PrefsDialog = ({ isOpen, onClose }: PrefsProps) => {
const [defaultFolder, setDefaultFolder] = useState(settingsState.defaultFolder)
const [darkMode, setDarkMode] = useState(settingsState.darkMode)
const [defaultTerminal, setDefaultTerminal] = useState(settingsState.defaultTerminal)
// TODO: we could have a default folder that's not using FsLocal
const [isFolderValid, setIsFolderValid] = useState(
() => FsLocal.canread(defaultFolder) && FolderExists(defaultFolder),
)
const { t } = useTranslation()

const checkPath: (path: string) => void = debounce((path: string) => {
const isValid = FsLocal.canread(path) && FolderExists(path)

if (path !== settingsState.defaultFolder) {
setIsFolderValid(isValid)
// need to save settings
Expand Down
10 changes: 7 additions & 3 deletions src/components/dialogs/__tests__/prefsDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { setup, screen, LOCALE_EN, waitFor } from 'rtl'
import { SettingsState } from '$src/state/settingsState'
import { PrefsDialog } from '../PrefsDialog'
import { ipcRenderer } from 'electron'
import * as FsLocalAll from '$src/services/plugins/FsLocal'

describe('PrefsDialog', () => {
let settingsState: SettingsState
Expand Down Expand Up @@ -50,14 +51,17 @@ describe('PrefsDialog', () => {

it('should set default folder', async () => {
const spy = jest.spyOn(settingsState, 'setDefaultFolder')
jest.spyOn(FsLocalAll, 'FolderExists').mockReturnValue(true)
const { user } = setup(<PrefsDialog {...PROPS} />, { providerProps: { settingsState } })
screen.getByPlaceholderText(LOCALE_EN.DIALOG.PREFS.DEFAULT_FOLDER).focus()
const defaultFolderInput = screen.getByPlaceholderText(LOCALE_EN.DIALOG.PREFS.DEFAULT_FOLDER)

await user.paste('tmp/')
await user.clear(defaultFolderInput)
await user.paste('/virtual/dir1')

await waitFor(() => expect(spy).toHaveBeenCalled())

expect(settingsState.defaultFolder).toBe('/tmp/')
expect(settingsState.defaultFolder).toBe('/virtual/dir1')
;(FsLocalAll.FolderExists as jest.Mock).mockReset()
})

it('should set terminal', async () => {
Expand Down
1 change: 0 additions & 1 deletion src/gui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { I18nextProvider } from 'react-i18next'
import { Provider } from 'mobx-react'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { ipcRenderer } from 'electron'
import process from 'process'
import child_process from 'child_process'

Expand Down
6 changes: 2 additions & 4 deletions src/services/Fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,11 @@ export const withConnection = (method: (...args: any[]) => any, waitForConnectio
const retry_fn = async (...args: any[]) => {
try {
await waitForConnection()
return method(...args)
} catch (e) {
console.log('e')
debugger
retry_fn(...args)
console.log('withConnection error', { e })
}

return method(...args)
}

return retry_fn
Expand Down
2 changes: 1 addition & 1 deletion src/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const OS = (ipcRenderer && ipcRenderer.sendSync('app:getOS')) || {
isLinux: platform === 'linux',
optionKey: platform === 'darwin' ? 'Alt' : 'Control',
lineEnding: platform === 'win32' ? '\r\n' : '\n',
defaultFolder: '/',
defaultFolder: typeof jest !== 'undefined' ? '/virtual' : '/',
TMP_DIR: '/tmp',
HOME_DIR: '/cy/home',
DOWNLOADS_DIR: '/cy/downloads',
Expand Down
34 changes: 31 additions & 3 deletions src/utils/test/rtl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { i18n } from './i18n'
import { I18nextProvider } from 'react-i18next'
import { HotkeysProvider } from '@blueprintjs/core'
import { Classes, HotkeysProvider } from '@blueprintjs/core'
import { vol } from 'memfs'
import userEvent from '@testing-library/user-event'
import { configure as configureMobx } from 'mobx'

import { registerFs } from '$src/services/Fs'
import { FsVirtual } from '$src/services/plugins/FsVirtual'
import userEvent from '@testing-library/user-event'
import en from '$src/locale/lang/en.json'
const i18next = i18n.i18next

Expand All @@ -24,6 +27,9 @@ jest.mock('$src/locale/i18n', () => ({
languageList: ['en', 'fr'],
}))
jest.mock('electron', () => ({
shell: {
openPath: jest.fn(),
},
ipcRenderer: {
on: jest.fn(),
removeListener: jest.fn(),
Expand Down Expand Up @@ -128,17 +134,39 @@ const setup = (jsx: ReactElement, options = {}) => {

const wait = (delay = 0) => new Promise((res) => setTimeout(res, delay))

const isSelected = (element: HTMLElement) => element.classList.contains(Classes.INTENT_PRIMARY)

const t = i18n.i18next.t
const LOCALE_EN = en.translations

configure({
testIdAttribute: 'id',
})

// disable safeDescriptors so that we can spy on mobx actions
configureMobx({ safeDescriptors: false })

registerFs(FsVirtual)
;(global as unknown as Window).ENV = {
CY: false,
VERSION: 'jest',
HASH: '',
NODE_ENV: 'production',
BUILD_DATE: new Date().toString(),
}

vol.fromJSON(
{
dir1: null,
foo1: '',
foo2: '',
'.hidden': '',
},
'/virtual',
)

// re-export everything
export * from '@testing-library/react'

// override render method
export { customRender as render, setup, withMarkup, LOCALE_EN, userEvent, t, i18next, wait }
export { customRender as render, setup, withMarkup, isSelected, LOCALE_EN, userEvent, t, i18next, wait }

0 comments on commit 8d1f465

Please sign in to comment.