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

CBIIT / crdc-datahub-ui / 15497092546

06 Jun 2025 06:19PM UTC coverage: 65.179% (+2.5%) from 62.708%
15497092546

push

github

web-flow
Merge pull request #726 from CBIIT/CRDCDH-2817

CRDCDH-2817 Vite/Vitest Migration & Upgrade dependencies

3529 of 3882 branches covered (90.91%)

Branch coverage included in aggregate %.

167 of 224 new or added lines in 82 files covered. (74.55%)

7620 existing lines in 126 files now uncovered.

22012 of 35304 relevant lines covered (62.35%)

101.98 hits per line

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

88.46
/src/components/Questionnaire/TextInput.tsx
1
import React, { FC, ReactNode, useEffect, useId, useRef, useState } from "react";
1✔
2
import {
1✔
3
  FormControl,
4
  FormHelperText,
5
  Grid,
6
  OutlinedInput,
7
  OutlinedInputProps,
8
  styled,
9
} from "@mui/material";
10
import { updateInputValidity } from "../../utils";
1✔
11
import Tooltip from "../Tooltip";
1✔
12

13
const StyledGridWrapper = styled(Grid)(({ theme }) => ({
1✔
14
  "& .MuiFormHelperText-root": {
1,647✔
15
    color: "#083A50",
1,647✔
16
    marginLeft: "0",
1,647✔
17
    [theme.breakpoints.up("lg")]: {
1,647✔
18
      whiteSpace: "nowrap",
1,647✔
19
    },
1,647✔
20
  },
1,647✔
21
  "& .MuiFormHelperText-root.Mui-error": {
1,647✔
22
    color: "#D54309 !important",
1,647✔
23
  },
1,647✔
24
}));
1✔
25

26
const StyledFormControl = styled(FormControl)(() => ({
1✔
27
  height: "100%",
1,647✔
28
  justifyContent: "end",
1,647✔
29
}));
1✔
30

31
export const StyledLabelWrapper = styled("div")(() => ({
1✔
32
  display: "flex",
1,647✔
33
  justifyContent: "flex-start",
1,647✔
34
  alignItems: "center",
1,647✔
35
  minHeight: "20px",
1,647✔
36
  color: "#083A50",
1,647✔
37
  marginBottom: "4px",
1,647✔
38
}));
1✔
39

40
export const StyledLabel = styled("label")(() => ({
1✔
41
  fontWeight: 700,
129✔
42
  fontSize: "16px",
129✔
43
  lineHeight: "19.6px",
129✔
44
  minHeight: "20px",
129✔
45
  color: "#083A50",
129✔
46
}));
1✔
47

48
const StyledAsterisk = styled("span")(() => ({
1✔
UNCOV
49
  color: "#C93F08",
×
UNCOV
50
  marginLeft: "2px",
×
51
}));
1✔
52

53
const StyledHelperText = styled(FormHelperText)(() => ({
1✔
54
  marginTop: "4px",
1,647✔
55
  minHeight: "20px",
1,647✔
56
}));
1✔
57

58
const StyledOutlinedInput = styled(OutlinedInput, {
1✔
59
  shouldForwardProp: (prop) => prop !== "resize" && prop !== "rowHeight",
1✔
60
})<OutlinedInputProps & { resize: boolean; rowHeight: number }>(
1✔
61
  ({ resize, rowHeight, rows, minRows, maxRows }) => ({
1✔
62
    borderRadius: "8px",
1,647✔
63
    backgroundColor: "#fff",
1,647✔
64
    color: "#083A50",
1,647✔
65
    "& .MuiInputBase-input": {
1,647✔
66
      fontWeight: 400,
1,647✔
67
      fontSize: "16px",
1,647✔
68
      fontFamily: "'Nunito', 'Rubik', sans-serif",
1,647✔
69
      lineHeight: "19.6px",
1,647✔
70
      padding: "12px",
1,647✔
71
      height: "20px",
1,647✔
72
    },
1,647✔
73
    "&.MuiInputBase-multiline": {
1,647✔
74
      padding: "12px",
1,647✔
75
    },
1,647✔
76
    "& .MuiInputBase-inputMultiline": {
1,647✔
77
      resize: resize ? "vertical" : "none",
1,647!
78
      minHeight: resize && rowHeight ? `${(+rows || +minRows || 1) * rowHeight}px` : 0,
1,647!
79
      maxHeight: resize && maxRows && rowHeight ? `${+maxRows * rowHeight}px` : "none",
1,647!
80
    },
1,647✔
81
    "&.MuiInputBase-multiline .MuiInputBase-input": {
1,647✔
82
      lineHeight: `${rowHeight}px`,
1,647✔
83
      padding: 0,
1,647✔
84
    },
1,647✔
85
    "& .MuiOutlinedInput-notchedOutline": {
1,647✔
86
      borderColor: "#6B7294",
1,647✔
87
    },
1,647✔
88
    "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
1,647✔
89
      border: "1px solid #209D7D",
1,647✔
90
      boxShadow:
1,647✔
91
        "2px 2px 4px 0px rgba(38, 184, 147, 0.10), -1px -1px 6px 0px rgba(38, 184, 147, 0.20)",
1,647✔
92
    },
1,647✔
93
    "& .MuiInputBase-input::placeholder": {
1,647✔
94
      color: "#87878C",
1,647✔
95
      fontWeight: 400,
1,647✔
96
      opacity: 1,
1,647✔
97
    },
1,647✔
98
    // Override the input error border color
99
    "&.Mui-error fieldset": {
1,647✔
100
      borderColor: "#D54309 !important",
1,647✔
101
    },
1,647✔
102
    // Target readOnly <textarea> inputs
103
    "&.MuiInputBase-multiline.Mui-readOnly": {
1,647✔
104
      backgroundColor: "#E5EEF4",
1,647✔
105
      color: "#083A50",
1,647✔
106
      cursor: "not-allowed",
1,647✔
107
      borderRadius: "8px",
1,647✔
108
    },
1,647✔
109
    // Target readOnly <input> inputs
110
    "& .MuiOutlinedInput-input:read-only": {
1,647✔
111
      backgroundColor: "#E5EEF4",
1,647✔
112
      color: "#083A50",
1,647✔
113
      cursor: "not-allowed",
1,647✔
114
      borderRadius: "8px",
1,647✔
115
    },
1,647✔
116
  })
1,647✔
117
);
1✔
118

119
type Props = {
120
  label?: string | ReactNode;
121
  labelStartAddornment?: ReactNode;
122
  infoText?: string;
123
  errorText?: string;
124
  tooltipText?: string | ReactNode;
125
  gridWidth?: 2 | 4 | 6 | 8 | 10 | 12;
126
  maxLength?: number;
127
  hideValidation?: boolean;
128
  resize?: boolean;
129
  validate?: (input: string) => boolean;
130
  filter?: (input: string) => string;
131
  parentStateSetter?: (string) => void;
132
} & OutlinedInputProps;
133

134
/**
135
 * Generates a generic text input with a label and help text
136
 *
137
 * NOTE:
138
 * - We're using a custom wrapper for Material UI's OutlinedInput component
139
 *   instead of using the TextField component because of the forced
140
 *   floating label behavior of TextField.
141
 *
142
 * @param {Props} props & number props
143
 * @returns {JSX.Element}
144
 */
145
const TextInput: FC<Props> = ({
1✔
146
  classes,
3,294✔
147
  value,
3,294✔
148
  label,
3,294✔
149
  labelStartAddornment,
3,294✔
150
  required = false,
3,294✔
151
  gridWidth,
3,294✔
152
  maxLength,
3,294✔
153
  infoText,
3,294✔
154
  errorText,
3,294✔
155
  tooltipText,
3,294✔
156
  hideValidation,
3,294✔
157
  validate,
3,294✔
158
  filter,
3,294✔
159
  type,
3,294✔
160
  readOnly,
3,294✔
161
  rows,
3,294✔
162
  multiline,
3,294✔
163
  resize,
3,294✔
164
  inputProps,
3,294✔
165
  onChange,
3,294✔
166
  parentStateSetter,
3,294✔
167
  ...rest
3,294✔
168
}) => {
3,294✔
169
  const id = rest.id || useId();
3,294✔
170

171
  const [val, setVal] = useState(value);
3,294✔
172
  const [error, setError] = useState(false);
3,294✔
173
  const errorMsg = errorText || (required ? "This field is required" : null);
3,294!
174
  const inputRef = useRef<HTMLInputElement>(null);
3,294✔
175
  const ROW_HEIGHT = 25; // line height of each row in a multiline
3,294✔
176

177
  const processValue = (inputVal: string) => {
3,294✔
178
    let newVal = inputVal;
3,336✔
179

180
    if (typeof filter === "function") {
3,336!
181
      newVal = filter(newVal);
×
UNCOV
182
    }
×
183
    if (typeof maxLength === "number" && newVal?.length > maxLength) {
3,336✔
184
      newVal = newVal.slice(0, maxLength);
153✔
185
    }
153✔
186
    if (typeof validate === "function") {
3,336!
187
      const customIsValid = validate(newVal);
×
188
      updateInputValidity(inputRef, !customIsValid ? errorMsg : "");
×
UNCOV
189
    }
×
190
    if (typeof parentStateSetter === "function") {
3,336!
191
      parentStateSetter(newVal);
×
UNCOV
192
    }
×
193
    setVal(newVal);
3,336✔
194
  };
3,336✔
195

196
  const onChangeWrapper = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
3,294✔
197
    if (typeof onChange === "function") {
1,725✔
198
      onChange(event);
1,725✔
199
    }
1,725✔
200
    const newVal = event.target.value;
1,725✔
201

202
    processValue(newVal);
1,725✔
203
    setError(false);
1,725✔
204
  };
1,725✔
205

206
  useEffect(() => {
3,294✔
207
    const invalid = () => setError(true);
36✔
208

209
    inputRef.current?.addEventListener("invalid", invalid);
36✔
210
    return () => {
36✔
211
      inputRef.current?.removeEventListener("invalid", invalid);
36!
212
    };
36✔
213
  }, [inputRef]);
3,294✔
214

215
  useEffect(() => {
3,294✔
216
    processValue(value?.toString());
1,611✔
217
  }, [value]);
3,294✔
218

219
  /* MUI sets the height for multiline input using inline styling. Needs to be overwritten to have a working minHeight */
220
  const customInputProps =
3,294✔
221
    resize && multiline
3,294✔
222
      ? { style: { height: `${(+rows || 1) * ROW_HEIGHT}px`, overflow: "auto" } }
3,294!
UNCOV
223
      : {};
×
224

225
  return (
3,294✔
226
    <StyledGridWrapper md={gridWidth || 6} xs={12} item>
3,294✔
227
      <StyledFormControl fullWidth error={error}>
3,294✔
228
        <StyledLabelWrapper>
3,294✔
229
          {labelStartAddornment}
3,294✔
230
          {label && (
3,294!
UNCOV
231
            <StyledLabel htmlFor={id}>
×
UNCOV
232
              {label}
×
UNCOV
233
              {required && label ? <StyledAsterisk>*</StyledAsterisk> : ""}
×
UNCOV
234
              {tooltipText && <Tooltip placement="right" title={tooltipText} />}
×
UNCOV
235
            </StyledLabel>
×
236
          )}
237
        </StyledLabelWrapper>
3,294✔
238
        <StyledOutlinedInput
3,294✔
239
          inputRef={inputRef}
3,294✔
240
          type={type || "text"}
3,294✔
241
          size="small"
3,294✔
242
          value={val ?? ""}
3,294!
243
          onChange={onChangeWrapper}
3,294✔
244
          required={required}
3,294✔
245
          readOnly={readOnly}
3,294✔
246
          rows={rows}
3,294✔
247
          multiline={multiline}
3,294✔
248
          resize={resize}
3,294✔
249
          rowHeight={ROW_HEIGHT}
3,294✔
250
          {...rest}
3,294✔
251
          inputProps={{ ...customInputProps, ...inputProps }}
3,294✔
252
          id={id}
3,294✔
253
        />
254
        <StyledHelperText>
3,294✔
255
          {(!hideValidation && !readOnly && error ? errorMsg : infoText) || " "}
3,294✔
256
        </StyledHelperText>
3,294✔
257
      </StyledFormControl>
3,294✔
258
    </StyledGridWrapper>
3,294✔
259
  );
260
};
3,294✔
261

262
export default TextInput;
1✔
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

© 2026 Coveralls, Inc