Skip to content

Commit b573d33

Browse files
committed
fix: renterd file rename, add e2e tests
1 parent 1a4a2bd commit b573d33

27 files changed

+419
-109
lines changed

.changeset/gentle-countries-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@siafoundation/design-system': minor
3+
---
4+
5+
Updated react-hook-form.

.changeset/hip-turkeys-remember.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'renterd': patch
3+
---
4+
5+
Fixed an issue where renaming a file would throw an error.

.changeset/short-bears-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'renterd': patch
3+
---
4+
5+
Fixed an issue where the empty directory state was showing an empty bucket message.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { mockApiSiaCentralExchangeRates } from '@siafoundation/sia-central-mock'
2+
import { login } from './login'
3+
import { navigateToConfig } from './navigate'
4+
import { configResetAllSettings } from './configResetAllSettings'
5+
import { setViewMode } from './configViewMode'
6+
import { Page } from 'playwright'
7+
8+
export async function beforeTest(page: Page) {
9+
await mockApiSiaCentralExchangeRates({ page })
10+
await login({ page })
11+
12+
// Reset state.
13+
await navigateToConfig({ page })
14+
await configResetAllSettings({ page })
15+
await setViewMode({ page, state: 'basic' })
16+
}

apps/renterd-e2e/src/fixtures/buckets.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Page, expect } from '@playwright/test'
22
import { navigateToBuckets } from './navigate'
33
import { fillTextInputByName } from './textInput'
44
import { clearToasts } from './clearToasts'
5+
import { deleteDirectory, deleteFile } from './files'
56

67
export async function createBucket(page: Page, name: string) {
78
await navigateToBuckets({ page })
@@ -25,10 +26,36 @@ export async function deleteBucket(page: Page, name: string) {
2526

2627
export async function deleteBucketIfExists(page: Page, name: string) {
2728
const doesBucketExist = await page
28-
.getByRole('table')
29+
.getByTestId('bucketsTable')
2930
.getByText(name)
3031
.isVisible()
3132
if (doesBucketExist) {
33+
await openBucket(page, name)
34+
// The list changes to filesTable and is still loading=false for a split second
35+
// before the files start fetching - this is why we need to wait for 1000ms.
36+
// eslint-disable-next-line playwright/no-wait-for-timeout
37+
await page.waitForTimeout(1000)
38+
await expect(
39+
page.locator('[data-testid=filesTable][data-loading=false]')
40+
).toBeVisible()
41+
const tableRows = await page
42+
.getByTestId('filesTable')
43+
.getByTestId(new RegExp(`${name}.*`))
44+
.all()
45+
// First delete all top-level objects in the bucket, because a bucket
46+
// can't be deleted if there are objects in it.
47+
for (const row of tableRows) {
48+
const id = await row.getAttribute('data-testid')
49+
if (id === '..') {
50+
continue
51+
}
52+
if (id?.endsWith('/')) {
53+
await deleteDirectory(page, id)
54+
} else {
55+
await deleteFile(page, id)
56+
}
57+
}
58+
await navigateToBuckets({ page })
3259
await deleteBucket(page, name)
3360
}
3461
}
@@ -37,10 +64,16 @@ export async function openBucketContextMenu(page: Page, name: string) {
3764
await page.getByRole('row', { name }).getByRole('button').first().click()
3865
}
3966

67+
export async function openBucket(page: Page, name: string) {
68+
await page.getByRole('row').getByText(name).click()
69+
await expect(page.getByTestId('navbar').getByText(name)).toBeVisible()
70+
await expect(page.getByLabel('Upload files')).toBeVisible()
71+
}
72+
4073
export async function bucketInList(page: Page, name: string) {
41-
await expect(page.getByRole('table').getByText(name)).toBeVisible()
74+
await expect(page.getByTestId('bucketsTable').getByText(name)).toBeVisible()
4275
}
4376

4477
export async function bucketNotInList(page: Page, name: string) {
45-
await expect(page.getByRole('table').getByText(name)).toBeHidden()
78+
await expect(page.getByTestId('bucketsTable').getByText(name)).toBeHidden()
4679
}

apps/renterd-e2e/src/fixtures/configResetAllSettings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function configResetAllSettings({ page }: { page: Page }) {
1313
await fillTextInputByName(page, 'storageTB', '1')
1414
await fillTextInputByName(page, 'uploadTBMonth', '1')
1515
await fillTextInputByName(page, 'downloadTBMonth', '1')
16-
await fillTextInputByName(page, 'allowanceMonth', '1')
16+
await fillTextInputByName(page, 'allowanceMonth', '21000')
1717
await fillTextInputByName(page, 'periodWeeks', '6')
1818
await fillTextInputByName(page, 'renewWindowWeeks', '2')
1919
await fillTextInputByName(page, 'amountHosts', '12')
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Page, expect } from '@playwright/test'
2+
import { readFileSync } from 'fs'
3+
import { fillTextInputByName } from './textInput'
4+
5+
export async function deleteFile(page: Page, path: string) {
6+
await openFileContextMenu(page, path)
7+
await page.getByRole('menuitem', { name: 'Delete file' }).click()
8+
await expect(page.getByRole('dialog').getByText('Delete file')).toBeVisible()
9+
await page.locator('form button[type=submit]').click()
10+
await expect(page.getByRole('dialog')).toBeHidden()
11+
await fileNotInList(page, path)
12+
}
13+
14+
export async function deleteFileIfExists(page: Page, path: string) {
15+
const exists = await page.getByRole('table').getByTestId(path).isVisible()
16+
if (exists) {
17+
await deleteFile(page, path)
18+
}
19+
}
20+
21+
export async function deleteDirectory(page: Page, path: string) {
22+
await openDirectoryContextMenu(page, path)
23+
const deleteDirectoryItem = page.getByRole('menuitem', {
24+
name: 'Delete directory',
25+
})
26+
await expect(deleteDirectoryItem).toBeVisible()
27+
await deleteDirectoryItem.click()
28+
await expect(
29+
page.getByRole('dialog').getByText('Delete directory')
30+
).toBeVisible()
31+
await page.locator('form button[type=submit]').click()
32+
await expect(page.getByRole('dialog')).toBeHidden()
33+
await fileNotInList(page, path)
34+
}
35+
36+
export async function deleteDirectoryIfExists(page: Page, path: string) {
37+
const exists = await page.getByRole('table').getByTestId(path).isVisible()
38+
if (exists) {
39+
await deleteDirectory(page, path)
40+
}
41+
}
42+
43+
export async function openDirectoryContextMenu(page: Page, path: string) {
44+
const selector = page.getByTestId(path).getByLabel('Directory context menu')
45+
// Click doesn't work until animation is finished.
46+
// eslint-disable-next-line playwright/no-wait-for-timeout
47+
await page.waitForTimeout(100)
48+
await expect(selector).toBeVisible()
49+
await selector.click()
50+
}
51+
52+
export async function openFileContextMenu(page: Page, path: string) {
53+
const selector = page.getByTestId(path).getByLabel('File context menu')
54+
await expect(selector).toBeVisible()
55+
await selector.click()
56+
}
57+
58+
export async function openDirectory(page: Page, path: string) {
59+
await page.getByRole('table').getByTestId(path).click()
60+
for (const dir of path.split('/').slice(0, -1)) {
61+
await expect(page.getByTestId('navbar').getByText(dir)).toBeVisible()
62+
}
63+
}
64+
65+
export async function crateDirectory(page: Page, name: string) {
66+
await expect(page.getByLabel('Create directory')).toBeVisible()
67+
await page.getByLabel('Create directory').click()
68+
await fillTextInputByName(page, 'name', name)
69+
await page.locator('input[name=name]').press('Enter')
70+
await expect(page.getByRole('dialog')).toBeHidden()
71+
}
72+
73+
export async function createDirectoryIfNotExists(page: Page, name: string) {
74+
const exists = await page.getByRole('table').getByTestId(name).isVisible()
75+
if (!exists) {
76+
await crateDirectory(page, name)
77+
}
78+
}
79+
80+
export async function fileInList(page: Page, path: string) {
81+
await expect(page.getByRole('table').getByTestId(path)).toBeVisible()
82+
}
83+
84+
export async function fileNotInList(page: Page, path: string) {
85+
await expect(page.getByRole('table').getByTestId(path)).toBeHidden()
86+
}
87+
88+
export async function dragAndDropFile(
89+
page: Page,
90+
selector: string,
91+
filePath: string,
92+
fileName: string,
93+
fileType = ''
94+
) {
95+
const buffer = readFileSync(filePath).toString('base64')
96+
97+
const dataTransfer = await page.evaluateHandle(
98+
async ({ bufferData, localFileName, localFileType }) => {
99+
const dt = new DataTransfer()
100+
101+
const blobData = await fetch(bufferData).then((res) => res.blob())
102+
103+
const file = new File([blobData], localFileName, { type: localFileType })
104+
dt.items.add(file)
105+
return dt
106+
},
107+
{
108+
bufferData: `data:application/octet-stream;base64,${buffer}`,
109+
localFileName: fileName,
110+
localFileType: fileType,
111+
}
112+
)
113+
114+
await page.dispatchEvent(selector, 'drop', { dataTransfer })
115+
}

apps/renterd-e2e/src/fixtures/navigate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Page, expect } from '@playwright/test'
22

33
export async function navigateToBuckets({ page }: { page: Page }) {
4-
await page.getByLabel('Files').click()
4+
await page.getByTestId('sidenav').getByLabel('Files').click()
55
await expect(page.getByTestId('navbar').getByText('Buckets')).toBeVisible()
66
}
77

88
export async function navigateToConfig({ page }: { page: Page }) {
9-
await page.getByLabel('Configuration').click()
9+
await page.getByTestId('sidenav').getByLabel('Configuration').click()
1010
await expect(
1111
page.getByTestId('navbar').getByText('Configuration')
1212
).toBeVisible()

apps/renterd-e2e/src/specs/buckets.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { test, expect } from '@playwright/test'
22
import { navigateToBuckets } from '../fixtures/navigate'
3-
import { login } from '../fixtures/login'
43
import {
54
bucketInList,
65
createBucket,
76
deleteBucket,
87
deleteBucketIfExists,
98
openBucketContextMenu,
109
} from '../fixtures/buckets'
10+
import { beforeTest } from '../fixtures/beforeTest'
11+
12+
test.beforeEach(async ({ page }) => {
13+
await beforeTest(page)
14+
})
1115

1216
test('can change a buckets policy', async ({ page }) => {
13-
await login({ page })
1417
await navigateToBuckets({ page })
1518
await openBucketContextMenu(page, 'default')
1619
await page.getByRole('menuitem', { name: 'Change policy' }).click()
@@ -22,7 +25,7 @@ test('can change a buckets policy', async ({ page }) => {
2225
})
2326

2427
test('can create and delete a bucket', async ({ page }) => {
25-
await login({ page })
28+
await navigateToBuckets({ page })
2629
await deleteBucketIfExists(page, 'my-new-bucket')
2730
await createBucket(page, 'my-new-bucket')
2831
await deleteBucket(page, 'my-new-bucket')

apps/renterd-e2e/src/specs/config.spec.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
import { test, expect } from '@playwright/test'
2-
import { login } from '../fixtures/login'
32
import { expectSwitchByLabel, setSwitchByLabel } from '../fixtures/switchValue'
43
import { setViewMode } from '../fixtures/configViewMode'
54
import { navigateToConfig } from '../fixtures/navigate'
6-
import { mockApiSiaCentralExchangeRates } from '@siafoundation/sia-central-mock'
75
import {
86
expectTextInputByName,
97
expectTextInputByNameAttribute,
108
fillTextInputByName,
119
} from '../fixtures/textInput'
12-
import { configResetAllSettings } from '../fixtures/configResetAllSettings'
1310
import { clearToasts } from '../fixtures/clearToasts'
1411
import { clickIfEnabledAndWait, clickIf } from '../fixtures/click'
12+
import { beforeTest } from '../fixtures/beforeTest'
1513

16-
test('basic field change and save behaviour', async ({ page }) => {
17-
// Set up.
18-
await mockApiSiaCentralExchangeRates({ page })
19-
await login({ page })
14+
test.beforeEach(async ({ page }) => {
15+
await beforeTest(page)
16+
})
2017

18+
test('basic field change and save behaviour', async ({ page }) => {
2119
// Reset state.
2220
await navigateToConfig({ page })
23-
await configResetAllSettings({ page })
2421
await setViewMode({ page, state: 'basic' })
2522
await setSwitchByLabel(page, 'autoAllowance', true)
2623

@@ -50,10 +47,6 @@ test('basic field change and save behaviour', async ({ page }) => {
5047
test('estimate based off storage, pricing, and redundancy', async ({
5148
page,
5249
}) => {
53-
// Set up.
54-
await mockApiSiaCentralExchangeRates({ page })
55-
await login({ page })
56-
5750
// Reset state.
5851
await navigateToConfig({ page })
5952
await setSwitchByLabel(page, 'autoAllowance', true)
@@ -84,10 +77,6 @@ test('estimate based off storage, pricing, and redundancy', async ({
8477
})
8578

8679
test('configure with auto allowance', async ({ page }) => {
87-
// Set up.
88-
await mockApiSiaCentralExchangeRates({ page })
89-
await login({ page })
90-
9180
// Reset state.
9281
await navigateToConfig({ page })
9382
await setSwitchByLabel(page, 'autoAllowance', true)
@@ -108,13 +97,8 @@ test('configure with auto allowance', async ({ page }) => {
10897
})
10998

11099
test('configure allowance manually', async ({ page }) => {
111-
// Set up.
112-
await mockApiSiaCentralExchangeRates({ page })
113-
await login({ page })
114-
115100
// Reset state.
116101
await navigateToConfig({ page })
117-
await configResetAllSettings({ page })
118102
await setSwitchByLabel(page, 'autoAllowance', false)
119103
await setViewMode({ page, state: 'basic' })
120104
await fillTextInputByName(page, 'allowanceMonth', '777')
@@ -135,13 +119,8 @@ test('configure allowance manually', async ({ page }) => {
135119
})
136120

137121
test('system offers recommendations', async ({ page }) => {
138-
// Set up.
139-
await mockApiSiaCentralExchangeRates({ page })
140-
await login({ page })
141-
142122
// Reset state.
143123
await navigateToConfig({ page })
144-
await configResetAllSettings({ page })
145124
await setViewMode({ page, state: 'basic' })
146125
await setSwitchByLabel(page, 'autoAllowance', true)
147126

0 commit comments

Comments
 (0)