• 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

6.12
/src/components/Questionnaire/TableFileTypeAndExtensionInput.tsx
1
import React, { FC, useEffect, useState, useRef } from "react";
1✔
2
import {
1✔
3
  Autocomplete,
4
  TextField,
5
  TableCell,
6
  Tooltip,
7
  TooltipProps,
8
  styled,
9
  Paper,
10
} from "@mui/material";
11
import dropdownArrowsIcon from "../../assets/icons/dropdown_arrows.svg?url";
1✔
12
import { fileTypeExtensions } from "../../config/FileTypeConfig";
1✔
13
import useFormMode from "../../hooks/useFormMode";
1✔
14

15
const DropdownArrowsIcon = styled("div")(() => ({
1✔
NEW
16
  backgroundImage: `url("${dropdownArrowsIcon}")`,
×
UNCOV
17
  backgroundSize: "contain",
×
UNCOV
18
  backgroundRepeat: "no-repeat",
×
UNCOV
19
  width: "9.17px",
×
UNCOV
20
  height: "18px",
×
21
}));
1✔
22

23
type Props = {
24
  inputID: string;
25
  typeValue: string;
26
  extensionValue: string;
27
  options: string[];
28
  name: string;
29
  required?: boolean;
30
  helpText?: string;
31
  placeholder?: string;
32
  disableClearable?: boolean;
33
  onChange?: (e: React.SyntheticEvent, v: string, r: string) => void;
34
};
35

36
const StyledTooltip = styled((props: TooltipProps) => (
1✔
37
  <Tooltip classes={{ popper: props.className }} {...props} />
×
38
))(() => ({
1✔
UNCOV
39
  "& .MuiTooltip-tooltip": {
×
UNCOV
40
    marginTop: "4px !important",
×
UNCOV
41
    color: "#C93F08",
×
UNCOV
42
    background: "#FFFFFF",
×
UNCOV
43
    border: "1px solid #2B528B",
×
UNCOV
44
  },
×
UNCOV
45
  "& .MuiTooltip-arrow": {
×
UNCOV
46
    color: "#2B528B",
×
UNCOV
47
  },
×
48
}));
1✔
49

50
const StyledTableCell = styled(TableCell)(() => ({
1✔
UNCOV
51
  borderTop: "1px solid #6B7294 !important",
×
UNCOV
52
  borderRight: "1px solid #6B7294 !important",
×
UNCOV
53
  borderBottom: "none!important",
×
UNCOV
54
  borderLeft: "none!important",
×
UNCOV
55
  padding: "0",
×
UNCOV
56
  "& .MuiStack-root": {
×
UNCOV
57
    width: "auto",
×
UNCOV
58
  },
×
59
}));
1✔
60

61
const StyledPaper = styled(Paper)(() => ({
1✔
UNCOV
62
  borderRadius: "8px",
×
UNCOV
63
  border: "1px solid #6B7294",
×
UNCOV
64
  marginTop: "2px",
×
UNCOV
65
  "& .MuiAutocomplete-listbox": {
×
UNCOV
66
    padding: 0,
×
UNCOV
67
  },
×
UNCOV
68
  "& .MuiAutocomplete-option[aria-selected='true']": {
×
UNCOV
69
    color: "#083A50",
×
UNCOV
70
    background: "#FFFFFF",
×
UNCOV
71
  },
×
UNCOV
72
  "& .MuiAutocomplete-option": {
×
UNCOV
73
    padding: "0 10px",
×
UNCOV
74
    height: "35px",
×
UNCOV
75
    color: "#083A50",
×
UNCOV
76
    background: "#FFFFFF",
×
UNCOV
77
  },
×
UNCOV
78
  "& .MuiAutocomplete-option:hover": {
×
UNCOV
79
    backgroundColor: "#3E7E6D",
×
UNCOV
80
    color: "#FFFFFF",
×
UNCOV
81
  },
×
UNCOV
82
  "& .MuiAutocomplete-option.Mui-focused": {
×
UNCOV
83
    backgroundColor: "#3E7E6D !important",
×
UNCOV
84
    color: "#FFFFFF",
×
UNCOV
85
  },
×
86
}));
1✔
87

88
const StyledAutocomplete = styled(Autocomplete)(({ readOnly }: { readOnly?: boolean }) => ({
1✔
UNCOV
89
  "& .MuiInputBase-root": {
×
UNCOV
90
    backgroundColor: readOnly ? "#E5EEF4" : "#FFFFFF",
×
UNCOV
91
    "&.MuiAutocomplete-inputRoot.MuiInputBase-root": {
×
UNCOV
92
      display: "flex",
×
UNCOV
93
      alignItems: "center",
×
UNCOV
94
      padding: 0,
×
UNCOV
95
    },
×
UNCOV
96
    "& .MuiInputBase-input": {
×
UNCOV
97
      fontWeight: 400,
×
UNCOV
98
      fontSize: "16px",
×
UNCOV
99
      fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
100
      padding: "10px 12px 10px 12px !important",
×
UNCOV
101
      color: readOnly ? "#083A50" : "initial",
×
UNCOV
102
      cursor: readOnly ? "not-allowed !important" : "initial",
×
UNCOV
103
    },
×
UNCOV
104
    "& ::placeholder": {
×
UNCOV
105
      color: "#87878C",
×
UNCOV
106
      fontWeight: 400,
×
UNCOV
107
      opacity: 1,
×
UNCOV
108
    },
×
UNCOV
109
    "& .MuiAutocomplete-endAdornment": {
×
UNCOV
110
      right: "8px",
×
UNCOV
111
    },
×
UNCOV
112
    "&.Mui-focused": {
×
UNCOV
113
      boxShadow:
×
UNCOV
114
        "2px 2px 2px 1px rgba(38, 184, 147, 0.10), -2px -2px 2px 1px rgba(38, 184, 147, 0.20)",
×
UNCOV
115
    },
×
UNCOV
116
  },
×
117
}));
1✔
118

119
/**
120
 * Generates a generic autocomplete select box with a label and help text
121
 *
122
 * @param {Props} props
123
 * @returns {JSX.Element}
124
 */
125
const TableAutocompleteInput: FC<Props> = ({
1✔
UNCOV
126
  inputID,
×
UNCOV
127
  typeValue,
×
UNCOV
128
  extensionValue,
×
UNCOV
129
  name,
×
UNCOV
130
  required = false,
×
UNCOV
131
  helpText,
×
UNCOV
132
  onChange,
×
UNCOV
133
  ...rest
×
UNCOV
134
}) => {
×
135
  const { readOnlyInputs } = useFormMode();
×
136

137
  const [typeVal, setTypeVal] = useState(typeValue);
×
138
  const [extensionVal, setExtensionVal] = useState(extensionValue);
×
139
  const fileTypeRef = useRef<HTMLInputElement>(null);
×
140
  const fileExtensionRef = useRef<HTMLInputElement>(null);
×
141
  const [showFileTypeEror, setShowFileTypeError] = useState<boolean>(false);
×
142
  const [showFileExtensionError, setShowFileExtensionError] = useState<boolean>(false);
×
143

144
  useEffect(() => {
×
145
    const invalid = () => {
×
146
      setShowFileTypeError(true);
×
UNCOV
147
    };
×
148
    fileTypeRef.current?.addEventListener("invalid", invalid);
×
149
    return () => {
×
150
      fileTypeRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
151
    };
×
UNCOV
152
  }, [fileTypeRef]);
×
153

154
  useEffect(() => {
×
155
    const invalid = () => {
×
156
      setShowFileExtensionError(true);
×
UNCOV
157
    };
×
158

159
    fileExtensionRef.current?.addEventListener("invalid", invalid);
×
160
    return () => {
×
161
      fileExtensionRef.current?.removeEventListener("invalid", invalid);
×
UNCOV
162
    };
×
UNCOV
163
  }, [fileExtensionRef]);
×
164

165
  const onTypeValChangeWrapper = (e, v, r) => {
×
166
    setShowFileTypeError(false);
×
167
    v = v || "";
×
168
    if (v === "") {
×
169
      fileTypeRef.current.setCustomValidity("Please specify a file type");
×
UNCOV
170
    } else {
×
171
      fileTypeRef.current.setCustomValidity("");
×
UNCOV
172
    }
×
173
    if (typeof onChange === "function") {
×
174
      onChange(e, v, r);
×
UNCOV
175
    }
×
176

177
    setTypeVal(v);
×
UNCOV
178
  };
×
179
  const onExtensionValChangeWrapper = (e, v, r) => {
×
180
    setShowFileExtensionError(false);
×
181
    v = v || "";
×
182
    if (v === "") {
×
183
      fileExtensionRef.current.setCustomValidity("Please specify a file extension type");
×
UNCOV
184
    } else {
×
185
      fileExtensionRef.current.setCustomValidity("");
×
UNCOV
186
    }
×
187
    if (typeof onChange === "function") {
×
188
      onChange(e, v, r);
×
UNCOV
189
    }
×
190

191
    setExtensionVal(v);
×
UNCOV
192
  };
×
193
  const typeTextInputOnChange = (r) => {
×
194
    onTypeValChangeWrapper(null, r.target.value, null);
×
UNCOV
195
  };
×
196
  const extensionTextInputOnChange = (r) => {
×
197
    onExtensionValChangeWrapper(null, r.target.value, null);
×
UNCOV
198
  };
×
199

200
  useEffect(() => {
×
201
    onTypeValChangeWrapper(null, typeValue, null);
×
UNCOV
202
  }, [typeValue]);
×
203

204
  useEffect(() => {
×
205
    onExtensionValChangeWrapper(null, extensionValue, null);
×
UNCOV
206
  }, [extensionValue]);
×
207

208
  return (
×
UNCOV
209
    <>
×
UNCOV
210
      <StyledTableCell>
×
UNCOV
211
        <StyledAutocomplete
×
UNCOV
212
          sx={{
×
UNCOV
213
            "& .MuiInputBase-input": {
×
UNCOV
214
              fontWeight: 400,
×
UNCOV
215
              fontSize: "16px",
×
UNCOV
216
              fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
217
              padding: "0 !important",
×
UNCOV
218
              height: "20px",
×
UNCOV
219
            },
×
UNCOV
220
          }}
×
UNCOV
221
          id={inputID.concat("-type")}
×
UNCOV
222
          size="small"
×
UNCOV
223
          value={typeVal || ""}
×
UNCOV
224
          onChange={onTypeValChangeWrapper}
×
UNCOV
225
          popupIcon={<DropdownArrowsIcon />}
×
UNCOV
226
          readOnly={readOnlyInputs}
×
UNCOV
227
          freeSolo
×
UNCOV
228
          PaperComponent={StyledPaper}
×
UNCOV
229
          slotProps={{
×
UNCOV
230
            popper: {
×
UNCOV
231
              disablePortal: true,
×
UNCOV
232
              sx: {
×
UNCOV
233
                top: "-2px !important",
×
UNCOV
234
                zIndex: "2000",
×
UNCOV
235
              },
×
UNCOV
236
              modifiers: [
×
UNCOV
237
                {
×
238
                  // disables popper from flipping above the input when out of screen room
UNCOV
239
                  name: "flip",
×
UNCOV
240
                  enabled: false,
×
UNCOV
241
                  options: {
×
UNCOV
242
                    fallbackPlacements: [],
×
UNCOV
243
                  },
×
UNCOV
244
                },
×
245
              ],
UNCOV
246
            },
×
UNCOV
247
          }}
×
UNCOV
248
          renderInput={(p) => (
×
249
            <StyledTooltip
×
UNCOV
250
              title="Missing required field"
×
UNCOV
251
              arrow
×
UNCOV
252
              disableHoverListener
×
UNCOV
253
              disableFocusListener
×
UNCOV
254
              disableTouchListener
×
UNCOV
255
              open={showFileTypeEror}
×
UNCOV
256
              slotProps={{
×
UNCOV
257
                tooltip: { style: { marginTop: "4px !important" } },
×
UNCOV
258
              }}
×
259
            >
UNCOV
260
              <TextField
×
UNCOV
261
                {...p}
×
UNCOV
262
                onChange={typeTextInputOnChange}
×
UNCOV
263
                name={name.concat("[type]")}
×
UNCOV
264
                required={required}
×
UNCOV
265
                placeholder={rest.placeholder || "Enter or select a type"}
×
UNCOV
266
                variant="standard"
×
UNCOV
267
                inputRef={fileTypeRef}
×
UNCOV
268
                InputProps={{ ...p.InputProps, disableUnderline: true }}
×
269
                // eslint-disable-next-line react/jsx-no-duplicate-props
UNCOV
270
                inputProps={{
×
UNCOV
271
                  ...p.inputProps,
×
UNCOV
272
                  maxLength: 30,
×
UNCOV
273
                  "aria-label": "File type",
×
UNCOV
274
                }}
×
275
              />
UNCOV
276
            </StyledTooltip>
×
277
          )}
UNCOV
278
          {...rest}
×
279
        />
UNCOV
280
      </StyledTableCell>
×
UNCOV
281
      <StyledTableCell>
×
UNCOV
282
        <StyledAutocomplete
×
UNCOV
283
          sx={{
×
UNCOV
284
            "& .MuiInputBase-input": {
×
UNCOV
285
              fontWeight: 400,
×
UNCOV
286
              fontSize: "16px",
×
UNCOV
287
              fontFamily: "'Nunito', 'Rubik', sans-serif",
×
UNCOV
288
              padding: "0 !important",
×
UNCOV
289
              height: "20px",
×
UNCOV
290
            },
×
UNCOV
291
          }}
×
UNCOV
292
          id={inputID.concat("-extension")}
×
UNCOV
293
          size="small"
×
UNCOV
294
          value={extensionVal || ""}
×
UNCOV
295
          onChange={onExtensionValChangeWrapper}
×
UNCOV
296
          popupIcon={<DropdownArrowsIcon />}
×
UNCOV
297
          readOnly={readOnlyInputs}
×
UNCOV
298
          freeSolo
×
UNCOV
299
          PaperComponent={StyledPaper}
×
UNCOV
300
          slotProps={{
×
UNCOV
301
            popper: {
×
UNCOV
302
              disablePortal: true,
×
UNCOV
303
              sx: {
×
UNCOV
304
                top: "-2px !important",
×
UNCOV
305
                zIndex: "2000",
×
UNCOV
306
              },
×
UNCOV
307
              modifiers: [
×
UNCOV
308
                {
×
309
                  // disables popper from flipping above the input when out of screen room
UNCOV
310
                  name: "flip",
×
UNCOV
311
                  enabled: false,
×
UNCOV
312
                  options: {
×
UNCOV
313
                    fallbackPlacements: [],
×
UNCOV
314
                  },
×
UNCOV
315
                },
×
316
              ],
UNCOV
317
            },
×
UNCOV
318
          }}
×
UNCOV
319
          renderInput={(p) => (
×
320
            <StyledTooltip
×
UNCOV
321
              title="Missing required field"
×
UNCOV
322
              arrow
×
UNCOV
323
              disableHoverListener
×
UNCOV
324
              disableFocusListener
×
UNCOV
325
              disableTouchListener
×
UNCOV
326
              open={showFileExtensionError}
×
327
            >
UNCOV
328
              <TextField
×
UNCOV
329
                {...p}
×
UNCOV
330
                onChange={extensionTextInputOnChange}
×
UNCOV
331
                name={name.concat("[extension]")}
×
UNCOV
332
                required={required}
×
UNCOV
333
                placeholder={rest.placeholder || "Enter or select an extension"}
×
UNCOV
334
                variant="standard"
×
UNCOV
335
                inputRef={fileExtensionRef}
×
UNCOV
336
                InputProps={{ ...p.InputProps, disableUnderline: true }}
×
337
                // eslint-disable-next-line react/jsx-no-duplicate-props
UNCOV
338
                inputProps={{
×
UNCOV
339
                  ...p.inputProps,
×
UNCOV
340
                  maxLength: 10,
×
UNCOV
341
                  "aria-label": "File extension",
×
UNCOV
342
                }}
×
343
              />
UNCOV
344
            </StyledTooltip>
×
345
          )}
UNCOV
346
          options={fileTypeExtensions[typeVal] || []}
×
347
        />
UNCOV
348
      </StyledTableCell>
×
UNCOV
349
    </>
×
350
  );
UNCOV
351
};
×
352

353
export default TableAutocompleteInput;
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