diff --git a/client/src/components/InputsModal/AuthSettings.ts b/client/src/components/InputsModal/Auth/AuthSettings.ts similarity index 100% rename from client/src/components/InputsModal/AuthSettings.ts rename to client/src/components/InputsModal/Auth/AuthSettings.ts diff --git a/client/src/components/InputsModal/AuthTypeSelector.tsx b/client/src/components/InputsModal/Auth/AuthTypeSelector.tsx similarity index 96% rename from client/src/components/InputsModal/AuthTypeSelector.tsx rename to client/src/components/InputsModal/Auth/AuthTypeSelector.tsx index 770d2adff..aa92c5931 100644 --- a/client/src/components/InputsModal/AuthTypeSelector.tsx +++ b/client/src/components/InputsModal/Auth/AuthTypeSelector.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { InputOption, TestInput } from '~/models/testSuiteModels'; -import InputCombobox from './InputCombobox'; +import InputCombobox from '~/components/InputsModal/InputCombobox'; export interface InputAccessProps { input: TestInput; diff --git a/client/src/components/InputsModal/InputAccess.tsx b/client/src/components/InputsModal/Auth/InputAuth.tsx similarity index 60% rename from client/src/components/InputsModal/InputAccess.tsx rename to client/src/components/InputsModal/Auth/InputAuth.tsx index 6471c787f..916ebfa91 100644 --- a/client/src/components/InputsModal/InputAccess.tsx +++ b/client/src/components/InputsModal/Auth/InputAuth.tsx @@ -3,23 +3,28 @@ import { Card, CardContent, InputLabel, List, ListItem } from '@mui/material'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Auth, TestInput } from '~/models/testSuiteModels'; -import { AuthType, getAccessFields } from '~/components/InputsModal/AuthSettings'; +import { + AuthType, + getAccessFields, + getAuthFields, +} from '~/components/InputsModal/Auth/AuthSettings'; +import AuthTypeSelector from '~/components/InputsModal/Auth/AuthTypeSelector'; import FieldLabel from '~/components/InputsModal/FieldLabel'; import InputFields from '~/components/InputsModal/InputFields'; -import useStyles from './styles'; -import AuthTypeSelector from './AuthTypeSelector'; +import useStyles from '../styles'; -export interface InputAccessProps { +export interface InputAuthProps { + mode: 'access' | 'auth'; input: TestInput; index: number; inputsMap: Map; setInputsMap: (map: Map, edited?: boolean) => void; } -const InputAccess: FC = ({ input, index, inputsMap, setInputsMap }) => { +const InputAuth: FC = ({ mode, input, index, inputsMap, setInputsMap }) => { const { classes } = useStyles(); - const [accessValues, setAccessValues] = React.useState>(new Map()); - const [accessValuesPopulated, setAccessValuesPopulated] = React.useState(false); + const [authValues, setAuthValues] = React.useState>(new Map()); + const [authValuesPopulated, setAuthValuesPopulated] = React.useState(false); // Default auth type settings const authComponent = input.options?.components?.find( @@ -35,16 +40,21 @@ const InputAccess: FC = ({ input, index, inputsMap, setInputsM (authComponent?.default || firstListOption || 'public') as string, ); - const [accessFields, setAccessFields] = React.useState( - getAccessFields(authType as AuthType, accessValues, input.options?.components || []), - ); + // Set fields depending on mode + let fields: TestInput[] = []; + if (mode === 'access') { + fields = getAccessFields(authType as AuthType, authValues, input.options?.components || []); + } else if (mode === 'auth') { + fields = getAuthFields(authType as AuthType, authValues, input.options?.components || []); + } + const [authFields, setAuthFields] = React.useState(fields); useEffect(() => { // Set defaults on radio buttons // This is necessary because radio buttons with no preset defaults will still cause // missing input errors - setAccessFields( - accessFields.map((field) => { + setAuthFields( + authFields.map((field) => { if ( field.type === 'radio' && !field.default && @@ -59,38 +69,47 @@ const InputAccess: FC = ({ input, index, inputsMap, setInputsM const combinedStartingValues = getStartingValues(); - // Populate accessValues on mount - accessValues.set('auth_type', authType); - accessFields.forEach((field: TestInput) => { - accessValues.set(field.name, combinedStartingValues[field.name as keyof Auth] || ''); + // Populate authValues on mount + authValues.set('auth_type', authType); + authFields.forEach((field: TestInput) => { + authValues.set(field.name, combinedStartingValues[field.name as keyof Auth] || ''); }); - setAccessValuesPopulated(true); + setAuthValuesPopulated(true); // Trigger change on mount for default values - const accessValuesCopy = new Map(accessValues); - setAccessValues(accessValuesCopy); + const authValuesCopy = new Map(authValues); + setAuthValues(authValuesCopy); }, []); useEffect(() => { // Recalculate hidden fields - setAccessFields( - getAccessFields(authType as AuthType, accessValues, input.options?.components || []), - ); + if (mode === 'access') { + setAuthFields( + getAccessFields(authType as AuthType, authValues, input.options?.components || []), + ); + } else if (mode === 'auth') { + setAuthFields( + getAuthFields(authType as AuthType, authValues, input.options?.components || []), + ); + } // Update inputsMap while maintaining hidden values - if (accessValuesPopulated) { - const combinedStartingValues = getStartingValues(); - const accessValuesObject = Object.fromEntries(accessValues) as Auth; - const combinedValues = { ...combinedStartingValues, ...accessValuesObject }; - const stringifiedAccessValues = JSON.stringify(combinedValues); - inputsMap.set(input.name, stringifiedAccessValues); + if (authValuesPopulated) { + let stringifiedValues = JSON.stringify(Object.fromEntries(authValues)); + if (mode === 'access') { + const combinedStartingValues = getStartingValues(); + const accessValuesObject = Object.fromEntries(authValues) as Auth; + const combinedValues = { ...combinedStartingValues, ...accessValuesObject }; + stringifiedValues = JSON.stringify(combinedValues); + } + inputsMap.set(input.name, stringifiedValues); setInputsMap(new Map(inputsMap)); } - }, [accessValues]); + }, [authValues]); const getStartingValues = () => { // Pre-populate values from AuthFields, input, and inputsMap in order of precedence - const fieldDefaultValues = accessFields.reduce( + const fieldDefaultValues = authFields.reduce( (acc, field) => ({ ...acc, [field.name]: field.default }), {}, ) as Auth; @@ -101,6 +120,7 @@ const InputAccess: FC = ({ input, index, inputsMap, setInputsM const inputsMapValues = inputsMap.get(input.name) ? (JSON.parse(inputsMap.get(input.name) as string) as Auth) : {}; + return { ...fieldDefaultValues, ...inputDefaultValues, @@ -111,7 +131,7 @@ const InputAccess: FC = ({ input, index, inputsMap, setInputsM const updateAuthType = (map: Map) => { setAuthType(map.get('auth_type') as string); - setAccessValues(map); + setAuthValues(map); }; return ( @@ -134,20 +154,16 @@ const InputAccess: FC = ({ input, index, inputsMap, setInputsM - + ); }; -export default InputAccess; +export default InputAuth; diff --git a/client/src/components/InputsModal/InputAuth.tsx b/client/src/components/InputsModal/InputAuth.tsx deleted file mode 100644 index 437b1739b..000000000 --- a/client/src/components/InputsModal/InputAuth.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { FC, useEffect } from 'react'; -import { Box, List, ListItem } from '@mui/material'; -import Markdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import { Auth, TestInput } from '~/models/testSuiteModels'; -import InputFields from './InputFields'; -import useStyles from './styles'; -import { AuthType, getAuthFields } from './AuthSettings'; -import AuthTypeSelector from './AuthTypeSelector'; - -export interface InputAuthProps { - input: TestInput; - index: number; - inputsMap: Map; - setInputsMap: (map: Map, edited?: boolean) => void; -} - -const InputAuth: FC = ({ input, index, inputsMap, setInputsMap }) => { - const { classes } = useStyles(); - const [authValues, setAuthValues] = React.useState>(new Map()); - const [authValuesPopulated, setAuthValuesPopulated] = React.useState(false); - - // Default auth type settings - const authComponent = input.options?.components?.find( - (component) => component.name === 'auth_type', - ); - - const firstListOption = - authComponent?.options?.list_options && authComponent?.options?.list_options?.length > 0 - ? authComponent?.options?.list_options[0].value - : undefined; - - const [authType, setAuthType] = React.useState( - (authComponent?.default || firstListOption || 'public') as string, - ); - - const [authFields, setAuthFields] = React.useState( - getAuthFields(authType as AuthType, authValues, input.options?.components || []), - ); - - useEffect(() => { - // Set defaults on radio buttons - // This is necessary because radio buttons with no preset defaults will still cause - // missing input errors - setAuthFields( - authFields.map((field) => { - if ( - field.type === 'radio' && - !field.default && - !field.value && - field.options?.list_options - ) { - field.default = field.options?.list_options[0].value; - } - return field; - }), - ); - - const combinedStartingValues = getStartingValues(); - - // Populate authValues on mount - authValues.set('auth_type', authType); - authFields.forEach((field: TestInput) => { - authValues.set(field.name, combinedStartingValues[field.name as keyof Auth] || ''); - }); - - setAuthValuesPopulated(true); - - // Trigger change on mount for default values - const authValuesCopy = new Map(authValues); - setAuthValues(authValuesCopy); - }, []); - - useEffect(() => { - // Recalculate hidden fields - setAuthFields(getAuthFields(authType as AuthType, authValues, input.options?.components || [])); - - // Update inputsMap - if (authValuesPopulated) { - const stringifiedAuthValues = JSON.stringify(Object.fromEntries(authValues)); - inputsMap.set(input.name, stringifiedAuthValues); - setInputsMap(new Map(inputsMap)); - } - }, [authValues]); - - const getStartingValues = () => { - // Pre-populate values from AuthFields, input, and inputsMap in order of precedence - const fieldDefaultValues = authFields.reduce( - (acc, field) => ({ ...acc, [field.name]: field.default }), - {}, - ) as Auth; - const inputDefaultValues = - input.default && typeof input.default === 'string' ? (JSON.parse(input.default) as Auth) : {}; - const inputStartingValues = - input.value && typeof input.value === 'string' ? (JSON.parse(input.value) as Auth) : {}; - const inputsMapValues = inputsMap.get(input.name) - ? (JSON.parse(inputsMap.get(input.name) as string) as Auth) - : {}; - - return { - ...fieldDefaultValues, - ...inputDefaultValues, - ...inputStartingValues, - ...inputsMapValues, - } as Auth; - }; - - const updateAuthType = (map: Map) => { - setAuthType(map.get('auth_type') as string); - setAuthValues(map); - }; - - return ( - - - {input.description && ( - - {input.description} - - )} - - - - - - - ); -}; - -export default InputAuth; diff --git a/client/src/components/InputsModal/InputFields.tsx b/client/src/components/InputsModal/InputFields.tsx index 541f632f2..78f3d914d 100644 --- a/client/src/components/InputsModal/InputFields.tsx +++ b/client/src/components/InputsModal/InputFields.tsx @@ -1,14 +1,13 @@ import React, { FC } from 'react'; import { List } from '@mui/material'; import { TestInput } from '~/models/testSuiteModels'; -import InputOAuthCredentials from '~/components/InputsModal/InputOAuthCredentials'; +import InputAuth from '~/components/InputsModal/Auth/InputAuth'; import InputCheckboxGroup from '~/components/InputsModal/InputCheckboxGroup'; +import InputCombobox from '~/components/InputsModal/InputCombobox'; +import InputOAuthCredentials from '~/components/InputsModal/InputOAuthCredentials'; import InputRadioGroup from '~/components/InputsModal/InputRadioGroup'; -import InputTextField from '~/components/InputsModal/InputTextField'; -import InputAuth from '~/components/InputsModal/InputAuth'; import InputSingleCheckbox from '~/components/InputsModal/InputSingleCheckbox'; -import InputCombobox from '~/components/InputsModal/InputCombobox'; -import InputAccess from '~/components/InputsModal/InputAccess'; +import InputTextField from '~/components/InputsModal/InputTextField'; export interface InputFieldsProps { inputs: TestInput[]; @@ -26,6 +25,7 @@ const InputFields: FC = ({ inputs, inputsMap, setInputsMap }) if (input.options?.mode === 'auth') { return ( = ({ inputs, inputsMap, setInputsMap }) ); } return ( - ) => { return inputs.some((input: TestInput) => { diff --git a/client/src/components/InputsModal/__tests__/Inputs.test.tsx b/client/src/components/InputsModal/__tests__/Inputs.test.tsx index 0fdb6c0c6..97ceb7823 100644 --- a/client/src/components/InputsModal/__tests__/Inputs.test.tsx +++ b/client/src/components/InputsModal/__tests__/Inputs.test.tsx @@ -1,15 +1,14 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; import { SnackbarProvider } from 'notistack'; import { TestInput } from '~/models/testSuiteModels'; -import ThemeProvider from '~/components/ThemeProvider'; +import InputAuth from '~/components/InputsModal/Auth/InputAuth'; import InputCheckboxGroup from '~/components/InputsModal/InputCheckboxGroup'; +import InputOAuthCredentials from '~/components/InputsModal/InputOAuthCredentials'; import InputRadioGroup from '~/components/InputsModal/InputRadioGroup'; import InputTextField from '~/components/InputsModal/InputTextField'; -import InputOAuthCredentials from '~/components/InputsModal/InputOAuthCredentials'; -import InputAuth from '~/components/InputsModal/InputAuth'; -import InputAccess from '~/components/InputsModal/InputAccess'; -import { describe, expect, it } from 'vitest'; +import ThemeProvider from '~/components/ThemeProvider'; describe('Input Components', () => { it('renders InputCheckboxGroup', () => { @@ -176,6 +175,7 @@ describe('Input Components', () => { ()} @@ -208,7 +208,8 @@ describe('Input Components', () => { render( - ()}