diff --git a/e2e/availabilities.spec.ts b/e2e/availabilities.spec.ts index c64a67d..6203df7 100644 --- a/e2e/availabilities.spec.ts +++ b/e2e/availabilities.spec.ts @@ -1,11 +1,16 @@ import test, { expect } from "@playwright/test"; +import { Bytes } from "../src/utils/bytes" +import { GB } from "../src/utils/constants" test('create an availability', async ({ page }) => { await page.goto('/dashboard/availabilities'); await page.waitForTimeout(500); await page.locator('.availability-edit button').first().click(); await page.getByLabel('Total size').click(); - await page.getByLabel('Total size').fill('0.50'); + + const value = (Math.random() * 0.5); + + await page.getByLabel('Total size').fill(value.toFixed(2)); await page.getByLabel('Duration').click(); await page.getByLabel('Duration').fill('30'); await page.getByLabel('Min price').click(); @@ -20,7 +25,7 @@ test('create an availability', async ({ page }) => { await page.getByRole('button', { name: 'Next' }).click(); await expect(page.getByText('Success', { exact: true })).toBeVisible(); await page.getByRole('button', { name: 'Finish' }).click(); - await expect(page.getByText('512.0 MB allocated for the').first()).toBeVisible(); + await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible(); }) test('availability navigation buttons', async ({ page }) => { @@ -54,3 +59,63 @@ test('availability navigation buttons', async ({ page }) => { await page.getByRole('button', { name: 'Finish' }).click(); await expect(page.locator('.modal--open')).not.toBeVisible(); }) + +test('create an availability with changing the duration to months', async ({ page }) => { + await page.goto('/dashboard/availabilities'); + await page.waitForTimeout(500); + await page.locator('.availability-edit button').first().click(); + await page.getByLabel('Total size').click(); + + await page.getByLabel('Total size').fill("0.1"); + await page.getByLabel('Duration').click(); + await page.getByLabel('Duration').fill("3"); + await page.getByRole('combobox').nth(1).selectOption('months'); + + await page.getByLabel('Min price').click(); + await page.getByLabel('Min price').fill('5'); + await page.getByLabel('Max collateral').click(); + await page.getByLabel('Max collateral').fill('30'); + await page.getByLabel('Min price').fill('5'); + await page.getByLabel('Nickname').click(); + await page.getByLabel('Nickname').fill('test'); + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page.getByText('Confirm your new sale')).toBeVisible(); + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page.getByText('Success', { exact: true })).toBeVisible(); + await page.getByRole('button', { name: 'Finish' }).click(); + await expect(page.getByText("3 months").first()).toBeVisible(); +}) + + +test('create an availability after checking max size and invalid input', async ({ page }) => { + await page.goto('/dashboard/availabilities'); + await page.waitForTimeout(500); + await page.locator('.availability-edit button').first().click(); + await page.getByLabel('Total size').click(); + + + await page.getByLabel('Total size').fill("9999"); + await expect(page.getByLabel('Total size')).toHaveAttribute("aria-invalid"); + + await page.getByText("Use max size").click() + await expect(page.getByLabel('Total size')).not.toHaveAttribute("aria-invalid"); + + const value = (Math.random() * 0.5); + await page.getByLabel('Total size').fill(value.toFixed(1)); + + await page.getByLabel('Duration').click(); + await page.getByLabel('Duration').fill('30'); + await page.getByLabel('Min price').click(); + await page.getByLabel('Min price').fill('5'); + await page.getByLabel('Max collateral').click(); + await page.getByLabel('Max collateral').fill('30'); + await page.getByLabel('Min price').fill('5'); + await page.getByLabel('Nickname').click(); + await page.getByLabel('Nickname').fill('test'); + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page.getByText('Confirm your new sale')).toBeVisible(); + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page.getByText('Success', { exact: true })).toBeVisible(); + await page.getByRole('button', { name: 'Finish' }).click(); + await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible(); +}) \ No newline at end of file diff --git a/src/components/Availability/AvailabilityEdit.tsx b/src/components/Availability/AvailabilityEdit.tsx index c6780b1..260bf73 100644 --- a/src/components/Availability/AvailabilityEdit.tsx +++ b/src/components/Availability/AvailabilityEdit.tsx @@ -28,8 +28,8 @@ type Props = { const CONFIRM_STATE = 2; const defaultAvailabilityData: AvailabilityState = { - totalSize: 0.5 * GB, - duration: Times.value("days"), + totalSize: 0.5, + duration: 1, minPrice: 0, maxCollateral: 0, totalSizeUnit: "gb", @@ -52,7 +52,7 @@ export function AvailabilityEdit({ useEffect(() => { Promise.all([ WebStorage.get("availability-step"), - WebStorage.get("availability"), + WebStorage.get("availability-1"), ]).then(([s, a]) => { if (s) { dispatch({ @@ -67,26 +67,6 @@ export function AvailabilityEdit({ }); }, [dispatch]); - // We use a custom event to not re render the sunburst component - // useEffect(() => { - // const onAvailabilityIdChange = (e: Event) => { - // const custom = e as CustomEvent; - // setAvailabilityId(custom.detail); - // }; - - // document.addEventListener( - // "codexavailabilityid", - // onAvailabilityIdChange, - // false - // ); - - // return () => - // document.removeEventListener( - // "codexavailabilityid", - // onAvailabilityIdChange - // ); - // }, []); - const components = [ AvailabilityForm, AvailabilityConfirm, @@ -157,7 +137,7 @@ export function AvailabilityEdit({ editAvailabilityValue.current = a.totalSize; WebStorage.set("availability-step", 0); - WebStorage.set("availability", a); + WebStorage.set("availability-1", a); const unit = Times.unit(a.duration); diff --git a/src/components/Availability/AvailabilityForm.tsx b/src/components/Availability/AvailabilityForm.tsx index f8b1a7b..fcf42a3 100644 --- a/src/components/Availability/AvailabilityForm.tsx +++ b/src/components/Availability/AvailabilityForm.tsx @@ -11,7 +11,6 @@ import NodesIcon from "../../assets/icons/nodes.svg?react"; import InfoIcon from "../../assets/icons/info.svg?react"; import { attributes } from "../../utils/attributes"; import { AvailabilityUtils } from "./availability.utils"; -import { Times } from "../../utils/times"; export function AvailabilityForm({ dispatch, @@ -39,27 +38,24 @@ export function AvailabilityForm({ const element = e.currentTarget; onAvailabilityChange({ - totalSize: 0, totalSizeUnit: element.value as "tb" | "gb", }); }; const onDurationChange = async (e: ChangeEvent) => { const element = e.currentTarget; - const unitValue = Times.value(availability.durationUnit); onAvailabilityChange({ - duration: parseInt(element.value) * unitValue, + duration: parseInt(element.value), }); }; const onDurationUnitChange = async (e: ChangeEvent) => { const element = e.currentTarget; const unit = element.value as "hours" | "days" | "months"; - const unitValue = Times.value(unit); onAvailabilityChange({ - duration: unitValue, + duration: availability.duration, durationUnit: unit, }); }; @@ -67,10 +63,9 @@ export function AvailabilityForm({ const onAvailablityChange = async (e: ChangeEvent) => { const element = e.currentTarget; const v = element.value; - const unit = AvailabilityUtils.unitValue(availability.totalSizeUnit); onAvailabilityChange({ - totalSize: parseFloat(v) * unit, + totalSize: parseFloat(v), }); }; @@ -87,7 +82,12 @@ export function AvailabilityForm({ const available = AvailabilityUtils.maxValue(space); onAvailabilityChange({ - totalSize: available, + totalSize: + Math.floor( + ((available - 1) / + AvailabilityUtils.unitValue(availability.totalSizeUnit)) * + 10 + ) / 10, }); }; @@ -96,20 +96,17 @@ export function AvailabilityForm({ available += editAvailabilityValue; } - const isValid = - availability.totalSize > 0 && available >= availability.totalSize; + const totalSizeInBytes = + availability.totalSize * + AvailabilityUtils.unitValue(availability.totalSizeUnit); + + const isValid = totalSizeInBytes > 0 && available >= totalSizeInBytes; const helper = isValid ? "Total size of sale's storage in bytes" : "The total size cannot exceed the space available."; - const value = AvailabilityUtils.toUnit( - availability.totalSize, - availability.totalSizeUnit - ).toFixed(2); - - const unitValue = Times.value(availability.durationUnit); - const duration = availability.duration / unitValue; + const duration = availability.duration; return (
@@ -143,13 +140,12 @@ export function AvailabilityForm({ name="totalSize" type="number" label="Total size" - min={0.01} isInvalid={!isValid} max={available.toFixed(2)} onChange={onAvailablityChange} onGroupChange={onTotalSizeUnitChange} - step={"0.01"} - value={value} + value={availability.totalSize.toString()} + min={"0"} group={[ ["gb", "GB"], // ["tb", "TB"], diff --git a/src/components/Availability/availability.utils.ts b/src/components/Availability/availability.utils.ts index e73b1ee..2e3ac42 100644 --- a/src/components/Availability/availability.utils.ts +++ b/src/components/Availability/availability.utils.ts @@ -39,7 +39,8 @@ export const AvailabilityUtils = { return bytes / this.unitValue(unit || "gb") }, maxValue(space: CodexNodeSpace) { - return space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes + // Remove 1 byte to allow to create an availability with the max space possible + return space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes - 1 }, unitValue(unit: "gb" | "tb") { return unit === "tb" ? TB : GB diff --git a/src/components/Availability/useAvailabilityMutation.ts b/src/components/Availability/useAvailabilityMutation.ts index 64a8838..b79b2aa 100644 --- a/src/components/Availability/useAvailabilityMutation.ts +++ b/src/components/Availability/useAvailabilityMutation.ts @@ -9,6 +9,8 @@ import { } from "@codex-storage/marketplace-ui-components"; import { CodexSdk } from "../../sdk/codex"; import { CodexAvailabilityCreateResponse } from "@codex-storage/sdk-js"; +import { Times } from "../../utils/times"; +import { AvailabilityUtils } from "./availability.utils"; export function useAvailabilityMutation( @@ -42,15 +44,15 @@ export function useAvailabilityMutation( return fn({ ...input, - duration, - totalSize: Math.trunc(totalSize), + duration: Times.value(durationUnit) * duration, + totalSize: Math.trunc(totalSize * AvailabilityUtils.unitValue(totalSizeUnit)), }); }, onSuccess: (res, body) => { queryClient.invalidateQueries({ queryKey: ["availabilities"] }); queryClient.invalidateQueries({ queryKey: ["space"] }); - WebStorage.delete("availability"); + WebStorage.delete("availability-1"); WebStorage.delete("availability-step"); if (typeof res === "object" && body.name) {