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

mendersoftware / gui / 897326496

pending completion
897326496

Pull #3752

gitlab-ci

mzedel
chore(e2e): made use of shared timeout & login checking values to remove code duplication

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3752: chore(e2e-tests): slightly simplified log in test + separated log out test

4395 of 6392 branches covered (68.76%)

8060 of 9780 relevant lines covered (82.41%)

126.17 hits per line

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

82.22
/src/js/components/common/forms/form.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 from 'react';
15

16
import { Button } from '@mui/material';
17

18
import validator from 'validator';
19

20
const getErrorMsg = (validateMethod, args) => {
17✔
21
  switch (validateMethod) {
161!
22
    case 'isLength':
23
      if (args[0] === 1) {
81!
24
        return 'This field is required';
×
25
      } else if (args[0] > 1) {
81!
26
        return `Must be at least ${args[0]} characters long`;
81✔
27
      }
28
      break;
×
29
    case 'isAlpha':
30
      return 'This field must contain only letters';
×
31
    case 'isAlphanumeric':
32
      return 'This field must contain only letters or numbers';
×
33
    case 'isNumeric':
34
      return 'Please enter a valid code';
×
35
    case 'isEmail':
36
      return 'Please enter a valid email address';
63✔
37
    case 'isNot':
38
      if (args[0] === args[1]) {
17!
39
        return `This field should have a value other than ${args[0]}`;
×
40
      }
41
      break;
17✔
42
    default:
43
      return 'There is an error with this field';
×
44
  }
45
};
46

47
const tryApplyValidations = (value, validations, initialValidationResult) =>
17✔
48
  validations.split(',').reduce((accu, validation) => {
248✔
49
    if (!accu.isValid) {
381✔
50
      return accu;
36✔
51
    }
52
    var args = validation.split(':');
345✔
53
    var validateMethod = args.shift();
345✔
54
    // We use JSON.parse to convert the string values passed to the
55
    // correct type. Ex. 'isLength:1' will make '1' actually a number
56
    args = args.map(arg => JSON.parse(JSON.stringify(arg)));
345✔
57

58
    var tmpArgs = args;
345✔
59
    // We then merge two arrays, ending up with the value
60
    // to pass first, then options, if any. ['valueFromInput', 5]
61
    args = [value].concat(args);
345✔
62
    try {
345✔
63
      // So the next line of code is actually:
64
      // validator.isLength('valueFromInput', 5)
65
      if (!validator[validateMethod].apply(validator, args)) {
345✔
66
        return { errortext: getErrorMsg(validateMethod, tmpArgs), isValid: false };
144✔
67
      }
68
    } catch {
69
      const errortext = getErrorMsg(validateMethod, args) || '';
17✔
70
      return { errortext, isValid: !errortext };
17✔
71
    }
72
    return accu;
184✔
73
  }, initialValidationResult);
74

75
const runPasswordValidations = ({ required, value, validations, isValid, errortext }) => {
17✔
76
  if (required && !value) {
150✔
77
    return { isValid: false, errortext: 'Password is required' };
1✔
78
  } else if (required || value) {
149!
79
    isValid = tryApplyValidations(value, validations, { isValid, errortext }).isValid;
149✔
80
    return { isValid, errortext: !isValid ? 'Password too weak' : errortext };
149✔
81
  }
82
  return { isValid, errortext };
×
83
};
84

85
const runValidations = ({ file, required, value, id, validations }) => {
17✔
86
  let isValid = true;
250✔
87
  let errortext = '';
250✔
88
  if (file) {
250!
89
    if (required && !value) {
×
90
      return { isValid: false, errortext: 'You must choose a file to upload' };
×
91
    }
92
  } else if (id && id.substr(0, 8) === 'password') {
250✔
93
    return runPasswordValidations({ required, value, validations, isValid, errortext });
150✔
94
  } else {
95
    if (value || required) {
100✔
96
      return tryApplyValidations(value, validations, { isValid, errortext });
99✔
97
    }
98
  }
99
  return { isValid, errortext };
1✔
100
};
101

102
export default class Form extends React.Component {
103
  constructor(props, context) {
104
    super(props, context);
27✔
105
    this.state = { children: {}, isValid: false };
20✔
106
    this.model = {};
20✔
107
    this.inputs = {}; // We create a map of traversed inputs
20✔
108
  }
109

110
  componentDidMount() {
111
    this.registerInputs(); // We register inputs from the children
20✔
112
  }
113

114
  componentDidUpdate(prevProps) {
115
    const self = this;
297✔
116
    // Use nextprops for registering components cwu
117
    if (prevProps.children !== self.props.children) {
297✔
118
      self.registerInputs();
17✔
119
    }
120
  }
121

122
  registerInputs() {
123
    const self = this;
37✔
124
    self.setState({ newChildren: React.Children.map(self.props.children, child => self._cloneChild(child, self)) });
146✔
125
  }
126

127
  // eslint-disable-next-line consistent-this
128
  _cloneChild(child, self) {
129
    // If we use the required prop we add a validation rule
130
    // that ensures there is a value. The input
131
    // should not be valid with empty value
132
    if (typeof child?.type === 'undefined') {
146✔
133
      return child;
3✔
134
    }
135
    var props = child.props || {};
143!
136
    var validations = props.validations || '';
143✔
137
    if (props.required && validations.indexOf('isLength') == -1) {
143✔
138
      validations = validations ? `${validations}, ` : validations;
23!
139
      validations += 'isLength:1';
23✔
140
    }
141
    return React.cloneElement(child, {
143✔
142
      validations: validations,
143
      attachToForm: self.attachToForm.bind(self),
144
      detachFromForm: self.detachFromForm.bind(self),
145
      updateModel: self.updateModel.bind(self),
146
      validate: self.validate.bind(self),
147
      hideHelp: self.props.hideHelp,
148
      handleKeyPress: self._handleKeyPress.bind(self)
149
    });
150
  }
151

152
  validate(component, value) {
153
    const { file, id, required, validations } = component.props;
253✔
154
    if (!validations) {
253✔
155
      return;
3✔
156
    }
157
    const { isValid, errortext } = runValidations({ file, id, required, validations, value });
250✔
158

159
    // Now we set the state of the input based on the validation
160
    component.setState(
250✔
161
      { isValid, errortext },
162
      // We use the callback of setState to wait for the state
163
      // change being propagated, then we validate the form itself
164
      this.validateForm.bind(this)
165
    );
166
  }
167

168
  validateForm() {
169
    // We set allIsValid to true and flip it if we find any
170
    // invalid input components
171
    var allIsValid = true;
253✔
172

173
    // Now we run through the inputs registered and flip our state
174
    // if we find an invalid input component
175
    var inputs = this.inputs;
253✔
176
    Object.keys(inputs).forEach(name => {
253✔
177
      if (!inputs[name].state.isValid || (inputs[name].props.required && !inputs[name].state.value)) {
552✔
178
        allIsValid = false;
284✔
179
      }
180
    });
181

182
    // And last, but not least, we set the valid state of the
183
    // form itself
184
    this.setState({ isValid: allIsValid });
253✔
185
  }
186

187
  // All methods defined are bound to the component by React JS, so it is safe to use "this"
188
  // even though we did not bind it. We add the input component to our inputs map
189
  attachToForm(component) {
190
    this.inputs[component.props.id] = component;
42✔
191
    this.model[component.props.id] = component.state.value || component.state.checked;
42✔
192

193
    // We have to validate the input when it is attached to put the
194
    // form in its correct state
195
    //this.validate(component);
196
  }
197

198
  // We want to remove the input component from the inputs map
199
  detachFromForm(component) {
200
    delete this.inputs[component.props.id];
42✔
201
    delete this.model[component.props.id];
42✔
202
  }
203
  updateModel() {
204
    Object.keys(this.inputs).forEach(name => {
8✔
205
      // re validate each input in case submit button pressed too soon
206
      this.validate(this.inputs[name], this.inputs[name].state.value);
17✔
207
    });
208

209
    this.validateForm();
8✔
210
    Object.keys(this.inputs).forEach(id => {
8✔
211
      this.model[id] = this.inputs[id].state.value || this.inputs[id].state.checked;
17✔
212
    });
213
    if (this.state.isValid) {
8!
214
      this.props.onSubmit(this.model);
8✔
215
    }
216
  }
217
  _handleKeyPress(event) {
218
    event.stopPropagation();
227✔
219
    if (event.key === 'Enter' && this.state.isValid) {
227!
220
      this.updateModel();
×
221
    }
222
  }
223

224
  onSubmit(event) {
225
    event.preventDefault();
×
226
  }
227

228
  render() {
229
    var uploadActions = !!this.props.showButtons && (
317✔
230
      <div
231
        className="flexbox"
232
        style={Object.assign({ justifyContent: 'flex-end', height: 'min-content' }, this.props.dialog ? { margin: '24px 0 -16px 0' } : { marginTop: '32px' })}
315✔
233
      >
234
        {!!this.props.handleCancel && (
335✔
235
          <Button key="cancel" onClick={this.props.handleCancel} style={{ marginRight: '10px', display: 'inline-block' }}>
236
            Cancel
237
          </Button>
238
        )}
239
        <Button
240
          variant="contained"
241
          key="submit"
242
          id={this.props.submitButtonId}
243
          color={this.props.buttonColor}
244
          onClick={() => this.updateModel()}
8✔
245
          disabled={!this.state.isValid}
246
        >
247
          {this.props.submitLabel}
248
        </Button>
249
      </div>
250
    );
251

252
    return (
317✔
253
      <form
254
        key={this.props.uniqueId}
255
        className={this.props.className || ''}
542✔
256
        autoComplete={this.props.autocomplete || undefined}
632✔
257
        onSubmit={e => this.onSubmit(e)}
×
258
      >
259
        {this.state.newChildren}
260
        {uploadActions}
261
      </form>
262
    );
263
  }
264
}
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