Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
81 changes: 48 additions & 33 deletions src/components/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fireEvent, render } from '@testing-library/react'
import { act, render } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import Dropdown from './Dropdown'
import styles from './Dropdown.module.css'
Expand All @@ -14,7 +15,7 @@ describe('Dropdown Component', () => {
expect(div?.classList).toContain(styles.dropdownLeft)
})

it('toggles dropdown content on button click', () => {
it('toggles dropdown content on button click', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown label='go'>
<div>Child 1</div>
Expand All @@ -24,15 +25,16 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu with click
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')

// click again to close
fireEvent.click(dropdownButton)
await user.click(dropdownButton)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})

it('closes dropdown when clicking outside', () => {
it('closes dropdown when clicking outside', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown>
<div>Child 1</div>
Expand All @@ -41,15 +43,16 @@ describe('Dropdown Component', () => {
)

const dropdownButton = getByRole('button')
fireEvent.click(dropdownButton) // open dropdown
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')

// Simulate a click outside
fireEvent.mouseDown(document)
await user.click(document.body)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})

it('does not close dropdown when clicking inside', () => {
it('close dropdown when clicking inside', async () => {
const { container: { children: [ div ] }, getByRole, getByText } = render(
<Dropdown>
<div>Child 1</div>
Expand All @@ -58,16 +61,17 @@ describe('Dropdown Component', () => {
)

const dropdownButton = getByRole('button')
fireEvent.click(dropdownButton) // open dropdown
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')

const dropdownContent = getByText('Child 1').parentElement
if (!dropdownContent) throw new Error('Dropdown content not found')
fireEvent.mouseDown(dropdownContent)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')
await user.click(dropdownContent)
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})

it('closes dropdown on escape key press', () => {
it('closes dropdown on escape key press', async () => {
const { container: { children: [ div ] }, getByRole } = render(
<Dropdown>
<div>Child 1</div>
Expand All @@ -76,11 +80,12 @@ describe('Dropdown Component', () => {
)

const dropdownButton = getByRole('button')
fireEvent.click(dropdownButton) // open dropdown
const user = userEvent.setup()
await user.click(dropdownButton) // open dropdown
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('true')

// Press escape key
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
await user.keyboard('{Escape}')
expect(div?.children[0]?.getAttribute('aria-expanded')).toBe('false')
})

Expand All @@ -107,7 +112,7 @@ describe('Dropdown Component', () => {
})

// Keyboard navigation tests
it('opens dropdown and focuses first item on ArrowDown when closed', () => {
it('opens dropdown and focuses first item on ArrowDown when closed', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -120,15 +125,20 @@ describe('Dropdown Component', () => {
// initially closed
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')

// focus the button
act(() => {
dropdownButton.focus()
})
// down arrow to open menu
fireEvent.keyDown(dropdownButton, { key: 'ArrowDown', code: 'ArrowDown' })
const user = userEvent.setup()
await user.keyboard('{ArrowDown}')
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')

// first menu item should be focused
expect(document.activeElement).toBe(menuItems[0])
})

it('focuses the next item on ArrowDown and wraps to first item if at the end', () => {
it('focuses the next item on ArrowDown and wraps to first item if at the end', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -139,19 +149,20 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu, first item has focus
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])

// second item should be focused
fireEvent.keyDown(menuItems[0], { key: 'ArrowDown', code: 'ArrowDown' })
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[1])

// wrap back to first item
fireEvent.keyDown(menuItems[1], { key: 'ArrowDown', code: 'ArrowDown' })
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[0])
})

it('focuses the previous item on ArrowUp and wraps to the last item if at the top', () => {
it('focuses the previous item on ArrowUp and wraps to the last item if at the top', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -162,15 +173,16 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu, first item has focus
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])

// ArrowUp -> should wrap to last item
fireEvent.keyDown(menuItems[0], { key: 'ArrowUp', code: 'ArrowUp' })
await user.keyboard('{ArrowUp}')
expect(document.activeElement).toBe(menuItems[1])
})

it('focuses first item on Home key press', () => {
it('focuses first item on Home key press', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -182,19 +194,20 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu, first item has focus
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])

// move to the second item
fireEvent.keyDown(menuItems[0], { key: 'ArrowDown', code: 'ArrowDown' })
await user.keyboard('{ArrowDown}')
expect(document.activeElement).toBe(menuItems[1])

// Home key should focus first item
fireEvent.keyDown(menuItems[1], { key: 'Home', code: 'Home' })
await user.keyboard('{Home}')
expect(document.activeElement).toBe(menuItems[0])
})

it('focuses last item on End key press', () => {
it('focuses last item on End key press', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -206,15 +219,16 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu, first item has focus
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])

// End key should focus the last item
fireEvent.keyDown(menuItems[0], { key: 'End', code: 'End' })
await user.keyboard('{End}')
expect(document.activeElement).toBe(menuItems[2])
})

it('closes the menu and puts focus back on the button on Escape', () => {
it('closes the menu and puts focus back on the button on Escape', async () => {
const { getByRole, getAllByRole } = render(
<Dropdown label="Menu">
<button role="menuitem">Item 1</button>
Expand All @@ -225,12 +239,13 @@ describe('Dropdown Component', () => {
const dropdownButton = getByRole('button')

// open menu, first item has focus
fireEvent.click(dropdownButton)
const user = userEvent.setup()
await user.click(dropdownButton)
expect(document.activeElement).toBe(menuItems[0])
expect(dropdownButton.getAttribute('aria-expanded')).toBe('true')

// escape closes menu
fireEvent.keyDown(menuItems[0], { key: 'Escape', code: 'Escape' })
await user.keyboard('{Escape}')
expect(dropdownButton.getAttribute('aria-expanded')).toBe('false')

// focus returns to the button
Expand Down
6 changes: 5 additions & 1 deletion src/components/File/File.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react'
import { strict as assert } from 'assert'
import { act } from 'react'
import { describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Config, ConfigProvider } from '../../hooks/useConfig.js'
import { getHttpSource, getHyperparamSource } from '../../lib/sources/index.js'
import File from './File.js'
Expand All @@ -21,6 +21,10 @@ const headers = { get: vi.fn() }
globalThis.fetch = vi.fn(() => Promise.resolve({ text, headers } as unknown as Response))

describe('File Component', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('renders a local file path', async () => {
text.mockResolvedValueOnce('test content')
const source = getHyperparamSource('folder/subfolder/test.txt', { endpoint })
Expand Down
59 changes: 31 additions & 28 deletions src/components/Folder/Folder.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { fireEvent, render, waitFor } from '@testing-library/react'
import { render, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { strict as assert } from 'assert'
import { act } from 'react'
import { describe, expect, it, test, vi } from 'vitest'
import { beforeEach, describe, expect, it, test, vi } from 'vitest'
import { Config, ConfigProvider } from '../../hooks/useConfig.js'
import { DirSource, FileMetadata, HyperparamFileMetadata, getHyperparamSource } from '../../lib/sources/index.js'
import Folder from './Folder.js'
Expand All @@ -21,6 +22,10 @@ const config: Config = {
globalThis.fetch = vi.fn()

describe('Folder Component', () => {
beforeEach(() => {
vi.clearAllMocks()
})

test.for([
'',
'subfolder/',
Expand Down Expand Up @@ -99,19 +104,16 @@ describe('Folder Component', () => {

// Type a search query
const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
act(() => {
fireEvent.keyUp(searchInput, { target: { value: 'file1' } })
})
const user = userEvent.setup()
await user.type(searchInput, 'file1')

// Only matching files are displayed
await findByText('file1.txt')
expect(queryByText('folder1/')).toBeNull()
expect(queryByText('report.pdf')).toBeNull()

// Clear search with escape key
act(() => {
fireEvent.keyUp(searchInput, { key: 'Escape' })
})
await user.type(searchInput, '{Escape}')

await findByText('report.pdf')
getByText('folder1/')
Expand Down Expand Up @@ -140,26 +142,24 @@ describe('Folder Component', () => {

// Type a search query and hit enter
const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
act(() => {
fireEvent.keyUp(searchInput, { target: { value: 'file1' } })
})

const user = userEvent.setup()
await user.type(searchInput, 'file1')
await findByText('file1.txt')

act(() => {
fireEvent.keyUp(searchInput, { key: 'Enter' })
})

await user.type(searchInput, '{Enter}')
expect(location.href).toBe('/files?key=file1.txt')
})

it('jumps to search box when user types /', async () => {
it('jumps to search box when user types / while the body is focused', async () => {
const dirSource: DirSource = {
sourceId: 'test-source',
sourceParts: [{ text: 'test-source', sourceId: 'test-source' }],
kind: 'directory',
prefix: '',
listFiles: () => Promise.resolve([]),
listFiles: async () => {
await fetch('something') // to ensure we wait for loading
return []
},
}
const { getByPlaceholderText } = render(<Folder source={dirSource} />)

Expand All @@ -169,30 +169,33 @@ describe('Folder Component', () => {
})

const searchInput = getByPlaceholderText('Search...') as HTMLInputElement
const user = userEvent.setup()

// Typing / should focus the search box
act(() => {
fireEvent.keyDown(document.body, { key: '/' })
})
// By default, the search box is already focused in this test
expect(document.activeElement).toBe(searchInput)

// Typing inside the search box should work including /
act(() => {
fireEvent.keyUp(searchInput, { target: { value: 'file1/' } })
})
await user.type(searchInput, 'file1/')
expect(searchInput.value).toBe('file1/')

// Unfocus and re-focus should select all text in search box
act(() => {
searchInput.blur()
})
expect(document.activeElement).not.toBe(searchInput)
expect(document.activeElement).toBe(document.body)

act(() => {
fireEvent.keyDown(document.body, { key: '/' })
})
await user.keyboard('/')
expect(document.activeElement).toBe(searchInput)
expect(searchInput.selectionStart).toBe(0)
expect(searchInput.selectionEnd).toBe(searchInput.value.length)

// Focus another element and try again: it does not focus the search box
await user.tab()
expect(document.activeElement).not.toBe(searchInput)
expect(document.activeElement).not.toBe(document.body)

await user.keyboard('/')
expect(document.activeElement).not.toBe(searchInput)
})
})
7 changes: 6 additions & 1 deletion src/components/ImageView/ImageView.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { render } from '@testing-library/react'
import { strict as assert } from 'assert'
import { describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getHyperparamSource } from '../../lib/sources/index.js'
import ImageView from './ImageView.js'

globalThis.fetch = vi.fn()

describe('ImageView Component', () => {
beforeEach(() => {
vi.clearAllMocks()
// unnecessary for now because it has only one test, but safer for future tests
})

it('renders the image correctly', async () => {
const body = new ArrayBuffer(8)
vi.mocked(fetch).mockResolvedValueOnce({
Expand Down
Loading