From d9c7ed23d13cdb62bd1bc397dc2871d4bd5b77e9 Mon Sep 17 00:00:00 2001 From: Ian London Date: Thu, 22 Jul 2021 14:41:38 -0400 Subject: [PATCH] feat(labware-creator): use "tube" not "well" for x/y errors (#8150) Closes #8142 allows LC to have dynamic error labels via getLabel --- .../labware-creator/reservoir.spec.js | 8 ++-- .../labware-creator/tipRack.spec.js | 4 +- .../labware-creator/tubesBlock.spec.js | 48 +++++++++---------- .../labware-creator/tubesRack.spec.js | 42 ++++++++-------- .../labware-creator/wellPlate.spec.js | 8 ++-- .../components/HeightGuidingText.tsx | 2 +- .../components/__tests__/FormAlerts.test.tsx | 7 ++- .../__tests__/sections/Grid.test.tsx | 1 + .../__tests__/sections/GridOffset.test.tsx | 1 + .../sections/HandPlacedTipFit.test.tsx | 1 + .../components/alerts/FormAlerts.tsx | 33 ++++++++++--- .../sections/CreateNewDefinition.tsx | 7 ++- .../components/sections/Description.tsx | 7 ++- .../components/sections/Export.tsx | 7 ++- .../components/sections/File.tsx | 7 ++- .../components/sections/Footprint.tsx | 7 ++- .../components/sections/Grid.tsx | 7 ++- .../components/sections/GridOffset.tsx | 7 ++- .../components/sections/HandPlacedTipFit.tsx | 1 + .../components/sections/Height.tsx | 7 ++- .../components/sections/Regularity.tsx | 7 ++- .../components/sections/Volume.tsx | 7 ++- .../sections/WellBottomAndDepth.tsx | 7 ++- .../components/sections/WellShapeAndSides.tsx | 7 ++- .../components/sections/WellSpacing.tsx | 7 ++- labware-library/src/labware-creator/fields.ts | 11 ++++- .../src/labware-creator/labwareFormSchema.ts | 28 ++++++----- 27 files changed, 198 insertions(+), 88 deletions(-) diff --git a/labware-library/cypress/integration/labware-creator/reservoir.spec.js b/labware-library/cypress/integration/labware-creator/reservoir.spec.js index 5472cd07fdc..5aad9c7a81f 100644 --- a/labware-library/cypress/integration/labware-creator/reservoir.spec.js +++ b/labware-library/cypress/integration/labware-creator/reservoir.spec.js @@ -212,9 +212,9 @@ context('Reservoirs', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -226,7 +226,7 @@ context('Reservoirs', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -236,7 +236,7 @@ context('Reservoirs', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) diff --git a/labware-library/cypress/integration/labware-creator/tipRack.spec.js b/labware-library/cypress/integration/labware-creator/tipRack.spec.js index 48abfe95168..2aeafab0337 100644 --- a/labware-library/cypress/integration/labware-creator/tipRack.spec.js +++ b/labware-library/cypress/integration/labware-creator/tipRack.spec.js @@ -59,7 +59,9 @@ describe('Create a Tip Rack', () => { // verify that fit is required cy.get('#HandPlacedTipFit input').first().click() cy.get('#HandPlacedTipFit p').first().click() - cy.get('#HandPlacedTipFit span').contains('Fit is required').should('exist') + cy.get('#HandPlacedTipFit span') + .contains('Fit is a required field') + .should('exist') // verify that loose option is selected cy.get('#HandPlacedTipFit input') diff --git a/labware-library/cypress/integration/labware-creator/tubesBlock.spec.js b/labware-library/cypress/integration/labware-creator/tubesBlock.spec.js index bdf0ba21d18..172042c7a43 100644 --- a/labware-library/cypress/integration/labware-creator/tubesBlock.spec.js +++ b/labware-library/cypress/integration/labware-creator/tubesBlock.spec.js @@ -109,13 +109,13 @@ context('Tubes and Block', () => { cy.get("input[name='wellXDimension']").should('exist') cy.get("input[name='wellYDimension']").should('exist') cy.get("input[name='wellXDimension']").focus().blur() - cy.contains('Well X is a required field').should('exist') + cy.contains('Tube X is a required field').should('exist') cy.get("input[name='wellXDimension']").type('10').blur() - cy.contains('Well X is a required field').should('not.exist') + cy.contains('Tube X is a required field').should('not.exist') cy.get("input[name='wellYDimension']").focus().blur() - cy.contains('Well Y is a required field').should('exist') + cy.contains('Tube Y is a required field').should('exist') cy.get("input[name='wellYDimension']").type('10').blur() - cy.contains('Well Y is a required field').should('not.exist') + cy.contains('Tube Y is a required field').should('not.exist') }) it('tests well bottom shape and depth', () => { @@ -158,9 +158,9 @@ context('Tubes and Block', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -172,7 +172,7 @@ context('Tubes and Block', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -182,7 +182,7 @@ context('Tubes and Block', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) @@ -298,13 +298,13 @@ context('Tubes and Block', () => { cy.get("input[name='wellXDimension']").should('exist') cy.get("input[name='wellYDimension']").should('exist') cy.get("input[name='wellXDimension']").focus().blur() - cy.contains('Well X is a required field').should('exist') + cy.contains('Tube X is a required field').should('exist') cy.get("input[name='wellXDimension']").type('10').blur() - cy.contains('Well X is a required field').should('not.exist') + cy.contains('Tube X is a required field').should('not.exist') cy.get("input[name='wellYDimension']").focus().blur() - cy.contains('Well Y is a required field').should('exist') + cy.contains('Tube Y is a required field').should('exist') cy.get("input[name='wellYDimension']").type('10').blur() - cy.contains('Well Y is a required field').should('not.exist') + cy.contains('Tube Y is a required field').should('not.exist') }) it('tests well bottom shape and depth', () => { @@ -347,9 +347,9 @@ context('Tubes and Block', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -361,7 +361,7 @@ context('Tubes and Block', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -371,7 +371,7 @@ context('Tubes and Block', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) @@ -536,9 +536,9 @@ context('Tubes and Block', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -550,7 +550,7 @@ context('Tubes and Block', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -560,7 +560,7 @@ context('Tubes and Block', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) @@ -721,9 +721,9 @@ context('Tubes and Block', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -735,7 +735,7 @@ context('Tubes and Block', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -745,7 +745,7 @@ context('Tubes and Block', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) diff --git a/labware-library/cypress/integration/labware-creator/tubesRack.spec.js b/labware-library/cypress/integration/labware-creator/tubesRack.spec.js index 9cc8f483133..49d51185c24 100644 --- a/labware-library/cypress/integration/labware-creator/tubesRack.spec.js +++ b/labware-library/cypress/integration/labware-creator/tubesRack.spec.js @@ -97,13 +97,13 @@ context('Tubes and Rack', () => { cy.get("input[name='wellXDimension']").should('exist') cy.get("input[name='wellYDimension']").should('exist') cy.get("input[name='wellXDimension']").focus().blur() - cy.contains('Well X is a required field').should('exist') + cy.contains('Tube X is a required field').should('exist') cy.get("input[name='wellXDimension']").type('10').blur() - cy.contains('Well X is a required field').should('not.exist') + cy.contains('Tube X is a required field').should('not.exist') cy.get("input[name='wellYDimension']").focus().blur() - cy.contains('Well Y is a required field').should('exist') + cy.contains('Tube Y is a required field').should('exist') cy.get("input[name='wellYDimension']").type('10').blur() - cy.contains('Well Y is a required field').should('not.exist') + cy.contains('Tube Y is a required field').should('not.exist') }) it('tests well bottom shape and depth', () => { @@ -146,7 +146,7 @@ context('Tubes and Rack', () => { cy.contains('close').click({ force: true }) // Brand field should not be shown for Opentrons tube rack (aka non-custom) - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') // File info cy.get( @@ -155,7 +155,7 @@ context('Tubes and Rack', () => { cy.get("input[placeholder='opentrons_6_tuberack_10ul']").should('exist') // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -165,7 +165,7 @@ context('Tubes and Rack', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) @@ -271,13 +271,13 @@ context('Tubes and Rack', () => { cy.get("input[name='wellXDimension']").should('exist') cy.get("input[name='wellYDimension']").should('exist') cy.get("input[name='wellXDimension']").focus().blur() - cy.contains('Well X is a required field').should('exist') + cy.contains('Tube X is a required field').should('exist') cy.get("input[name='wellXDimension']").type('10').blur() - cy.contains('Well X is a required field').should('not.exist') + cy.contains('Tube X is a required field').should('not.exist') cy.get("input[name='wellYDimension']").focus().blur() - cy.contains('Well Y is a required field').should('exist') + cy.contains('Tube Y is a required field').should('exist') cy.get("input[name='wellYDimension']").type('10').blur() - cy.contains('Well Y is a required field').should('not.exist') + cy.contains('Tube Y is a required field').should('not.exist') }) it('tests well bottom shape and depth', () => { @@ -320,7 +320,7 @@ context('Tubes and Rack', () => { cy.contains('close').click({ force: true }) // Brand field should not be shown for Opentrons tube rack (aka non-custom) - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') // File info cy.get( @@ -331,7 +331,7 @@ context('Tubes and Rack', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -341,7 +341,7 @@ context('Tubes and Rack', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) @@ -447,13 +447,13 @@ context('Tubes and Rack', () => { cy.get("input[name='wellXDimension']").should('exist') cy.get("input[name='wellYDimension']").should('exist') cy.get("input[name='wellXDimension']").focus().blur() - cy.contains('Well X is a required field').should('exist') + cy.contains('Tube X is a required field').should('exist') cy.get("input[name='wellXDimension']").type('10').blur() - cy.contains('Well X is a required field').should('not.exist') + cy.contains('Tube X is a required field').should('not.exist') cy.get("input[name='wellYDimension']").focus().blur() - cy.contains('Well Y is a required field').should('exist') + cy.contains('Tube Y is a required field').should('exist') cy.get("input[name='wellYDimension']").type('10').blur() - cy.contains('Well Y is a required field').should('not.exist') + cy.contains('Tube Y is a required field').should('not.exist') }) it('tests well bottom shape and depth', () => { @@ -496,7 +496,7 @@ context('Tubes and Rack', () => { cy.contains('close').click({ force: true }) // Brand field should not be shown for Opentrons tube rack (aka non-custom) - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') // File info cy.get( @@ -507,7 +507,7 @@ context('Tubes and Rack', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -517,7 +517,7 @@ context('Tubes and Rack', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) diff --git a/labware-library/cypress/integration/labware-creator/wellPlate.spec.js b/labware-library/cypress/integration/labware-creator/wellPlate.spec.js index 60fa2895648..2ab735e7683 100644 --- a/labware-library/cypress/integration/labware-creator/wellPlate.spec.js +++ b/labware-library/cypress/integration/labware-creator/wellPlate.spec.js @@ -231,9 +231,9 @@ context('Well Plates', () => { cy.contains('close').click({ force: true }) // Brand info - cy.contains('Brand is required').should('exist') + cy.contains('Brand is a required field').should('exist') cy.get("input[name='brand']").type('TestPro') - cy.contains('Brand is required').should('not.exist') + cy.contains('Brand is a required field').should('not.exist') cy.get("input[name='brandId']").type('001') // File info @@ -245,7 +245,7 @@ context('Well Plates', () => { ) // Test pipette - cy.contains('Test Pipette is required').should('exist') + cy.contains('Test Pipette is a required field').should('exist') // TODO(IL, 2021-05-15): give Dropdown component semantic selectors for E2E cy.get('label') .contains('Test Pipette') @@ -255,7 +255,7 @@ context('Well Plates', () => { cy.get('*[class^="Dropdown__option_label"]') .contains(/P10.*Single-Channel.*GEN1/) .click() - cy.contains('Test Pipette is required').should('not.exist') + cy.contains('Test Pipette is a required field').should('not.exist') // All fields present cy.get('button[class*="_export_button_"]').click({ force: true }) diff --git a/labware-library/src/labware-creator/components/HeightGuidingText.tsx b/labware-library/src/labware-creator/components/HeightGuidingText.tsx index 6d748ae81f5..77040f5ba3b 100644 --- a/labware-library/src/labware-creator/components/HeightGuidingText.tsx +++ b/labware-library/src/labware-creator/components/HeightGuidingText.tsx @@ -18,7 +18,7 @@ export const HeightGuidingText = (props: {

Reference{' '} from the top of the tube to bottom of the rack.{' '} - Include any well lip. Exclude any cover or cap. + Include any tube lip. Exclude any cover or cap.

{footer} diff --git a/labware-library/src/labware-creator/components/__tests__/FormAlerts.test.tsx b/labware-library/src/labware-creator/components/__tests__/FormAlerts.test.tsx index aa9794df635..8dd4250c777 100644 --- a/labware-library/src/labware-creator/components/__tests__/FormAlerts.test.tsx +++ b/labware-library/src/labware-creator/components/__tests__/FormAlerts.test.tsx @@ -29,6 +29,7 @@ describe('FormAlerts', () => { .mockReturnValue(false) const props: FormAlertProps = { + values: { labwareType: 'wellPlate', tubeRackInsertLoadName: null } as any, fieldList: ['labwareType', 'tubeRackInsertLoadName'], touched: { labwareType: true, tubeRackInsertLoadName: true }, errors: { @@ -40,7 +41,7 @@ describe('FormAlerts', () => { const warning = container.querySelector('[class="alert warning"]') expect(warning?.textContent).toBe('some warning') }) - it('should render an incompatable labware error when the labware is not compatible with labware creator', () => { + it('should render an incompatible labware error when the labware is not compatible with labware creator', () => { when(getIsHiddenMock) .calledWith('labwareType', {} as any) .mockReturnValue(false) @@ -50,6 +51,7 @@ describe('FormAlerts', () => { .mockReturnValue(false) const props: FormAlertProps = { + values: { labwareType: 'wellPlate', tubeRackInsertLoadName: null } as any, fieldList: ['labwareType', 'tubeRackInsertLoadName'], touched: { labwareType: true, tubeRackInsertLoadName: true }, errors: { @@ -73,6 +75,7 @@ describe('FormAlerts', () => { .mockReturnValue(false) const props: FormAlertProps = { + values: { labwareType: 'wellPlate', tubeRackInsertLoadName: null } as any, fieldList: ['labwareType', 'tubeRackInsertLoadName'], touched: { labwareType: true, tubeRackInsertLoadName: true }, errors: { @@ -96,6 +99,7 @@ describe('FormAlerts', () => { .mockReturnValue(false) const props: FormAlertProps = { + values: { labwareType: 'wellPlate', tubeRackInsertLoadName: null } as any, fieldList: ['labwareType', 'tubeRackInsertLoadName'], touched: { labwareType: true, tubeRackInsertLoadName: true }, errors: { @@ -119,6 +123,7 @@ describe('FormAlerts', () => { .mockReturnValue(false) const props: FormAlertProps = { + values: { labwareType: 'wellPlate', tubeRackInsertLoadName: null } as any, fieldList: ['labwareType', 'tubeRackInsertLoadName'], touched: { labwareType: true, tubeRackInsertLoadName: true }, errors: { diff --git a/labware-library/src/labware-creator/components/__tests__/sections/Grid.test.tsx b/labware-library/src/labware-creator/components/__tests__/sections/Grid.test.tsx index 8914a1bd598..6082ce45c39 100644 --- a/labware-library/src/labware-creator/components/__tests__/sections/Grid.test.tsx +++ b/labware-library/src/labware-creator/components/__tests__/sections/Grid.test.tsx @@ -76,6 +76,7 @@ describe('Grid', () => { FormAlertsMock.mockImplementation(args => { if ( isEqual(args, { + values: formikConfig.initialValues, touched: {}, errors: {}, fieldList: [ diff --git a/labware-library/src/labware-creator/components/__tests__/sections/GridOffset.test.tsx b/labware-library/src/labware-creator/components/__tests__/sections/GridOffset.test.tsx index e6dee3bd1ef..bb60b0309aa 100644 --- a/labware-library/src/labware-creator/components/__tests__/sections/GridOffset.test.tsx +++ b/labware-library/src/labware-creator/components/__tests__/sections/GridOffset.test.tsx @@ -47,6 +47,7 @@ describe('GridOffset', () => { FormAlertsMock.mockImplementation(args => { if ( isEqual(args, { + values: formikConfig.initialValues, touched: {}, errors: {}, fieldList: ['gridOffsetX', 'gridOffsetY'], diff --git a/labware-library/src/labware-creator/components/__tests__/sections/HandPlacedTipFit.test.tsx b/labware-library/src/labware-creator/components/__tests__/sections/HandPlacedTipFit.test.tsx index 1426632a7d0..9ad133fd71f 100644 --- a/labware-library/src/labware-creator/components/__tests__/sections/HandPlacedTipFit.test.tsx +++ b/labware-library/src/labware-creator/components/__tests__/sections/HandPlacedTipFit.test.tsx @@ -48,6 +48,7 @@ describe('HandPlacedTipFit', () => { FormAlertsMock.mockImplementation(args => { if ( isEqual(args, { + values: formikConfig.initialValues, touched: {}, errors: {}, fieldList: ['handPlacedTipFit'], diff --git a/labware-library/src/labware-creator/components/alerts/FormAlerts.tsx b/labware-library/src/labware-creator/components/alerts/FormAlerts.tsx index 123fba70464..4a421564a49 100644 --- a/labware-library/src/labware-creator/components/alerts/FormAlerts.tsx +++ b/labware-library/src/labware-creator/components/alerts/FormAlerts.tsx @@ -1,8 +1,9 @@ import * as React from 'react' -import compact from 'lodash/compact' -import uniq from 'lodash/uniq' +import toPairs from 'lodash/toPairs' +import pick from 'lodash/pick' import { AlertItem } from '@opentrons/components' import { + getLabel, LabwareFields, IRREGULAR_LABWARE_ERROR, LABWARE_TOO_SMALL_ERROR, @@ -10,11 +11,14 @@ import { LOOSE_TIP_FIT_ERROR, LINK_CUSTOM_LABWARE_FORM, LINK_REQUEST_ADAPTER_FORM, + MUST_BE_A_NUMBER_ERROR, + REQUIRED_FIELD_ERROR, } from '../../fields' import { LinkOut } from '../LinkOut' import type { FormikTouched, FormikErrors } from 'formik' export interface Props { + values: LabwareFields fieldList: Array touched: FormikTouched errors: FormikErrors @@ -72,16 +76,16 @@ export const LooseTipFitAlert = (): JSX.Element => ( ) export const FormAlerts = (props: Props): JSX.Element | null => { - const { fieldList, touched, errors } = props + const { fieldList, touched, errors, values } = props const dirtyFieldNames = fieldList.filter(name => touched[name]) - const allErrors: string[] = uniq( - compact(dirtyFieldNames.map(name => errors[name])) - ) + const allErrors = toPairs(pick(errors, dirtyFieldNames)) as Array< + [keyof LabwareFields, string] + > return ( <> - {allErrors.map(error => { + {allErrors.map(([name, error]) => { if (error === LOOSE_TIP_FIT_ERROR) { return } @@ -94,7 +98,22 @@ export const FormAlerts = (props: Props): JSX.Element | null => { if (error === LABWARE_TOO_LARGE_ERROR) { return } + + // sometimes fields have dynamic labels, and the "__ is a required field" error + // should use the dynamic label from getLabel. + if (error === MUST_BE_A_NUMBER_ERROR) { + const message = `${getLabel(name, values)} must be a number` + return + } + if (error === REQUIRED_FIELD_ERROR) { + const message = `${getLabel(name, values)} is a required field` + return + } + + // TODO(IL, 2021-07-22): is there actually any cases + // where the error could be falsey here? if (error) return + return null })} ) diff --git a/labware-library/src/labware-creator/components/sections/CreateNewDefinition.tsx b/labware-library/src/labware-creator/components/sections/CreateNewDefinition.tsx index 75299f3275c..fa7f416d9f1 100644 --- a/labware-library/src/labware-creator/components/sections/CreateNewDefinition.tsx +++ b/labware-library/src/labware-creator/components/sections/CreateNewDefinition.tsx @@ -82,7 +82,12 @@ export const CreateNewDefinition = (props: Props): JSX.Element | null => { })} > <> - + {content} diff --git a/labware-library/src/labware-creator/components/sections/Description.tsx b/labware-library/src/labware-creator/components/sections/Description.tsx index 85bda9c2db2..3b5c91948b0 100644 --- a/labware-library/src/labware-creator/components/sections/Description.tsx +++ b/labware-library/src/labware-creator/components/sections/Description.tsx @@ -62,7 +62,12 @@ export const Description = (): JSX.Element | null => { return ( - + ) diff --git a/labware-library/src/labware-creator/components/sections/Export.tsx b/labware-library/src/labware-creator/components/sections/Export.tsx index 9e81892b1d1..b44cd463e71 100644 --- a/labware-library/src/labware-creator/components/sections/Export.tsx +++ b/labware-library/src/labware-creator/components/sections/Export.tsx @@ -47,7 +47,12 @@ export const Export = (props: ExportProps): JSX.Element | null => { return ( - +
diff --git a/labware-library/src/labware-creator/components/sections/File.tsx b/labware-library/src/labware-creator/components/sections/File.tsx index 612301bce6d..dc7e32a0e00 100644 --- a/labware-library/src/labware-creator/components/sections/File.tsx +++ b/labware-library/src/labware-creator/components/sections/File.tsx @@ -37,7 +37,12 @@ export const File = (): JSX.Element | null => { return ( - + ) diff --git a/labware-library/src/labware-creator/components/sections/Footprint.tsx b/labware-library/src/labware-creator/components/sections/Footprint.tsx index 062d3629f08..8aeb27ac207 100644 --- a/labware-library/src/labware-creator/components/sections/Footprint.tsx +++ b/labware-library/src/labware-creator/components/sections/Footprint.tsx @@ -73,7 +73,12 @@ export const Footprint = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/Grid.tsx b/labware-library/src/labware-creator/components/sections/Grid.tsx index 53d0695661b..ded2c120fd9 100644 --- a/labware-library/src/labware-creator/components/sections/Grid.tsx +++ b/labware-library/src/labware-creator/components/sections/Grid.tsx @@ -56,7 +56,12 @@ export const Grid = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/GridOffset.tsx b/labware-library/src/labware-creator/components/sections/GridOffset.tsx index 53b7dec3acc..87e67567418 100644 --- a/labware-library/src/labware-creator/components/sections/GridOffset.tsx +++ b/labware-library/src/labware-creator/components/sections/GridOffset.tsx @@ -84,7 +84,12 @@ export const GridOffset = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/HandPlacedTipFit.tsx b/labware-library/src/labware-creator/components/sections/HandPlacedTipFit.tsx index af24f61fc7d..5006df6d28b 100644 --- a/labware-library/src/labware-creator/components/sections/HandPlacedTipFit.tsx +++ b/labware-library/src/labware-creator/components/sections/HandPlacedTipFit.tsx @@ -39,6 +39,7 @@ export const HandPlacedTipFit = (): JSX.Element | null => { <> {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/Regularity.tsx b/labware-library/src/labware-creator/components/sections/Regularity.tsx index 8939daff733..f2ca721ebb2 100644 --- a/labware-library/src/labware-creator/components/sections/Regularity.tsx +++ b/labware-library/src/labware-creator/components/sections/Regularity.tsx @@ -20,7 +20,12 @@ export const Regularity = (): JSX.Element | null => { return ( <> - +
diff --git a/labware-library/src/labware-creator/components/sections/Volume.tsx b/labware-library/src/labware-creator/components/sections/Volume.tsx index e9095e1ce84..21d00b22867 100644 --- a/labware-library/src/labware-creator/components/sections/Volume.tsx +++ b/labware-library/src/labware-creator/components/sections/Volume.tsx @@ -42,7 +42,12 @@ export const Volume = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/WellBottomAndDepth.tsx b/labware-library/src/labware-creator/components/sections/WellBottomAndDepth.tsx index ba82b0d8477..5e65482b8b1 100644 --- a/labware-library/src/labware-creator/components/sections/WellBottomAndDepth.tsx +++ b/labware-library/src/labware-creator/components/sections/WellBottomAndDepth.tsx @@ -83,7 +83,12 @@ export const WellBottomAndDepth = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/WellShapeAndSides.tsx b/labware-library/src/labware-creator/components/sections/WellShapeAndSides.tsx index 79f665662c9..ae7d2d2b3d0 100644 --- a/labware-library/src/labware-creator/components/sections/WellShapeAndSides.tsx +++ b/labware-library/src/labware-creator/components/sections/WellShapeAndSides.tsx @@ -115,7 +115,12 @@ export const WellShapeAndSides = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/components/sections/WellSpacing.tsx b/labware-library/src/labware-creator/components/sections/WellSpacing.tsx index e6dc498bef7..3dc2282fc8f 100644 --- a/labware-library/src/labware-creator/components/sections/WellSpacing.tsx +++ b/labware-library/src/labware-creator/components/sections/WellSpacing.tsx @@ -82,7 +82,12 @@ export const WellSpacing = (): JSX.Element | null => {
<> - + diff --git a/labware-library/src/labware-creator/fields.ts b/labware-library/src/labware-creator/fields.ts index b7af7b22cbd..3b84a4524cd 100644 --- a/labware-library/src/labware-creator/fields.ts +++ b/labware-library/src/labware-creator/fields.ts @@ -3,7 +3,7 @@ import type { LabwareDefinition2, WellBottomShape, } from '@opentrons/shared-data' -import { getLabwareName } from './utils' +import { displayAsTube, getLabwareName } from './utils' export const MAX_X_DIMENSION = 129 export const MIN_X_DIMENSION = 127 @@ -23,6 +23,9 @@ export const DISPLAY_VOLUME_UNITS = 'µL' // magic string for all validation errors that direct user away to the labware request form export const IRREGULAR_LABWARE_ERROR = 'IRREGULAR_LABWARE_ERROR' +export const REQUIRED_FIELD_ERROR = 'REQUIRED_FIELD_ERROR' +export const MUST_BE_A_NUMBER_ERROR = 'MUST_BE_A_NUMBER_ERROR' + export const LOOSE_TIP_FIT_ERROR = 'LOOSE_TIP_FIT_ERROR' export const LABWARE_TOO_SMALL_ERROR = 'LABWARE_TOO_SMALL_ERROR' @@ -459,6 +462,12 @@ export const getLabel = ( if (name === 'wellShape') { return `${capitalize(getLabwareName(values, false))} shape` } + if (name === 'wellXDimension' && displayAsTube(values)) { + return 'Tube X' + } + if (name === 'wellYDimension' && displayAsTube(values)) { + return 'Tube Y' + } return LABELS[name] } diff --git a/labware-library/src/labware-creator/labwareFormSchema.ts b/labware-library/src/labware-creator/labwareFormSchema.ts index 131f8ef21ff..08cb7859208 100644 --- a/labware-library/src/labware-creator/labwareFormSchema.ts +++ b/labware-library/src/labware-creator/labwareFormSchema.ts @@ -16,18 +16,24 @@ import { MAX_Z_DIMENSION, MIN_X_DIMENSION, MIN_Y_DIMENSION, + REQUIRED_FIELD_ERROR, + MUST_BE_A_NUMBER_ERROR, LabwareFields, } from './fields' import type { ProcessedLabwareFields } from './fields' +// global overrides for Yup's default error messages. +Yup.setLocale({ + mixed: { + required: REQUIRED_FIELD_ERROR, + }, +}) + const ALL_DISPLAY_NAMES = new Set( getAllDisplayNames().map(n => n.toLowerCase().trim()) ) - -const REQUIRED_FIELD = '${label} is required' // eslint-disable-line no-template-curly-in-string const requiredString = (label: string): Yup.StringSchema => - Yup.string().label(label).typeError(REQUIRED_FIELD).required() -const MUST_BE_A_NUMBER = '${label} must be a number' // eslint-disable-line no-template-curly-in-string + Yup.string().label(label).typeError(REQUIRED_FIELD_ERROR).required() // put this in a transform to make Yup to show "this field is required" // instead of "must be a number". See https://github.com/jquense/yup/issues/971 @@ -38,7 +44,7 @@ const requiredPositiveNumber = (label: string): Yup.NumberSchema => Yup.number() .label(label) .transform(nanToUndefined) - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .moreThan(0) .required() @@ -46,7 +52,7 @@ const requiredPositiveInteger = (label: string): Yup.NumberSchema => Yup.number() .transform(nanToUndefined) .label(label) - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .moreThan(0) .integer() .required() @@ -55,7 +61,7 @@ const unsupportedLabwareIfFalse = (label: string): Yup.BooleanSchema => Yup.boolean() .default(false) .label(label) - .typeError(REQUIRED_FIELD) + .typeError(REQUIRED_FIELD_ERROR) .oneOf([true], IRREGULAR_LABWARE_ERROR) .required() @@ -118,7 +124,7 @@ export const labwareFormSchemaBaseObject = Yup.object({ footprintXDimension: Yup.number() .transform(nanToUndefined) .label(LABELS.footprintXDimension) - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .min(MIN_X_DIMENSION, LABWARE_TOO_SMALL_ERROR) .max(MAX_X_DIMENSION, LABWARE_TOO_LARGE_ERROR) .nullable() @@ -126,7 +132,7 @@ export const labwareFormSchemaBaseObject = Yup.object({ footprintYDimension: Yup.number() .transform(nanToUndefined) .label(LABELS.footprintYDimension) - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .min(MIN_Y_DIMENSION, LABWARE_TOO_SMALL_ERROR) .max(MAX_Y_DIMENSION, LABWARE_TOO_LARGE_ERROR) .nullable() @@ -177,7 +183,7 @@ export const labwareFormSchemaBaseObject = Yup.object({ then: Yup.number() .transform(nanToUndefined) .label('Length') - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .moreThan(0) .max( Yup.ref('labwareZDimension'), @@ -187,7 +193,7 @@ export const labwareFormSchemaBaseObject = Yup.object({ otherwise: Yup.number() .transform(nanToUndefined) .label(LABELS.wellDepth) - .typeError(MUST_BE_A_NUMBER) + .typeError(MUST_BE_A_NUMBER_ERROR) .moreThan(0) .max( Yup.ref('labwareZDimension'),