diff --git a/docs/source/dev/tests.md b/docs/source/dev/tests.md index 5a2d05d4e..9aa660dc4 100644 --- a/docs/source/dev/tests.md +++ b/docs/source/dev/tests.md @@ -58,6 +58,8 @@ pnpm --prefix ui build CYPRESS_BASE_URL="http://127.0.0.1:8081" pnpm --prefix ui e2e ``` +> Currently, WebSockets don't work when running Cypress in the CI. In order to replicate the CI environement more accurately, WebSockets can be disabled locally via environment variable `VITE_DISABLE_WEBSOCKETS`. For instance, you can start the development server with sockets disabled with: `VITE_DISABLE_WEBSOCKETS=true pnpm --prefix ui start`. + ### Best Practices The best use for development would be to test the components you changed first individually by using the cypress app. This allows for better error tracking and time efficiency as it prevents you from running tests on components that are not effected by your changes multiple times during development. diff --git a/ui/cypress/e2e/sampleControls.cy.js b/ui/cypress/e2e/sampleControls.cy.js index bdea27543..db61a8faa 100644 --- a/ui/cypress/e2e/sampleControls.cy.js +++ b/ui/cypress/e2e/sampleControls.cy.js @@ -24,7 +24,7 @@ describe('3-click centring', () => { cy.findByRole('button', { name: '3-click centring' }).click(); cy.findByText(/Clicks left: 3/u, { hidden: true }).should('exist'); - cy.get('.form-control[name="diffractometer.phi"]') + cy.findByTestId('MotorInput_value_diffractometer.phi') .invoke('val') .then((initialValue) => { cy.get('.canvas-container').click(); @@ -35,7 +35,7 @@ describe('3-click centring', () => { // Reload to see new omega value (since WebSockets don't work on CI) cy.reload(); - cy.get('.form-control[name="diffractometer.phi"]') + cy.findByTestId('MotorInput_value_diffractometer.phi') .invoke('val') .should('equal', (Number.parseFloat(initialValue) + 90).toFixed(2)); }); diff --git a/ui/cypress/support.js b/ui/cypress/support.js index 35e328e86..408f0f55f 100644 --- a/ui/cypress/support.js +++ b/ui/cypress/support.js @@ -22,6 +22,14 @@ Cypress.Commands.add('takeControl', () => { cy.findByRole('button', { name: 'Continue' }).click(); cy.findByRole('link', { name: /Remote/u, hidden: true }).click(); cy.findByRole('button', { name: 'Take control' }).click(); + + // Confirmation dialog appears only if sockets are enabled + // eslint-disable-next-line promise/no-nesting + cy.get('body').then(($body) => { + if ($body.text().includes('Hide')) { + cy.findByRole('button', { name: 'Hide' }).click(); + } + }); } }); }); diff --git a/ui/package.json b/ui/package.json index 45c6c39ab..35c19b103 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,7 +16,7 @@ "prettier": "prettier . --cache --check", "eslint": "eslint \"**/*.{js,jsx}\"", "e2e": "cypress run --e2e --browser firefox", - "cypress": "VITE_DISABLE_WEBSOCKETS=true cypress open --e2e --browser firefox" + "cypress": "cypress open --e2e --browser firefox" }, "dependencies": { "@fortawesome/fontawesome-free": "^5.15.4", diff --git a/ui/src/components/MotorInput/MotorInput.jsx b/ui/src/components/MotorInput/MotorInput.jsx index 66624cfb5..125a1a1dd 100644 --- a/ui/src/components/MotorInput/MotorInput.jsx +++ b/ui/src/components/MotorInput/MotorInput.jsx @@ -1,80 +1,66 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -/* eslint-disable react/jsx-handler-names */ -import React from 'react'; + +import React, { useEffect, useState } from 'react'; import { Button, Form } from 'react-bootstrap'; import { HW_STATE } from '../../constants'; import styles from './MotorInput.module.css'; import './motor.css'; -// eslint-disable-next-line react/no-unsafe -export default class MotorInput extends React.Component { - constructor(props) { - super(props); +function MotorInput(props) { + const { + label, + value, + motorName, + step, + state, + suffix, + decimalPoints, + disabled, + save, + stop, + saveStep, + } = props; - this.state = { edited: false }; + const [inputValue, setInputValue] = useState(value.toFixed(decimalPoints)); + const [isEdited, setEdited] = useState(false); - this.handleKey = this.handleKey.bind(this); - this.stopMotor = this.stopMotor.bind(this, props.motorName); - this.stepIncrement = this.stepChange.bind(this, props.motorName, 1); - this.stepDecrement = this.stepChange.bind(this, props.motorName, -1); - } + useEffect(() => { + setInputValue(value.toFixed(decimalPoints)); + setEdited(false); + }, [value, decimalPoints]); - /* eslint-enable react/no-set-state */ - UNSAFE_componentWillReceiveProps(nextProps) { - if (nextProps.value !== this.props.value) { - this.motorValue.value = nextProps.value.toFixed(this.props.decimalPoints); - this.motorValue.defaultValue = nextProps.value.toFixed( - this.props.decimalPoints, - ); - this.setState({ edited: false }); + function handleKey(evt) { + switch (evt.key) { + case 'ArrowUp': { + evt.preventDefault(); + save(motorName, value + step); + break; + } + case 'ArrowDown': { + evt.preventDefault(); + save(motorName, value - step); + break; + } + default: } } - handleKey(e) { - e.preventDefault(); - e.stopPropagation(); + function handleSubmit(evt) { + evt.preventDefault(); - this.setState({ edited: true }); + const newValue = Number.parseFloat(inputValue); - if ( - [13, 38, 40].includes(e.keyCode) && - this.props.state === HW_STATE.READY - ) { - this.setState({ edited: false }); - this.props.save(e.target.name, e.target.valueAsNumber); - this.motorValue.value = this.props.value.toFixed( - this.props.decimalPoints, - ); - } else if (this.props.state === HW_STATE.BUSY) { - this.setState({ edited: false }); - this.motorValue.value = this.props.value.toFixed( - this.props.decimalPoints, - ); + if (!Number.isNaN(newValue)) { + save(motorName, newValue); + setEdited(false); } } - /* eslint-enable react/no-set-state */ - - stepChange(name, operator) { - const { value, step } = this.props; - const newValue = value + step * operator; - - this.motorValue.value = this.props.value.toFixed(this.props.decimalPoints); - this.motorValue.defaultValue = newValue; - this.props.save(name, newValue); - } - stopMotor(name) { - this.props.stop(name); - } - - render() { - const { value, motorName, step, suffix, decimalPoints } = this.props; - const valueCropped = value.toFixed(decimalPoints); - - return ( -
-

{this.props.label}

-
+ return ( +
+

{label}

+
+
{ + save(motorName, value + step); + }} >