• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.73
/src/js/components/common/forms/passwordinput.js
1
// Copyright 2016 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React, { useEffect, useRef, useState } from 'react';
15
import { Controller, useFormContext, useWatch } from 'react-hook-form';
16

17
import { CheckCircle as CheckIcon, Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon } from '@mui/icons-material';
18
import { Button, FormControl, FormHelperText, IconButton, Input, InputAdornment, InputLabel } from '@mui/material';
19

20
import copy from 'copy-to-clipboard';
21
import generator from 'generate-password';
22

23
import { TIMEOUTS } from '../../../constants/appConstants';
24
import { toggle } from '../../../helpers';
25
import { runValidations } from './form';
26

27
const PasswordGenerateButtons = ({ clearPass, edit, generatePass }) => (
14✔
28
  <div className="pass-buttons">
72✔
29
    <Button color="primary" onClick={generatePass}>
30
      Generate
31
    </Button>
32
    {edit ? <Button onClick={clearPass}>Cancel</Button> : null}
72!
33
  </div>
34
);
35

36
const SCORE_THRESHOLD = 3;
14✔
37

38
const PasswordGenerationControls = ({ score, feedback }) => (
14✔
39
  <>
528✔
40
    <div className="help-text" id="pass-strength">
41
      Strength: <meter max={4} min={0} value={score} high={3.9} optimum={4} low={2.5} />
42
      {score > SCORE_THRESHOLD ? <CheckIcon className="fadeIn green" style={{ height: 18, marginTop: -3, marginBottom: -3 }} /> : null}
528✔
43
    </div>
44
    {!!feedback.length && (
664✔
45
      <p className="help-text">
46
        {feedback.map((message, index) => (
47
          <React.Fragment key={`feedback-${index}`}>
136✔
48
            <span>{message}</span>
49
            <br />
50
          </React.Fragment>
51
        ))}
52
      </p>
53
    )}
54
  </>
55
);
56

57
export const PasswordInput = ({
14✔
58
  autocomplete,
59
  className,
60
  control,
61
  create,
62
  defaultValue,
63
  disabled,
64
  edit,
65
  generate,
66
  id,
67
  InputLabelProps = {},
1,361✔
68
  label,
69
  onClear,
70
  placeholder,
71
  required,
72
  validations = ''
506✔
73
}) => {
74
  const [score, setScore] = useState('');
1,407✔
75
  const [visible, setVisible] = useState(false);
1,407✔
76
  const [copied, setCopied] = useState(false);
1,407✔
77
  const [feedback, setFeedback] = useState([]);
1,407✔
78
  const [confirmationId] = useState(id.includes('current') ? '' : ['password', 'password_confirmation'].find(thing => thing !== id));
2,480✔
79
  const timer = useRef();
1,407✔
80
  const {
81
    clearErrors,
82
    formState: { errors },
83
    setError,
84
    setValue,
85
    trigger,
86
    getValues
87
  } = useFormContext();
1,407✔
88
  const confirmation = useWatch({ name: confirmationId });
1,407✔
89
  const errorKey = `${id}-error`;
1,407✔
90

91
  useEffect(() => {
1,407✔
92
    return () => {
1,306✔
93
      clearTimeout(timer.current);
1,306✔
94
    };
95
  });
96

97
  const clearPassClick = () => {
1,407✔
98
    setValue(id, '');
×
99
    onClear();
×
100
    setCopied(false);
×
101
  };
102

103
  const generatePassClick = () => {
1,407✔
104
    const password = generator.generate({ length: 16, numbers: true });
6✔
105
    setValue(id, password);
6✔
106
    const form = getValues();
6✔
107
    if (form.hasOwnProperty(`${id}_confirmation`)) {
6✔
108
      setValue(`${id}_confirmation`, password);
1✔
109
    }
110
    copy(password);
6✔
111
    setCopied(true);
6✔
112
    setVisible(true);
6✔
113
    timer.current = setTimeout(() => setCopied(false), TIMEOUTS.fiveSeconds);
6✔
114
    trigger();
6✔
115
  };
116

117
  const validate = async (value = '') => {
1,407✔
118
    let { isValid, errortext } = runValidations({ id, required, validations, value });
353✔
119
    if (confirmation && value !== confirmation) {
353✔
120
      isValid = false;
80✔
121
      errortext = 'The passwords you provided do not match, please check again.';
80✔
122
    }
123
    if (isValid) {
353✔
124
      clearErrors(errorKey);
252✔
125
    } else {
126
      setError(errorKey, { type: 'validate', message: errortext });
101✔
127
    }
128
    if (!create || (!required && !value)) {
353✔
129
      return isValid;
214✔
130
    }
131
    const { default: zxcvbn } = await import(/* webpackChunkName: "zxcvbn" */ 'zxcvbn');
139✔
132
    const strength = zxcvbn(value);
139✔
133
    const score = strength.score;
139✔
134
    setFeedback(strength.feedback.suggestions || []);
139!
135
    setScore(score);
139✔
136
    return score > SCORE_THRESHOLD && isValid;
139✔
137
  };
138

139
  return (
1,407✔
140
    <div className={className}>
141
      <div className="password-wrapper">
142
        <Controller
143
          name={id}
144
          control={control}
145
          rules={{ required, validate }}
146
          render={({ field: { value, onChange, onBlur, ref }, fieldState: { error } }) => (
147
            <FormControl className={required ? 'required' : ''} error={Boolean(error?.message || errors[errorKey])} style={{ width: 400 }}>
1,700✔
148
              <InputLabel htmlFor={id} {...InputLabelProps}>
149
                {label}
150
              </InputLabel>
151
              <Input
152
                autoComplete={autocomplete}
153
                id={id}
154
                name={id}
155
                type={visible ? 'text' : 'password'}
1,700✔
156
                defaultValue={defaultValue}
157
                placeholder={placeholder}
158
                value={value ?? ''}
2,161✔
159
                disabled={disabled}
160
                inputRef={ref}
161
                required={required}
162
                onChange={({ target: { value } }) => {
163
                  setValue(id, value);
222✔
164
                  onChange(value);
222✔
165
                }}
166
                onBlur={onBlur}
167
                endAdornment={
168
                  <InputAdornment position="end">
169
                    <IconButton onClick={() => setVisible(toggle)} size="large">
×
170
                      {visible ? <VisibilityIcon /> : <VisibilityOffIcon />}
1,700✔
171
                    </IconButton>
172
                  </InputAdornment>
173
                }
174
              />
175
              <FormHelperText>{(errors[errorKey] || error)?.message}</FormHelperText>
3,196✔
176
            </FormControl>
177
          )}
178
        />
179
        {generate && !required && <PasswordGenerateButtons clearPass={clearPassClick} edit={edit} generatePass={generatePassClick} />}
1,526✔
180
      </div>
181
      {copied ? <div className="green fadeIn margin-bottom-small">Copied to clipboard</div> : null}
1,407✔
182
      {create && (
2,036✔
183
        <>
184
          <PasswordGenerationControls feedback={feedback} score={score} />
185
          {generate && required && <PasswordGenerateButtons clearPass={clearPassClick} edit={edit} generatePass={generatePassClick} />}
729✔
186
        </>
187
      )}
188
    </div>
189
  );
190
};
191

192
export default PasswordInput;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc