diff --git a/app/components/form/fields/TextField.tsx b/app/components/form/fields/TextField.tsx index e6c21798a..e341be08a 100644 --- a/app/components/form/fields/TextField.tsx +++ b/app/components/form/fields/TextField.tsx @@ -56,6 +56,8 @@ export interface TextFieldProps< units?: string validate?: Validate, TFieldValues> control: Control + /** Alters the value of the input during the field's onChange event. */ + transform?: (value: string) => FieldPathValue } export function TextField< @@ -119,6 +121,7 @@ export const TextFieldInner = < tooltipText, required, id: idProp, + transform, ...props }: TextFieldProps & UITextAreaProps) => { const generatedId = useId() @@ -144,6 +147,10 @@ export const TextFieldInner = < // for the calling code despite the actual input value necessarily // being a string. onChange={(e) => { + if (transform) { + onChange(transform(e.target.value)) + return + } if (type === 'number') { if (e.target.value.trim() === '') { onChange(0) diff --git a/app/forms/network-interface-create.tsx b/app/forms/network-interface-create.tsx index edec5d565..a459c9aa8 100644 --- a/app/forms/network-interface-create.tsx +++ b/app/forms/network-interface-create.tsx @@ -80,7 +80,12 @@ export default function CreateNetworkInterfaceForm({ required control={form.control} /> - + (ip.trim() === '' ? undefined : ip)} + /> ) } diff --git a/app/test/e2e/network-interface-create.e2e.ts b/app/test/e2e/network-interface-create.e2e.ts new file mode 100644 index 000000000..21c01ae93 --- /dev/null +++ b/app/test/e2e/network-interface-create.e2e.ts @@ -0,0 +1,78 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { test } from '@playwright/test' + +import { expect, expectRowVisible } from './utils' + +test('can create a NIC with a specified IP address', async ({ page }) => { + // go to an instance’s Network Interfaces page + await page.goto('/projects/mock-project/instances/db1/network-interfaces') + + // stop the instance + await page.getByRole('button', { name: 'Instance actions' }).click() + await page.getByRole('menuitem', { name: 'Stop' }).click() + + // open the add network interface side modal + await page.getByRole('button', { name: 'Add network interface' }).click() + + // fill out the form + await page.getByLabel('Name').fill('nic-1') + await page.getByRole('button', { name: 'VPC' }).click() + await page.getByRole('option', { name: 'mock-vpc' }).click() + await page.getByRole('button', { name: 'Subnet' }).click() + await page.getByRole('option', { name: 'mock-subnet' }).click() + await page.getByLabel('IP Address').fill('1.2.3.4') + + const sidebar = page.getByRole('dialog', { name: 'Add network interface' }) + + // test that the form can be submitted and a new network interface is created + await sidebar.getByRole('button', { name: 'Add network interface' }).click() + await expect(sidebar).toBeHidden() + + await expectRowVisible(page.getByRole('table'), { name: 'nic-1', ip: '1.2.3.4' }) +}) + +test('can create a NIC with a blank IP address', async ({ page }) => { + // go to an instance’s Network Interfaces page + await page.goto('/projects/mock-project/instances/db1/network-interfaces') + + // stop the instance + await page.getByRole('button', { name: 'Instance actions' }).click() + await page.getByRole('menuitem', { name: 'Stop' }).click() + + // open the add network interface side modal + await page.getByRole('button', { name: 'Add network interface' }).click() + + // fill out the form + await page.getByLabel('Name').fill('nic-2') + await page.getByRole('button', { name: 'VPC' }).click() + await page.getByRole('option', { name: 'mock-vpc' }).click() + await page.getByRole('button', { name: 'Subnet' }).click() + await page.getByRole('option', { name: 'mock-subnet' }).click() + + // make sure the IP address field has a non-conforming bit of text in it + await page.getByLabel('IP Address').fill('x') + + // try to submit it + const sidebar = page.getByRole('dialog', { name: 'Add network interface' }) + await sidebar.getByRole('button', { name: 'Add network interface' }).click() + + // it should error out + // todo: improve error message from API + await expect(sidebar.getByText('Unknown server error')).toBeVisible() + + // make sure the IP address field has spaces in it + await page.getByLabel('IP Address').fill(' ') + + // test that the form can be submitted and a new network interface is created + await sidebar.getByRole('button', { name: 'Add network interface' }).click() + await expect(sidebar).toBeHidden() + + // ip address is auto-assigned + await expectRowVisible(page.getByRole('table'), { name: 'nic-2', ip: '123.45.68.8' }) +})