Skip to content

test(renterd): files and uploads pagination and other tests #868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/stupid-clocks-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/design-system': minor
---

useTableState now supports a defaultSortDirection.
15 changes: 14 additions & 1 deletion apps/hostd-e2e/src/fixtures/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Page } from '@playwright/test'
import { expect, Page } from '@playwright/test'
import { maybeExpectAndReturn, step } from '@siafoundation/e2e'

export const getContractRowById = step(
Expand Down Expand Up @@ -33,6 +33,19 @@ export const getContractRowByIndex = step(
}
)

export const expectContractRowByIndex = step(
'expect contract row by index',
async (page: Page, index: number) => {
return expect(
page
.getByTestId('contractsTable')
.locator('tbody')
.getByRole('row')
.nth(index)
).toBeVisible()
}
)

export function getContractRows(page: Page) {
return page.getByTestId('contractsTable').locator('tbody').getByRole('row')
}
Expand Down
47 changes: 42 additions & 5 deletions apps/hostd-e2e/src/fixtures/volumes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Page, expect } from '@playwright/test'
import { navigateToVolumes } from './navigate'
import { fillTextInputByName, step } from '@siafoundation/e2e'
import {
clearToasts,
fillTextInputByName,
maybeExpectAndReturn,
step,
} from '@siafoundation/e2e'

export const createVolume = step(
'create volume',
Expand All @@ -20,9 +25,10 @@ export const createVolume = step(
await page.locator('input[name=size]').press('Enter')
await expect(page.getByRole('dialog')).toBeHidden()
const row = page.getByRole('row', { name: fullPath })
await expect(page.getByText('Volume created')).toBeVisible()
await expect(page.getByText('New volume created')).toBeVisible()
await expect(row.getByText('ready')).toBeVisible()
await expect(page.getByRole('cell', { name: fullPath })).toBeVisible()
await clearToasts({ page })
}
)

Expand All @@ -47,7 +53,7 @@ export const deleteVolumeIfExists = step(
'delete volume if exists',
async (page: Page, name: string, path: string) => {
const doesVolumeExist = await page
.getByRole('table')
.getByTestId('volumesTable')
.getByText(path + '/' + name)
.isVisible()
if (doesVolumeExist) {
Expand All @@ -67,16 +73,47 @@ export const openVolumeContextMenu = step(
}
)

export function getVolumeRows(page: Page) {
return page.getByTestId('volumesTable').locator('tbody').getByRole('row')
}

export const volumeInList = step(
'volume in list',
async (page: Page, name: string) => {
await expect(page.getByRole('table').getByText(name)).toBeVisible()
await expect(page.getByTestId('volumesTable').getByText(name)).toBeVisible()
}
)

export const volumeNotInList = step(
'volume not in list',
async (page: Page, name: string) => {
await expect(page.getByRole('table').getByText(name)).toBeHidden()
await expect(page.getByTestId('volumesTable').getByText(name)).toBeHidden()
}
)

export const getVolumeRowByIndex = step(
'get volume row by index',
async (page: Page, index: number, shouldExpect?: boolean) => {
return maybeExpectAndReturn(
page
.getByTestId('volumesTable')
.locator('tbody')
.getByRole('row')
.nth(index),
shouldExpect
)
}
)

export const expectVolumeRowByIndex = step(
'expect volume row by index',
async (page: Page, index: number) => {
return expect(
page
.getByTestId('volumesTable')
.locator('tbody')
.getByRole('row')
.nth(index)
).toBeVisible()
}
)
69 changes: 62 additions & 7 deletions apps/hostd-e2e/src/specs/contracts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
import { navigateToContracts } from '../fixtures/navigate'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import {
getContractRowByIndex,
expectContractRowByIndex,
getContractRows,
getContractRowsAll,
} from '../fixtures/contracts'
import { ContractsResponse, contractsRoute } from '@siafoundation/hostd-types'

test.beforeEach(async ({ page }) => {
await beforeTest(page, {
renterdCount: 2,
renterdCount: 3,
})
})

Expand All @@ -20,15 +21,15 @@ test.afterEach(async () => {
test('contracts bulk integrity check', async ({ page }) => {
await navigateToContracts(page)
const rows = await getContractRowsAll(page)
rows.at(0).click()
rows.at(-1).click({ modifiers: ['Shift'] })
await rows.at(0).click({ position: { x: 5, y: 5 } })
await rows.at(2).click({ modifiers: ['Shift'] })

const menu = page.getByLabel('contract multi-select menu')

// Run check for each contract.
await menu.getByLabel('run integrity check for each contract').click()
await expect(
page.getByText('Integrity checks started for 2 contracts')
page.getByText('Integrity checks started for 3 contracts')
).toBeVisible()
})

Expand All @@ -51,5 +52,59 @@ test('viewing a page with no data shows the correct empty state', async ({
await expect(page.getByText('Back to first page')).toBeVisible()
await page.getByText('Back to first page').click()
// Ensure we are now seeing rows of data.
await getContractRowByIndex(page, 0, true)
await expectContractRowByIndex(page, 0)
})

test('paginating contracts with known total and client side pagination', async ({
page,
}) => {
await interceptApiContactsAndEnsure3Results(page)
await navigateToContracts(page)
const url = page.url()
await page.goto(url + '?limit=1')

const first = page.getByRole('button', { name: 'go to first page' })
const previous = page.getByRole('button', { name: 'go to previous page' })
const next = page.getByRole('button', { name: 'go to next page' })
const last = page.getByRole('button', { name: 'go to last page' })
const rows = getContractRows(page)
await expect(rows).toHaveCount(1)
await expect(first).toBeDisabled()
await expect(previous).toBeDisabled()
await expect(next).toBeEnabled()
await expect(last).toBeEnabled()
await next.click()
await expect(rows).toHaveCount(1)
await expect(first).toBeEnabled()
await expect(previous).toBeEnabled()
await expect(next).toBeEnabled()
await expect(last).toBeEnabled()
await next.click()
await expect(rows).toHaveCount(1)
await expect(first).toBeEnabled()
await expect(previous).toBeEnabled()
await expect(next).toBeDisabled()
await expect(last).toBeDisabled()
})

async function interceptApiContactsAndEnsure3Results(page: Page) {
await page.route(`**/api${contractsRoute}*`, async (route) => {
console.log('Intercepted contracts API request')
// Fetch the original response.
const response = await route.fetch()

// Parse the response body as JSON.
const originalData: ContractsResponse = await response.json()

// Slice the contracts down to exactly 3 items.
const modifiedData: ContractsResponse = {
contracts: originalData.contracts.slice(0, 3),
count: Math.min(3, originalData.count),
}

// Fulfill the route with the modified response.
await route.fulfill({
json: modifiedData,
})
})
}
83 changes: 82 additions & 1 deletion apps/hostd-e2e/src/specs/volumes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { navigateToVolumes } from '../fixtures/navigate'
import {
createVolume,
deleteVolume,
expectVolumeRowByIndex,
getVolumeRows,
openVolumeContextMenu,
volumeInList,
} from '../fixtures/volumes'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import fs from 'fs'
import os from 'os'
import { fillTextInputByName } from '@siafoundation/e2e'
import path from 'path'

let dirPath = '/'

Expand Down Expand Up @@ -36,7 +40,7 @@ test('can resize volume', async ({ page }) => {
await createVolume(page, name, dirPath)
await openVolumeContextMenu(page, `${dirPath}/${name}`)
await page.getByText('Resize').click()
await fillTextInputByName(page, 'size', '1300')
await fillTextInputByName(page, 'size', '1300000')
const dialog = page.getByRole('dialog')
await expect(dialog.getByText('Must be between 10.00 GB')).toBeVisible()
await fillTextInputByName(page, 'size', '13')
Expand All @@ -46,3 +50,80 @@ test('can resize volume', async ({ page }) => {
await expect(page.getByText('Volume resizing')).toBeVisible()
await expect(page.getByText('resizing')).toBeVisible()
})

test('paginating volumes with known total and client side pagination', async ({
page,
}) => {
await navigateToVolumes({ page })
// The cluster creates an initial volume so the following is the second volume.
await createVolume(page, 'v2', dirPath)
await createVolume(page, 'v3', dirPath)
const url = page.url()
await page.goto(url + '?limit=1')

const first = page.getByRole('button', { name: 'go to first page' })
const previous = page.getByRole('button', { name: 'go to previous page' })
const next = page.getByRole('button', { name: 'go to next page' })
const last = page.getByRole('button', { name: 'go to last page' })
await expect(getVolumeRows(page).getByText('sia-cluster')).toBeVisible()
await expect(first).toBeDisabled()
await expect(previous).toBeDisabled()
await expect(next).toBeEnabled()
await expect(last).toBeEnabled()
await next.click()
await volumeInList(page, getVolumePath(dirPath, 'v2'))
await expect(first).toBeEnabled()
await expect(previous).toBeEnabled()
await expect(next).toBeEnabled()
await expect(last).toBeEnabled()
await next.click()
await volumeInList(page, getVolumePath(dirPath, 'v3'))
await expect(first).toBeEnabled()
await expect(previous).toBeEnabled()
await expect(next).toBeDisabled()
await expect(last).toBeDisabled()
})

test('viewing a page with no data shows the correct empty state', async ({
page,
}) => {
await navigateToVolumes({ page })
// The cluster creates an initial volume so the following is the second volume.
await createVolume(page, 'v2', dirPath)
const url = page.url()
await page.goto(url + '?limit=1')

const first = page.getByRole('button', { name: 'go to first page' })
const previous = page.getByRole('button', { name: 'go to previous page' })
const next = page.getByRole('button', { name: 'go to next page' })
const last = page.getByRole('button', { name: 'go to last page' })
await expect(getVolumeRows(page).getByText('sia-cluster')).toBeVisible()
await expect(first).toBeDisabled()
await expect(previous).toBeDisabled()
await expect(next).toBeEnabled()
await expect(last).toBeEnabled()
await next.click()
await volumeInList(page, getVolumePath(dirPath, 'v2'))
await expect(first).toBeEnabled()
await expect(previous).toBeEnabled()
await expect(next).toBeDisabled()
await expect(last).toBeDisabled()

await deleteVolume(page, 'v2', dirPath)

await expect(
page.getByText('No data on this page, reset pagination to continue.')
).toBeVisible()
await expect(page.getByText('Back to first page')).toBeVisible()
await page.getByText('Back to first page').click()
// Ensure we are now seeing rows of data.
await expectVolumeRowByIndex(page, 0)
await expect(first).toBeDisabled()
await expect(previous).toBeDisabled()
await expect(next).toBeDisabled()
await expect(last).toBeDisabled()
})

function getVolumePath(dirPath: string, name: string) {
return path.join(dirPath, name)
}
4 changes: 2 additions & 2 deletions apps/hostd/components/Volumes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useVolumes } from '../../contexts/volumes'
import { StateNoneYet } from './StateNoneYet'

export function Volumes() {
const { dataset, datasetState, isLoading, visibleColumns } = useVolumes()
const { datasetPage, datasetState, isLoading, visibleColumns } = useVolumes()
return (
<div className="p-6 min-w-fit">
<Table
testId="volumesTable"
isLoading={isLoading}
pageSize={20}
data={dataset}
data={datasetPage}
columns={visibleColumns}
emptyState={
<EmptyState datasetState={datasetState} noneYet={<StateNoneYet />} />
Expand Down
3 changes: 3 additions & 0 deletions apps/hostd/contexts/volumes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function useVolumesMain() {
} = useTableState('hostd/v0/volumes', {
columns,
columnsDefaultVisible,
defaultSortField: 'id',
defaultSortDirection: 'asc',
})

const response = useVolumesData({
Expand Down Expand Up @@ -68,6 +70,7 @@ function useVolumesMain() {
datasetPage,
isValidating,
error,
offset,
})

return {
Expand Down
Loading
Loading