fix(striker-ui): add input tests to GeneralInitForm

main
Tsu-ba-me 2 years ago
parent 807af8d7b0
commit 4175759b56
  1. 339
      striker-ui/components/GeneralInitForm.tsx

@ -4,28 +4,34 @@ import {
ReactNode,
useCallback,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import { v4 as uuidv4 } from 'uuid';
import INPUT_TYPES from '../lib/consts/INPUT_TYPES';
import FlexBox from './FlexBox';
import InputWithRef, { InputForwardedRefContent } from './InputWithRef';
import isEmpty from '../lib/isEmpty';
import MessageBox from './MessageBox';
import MessageBox, { Message } from './MessageBox';
import OutlinedInputWithLabel, {
OutlinedInputWithLabelProps,
} from './OutlinedInputWithLabel';
import pad from '../lib/pad';
import SuggestButton from './SuggestButton';
import { testInput, testLength, testNotBlank } from '../lib/test_input';
import { InputTestBatches } from '../types/TestInputFunction';
type GeneralInitFormForwardRefContent = {
get?: () => {
adminPassword?: string;
organizationName?: string;
organizationPrefix?: string;
domainName?: string;
hostNumber?: number;
hostName?: string;
hostNumber?: number;
organizationName?: string;
organizationPrefix?: string;
};
};
@ -35,8 +41,10 @@ type OutlinedInputWithLabelOnBlur = Exclude<
>['onBlur'];
const MAX_ORGANIZATION_PREFIX_LENGTH = 5;
const MIN_ORGANIZATION_PREFIX_LENGTH = 2;
const MIN_ORGANIZATION_PREFIX_LENGTH = 1;
const MAX_HOST_NUMBER_LENGTH = 2;
const INPUT_COUNT = 7;
const REP_DN_CHAR = /^[a-z0-9-.]+$/;
const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record<
number,
@ -49,6 +57,13 @@ const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record<
words.map((word) => word.substring(0, 1).toLocaleLowerCase()).join(''),
};
const INPUT_TEST_MESSAGE_KEYS: string[] = Array.from(
{
length: INPUT_COUNT,
},
() => uuidv4(),
);
const buildOrganizationPrefix = (organizationName = '') => {
const words: string[] = organizationName
.split(/\s/)
@ -75,13 +90,18 @@ const buildHostName = ({
const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
(generalInitFormProps, ref) => {
const [helpMessage, setHelpText] = useState<ReactNode | undefined>();
const [helpMessage, setHelpMessage] = useState<ReactNode | undefined>();
const [inputMessages, setInputMessages] = useState<
Array<Message | undefined>
>([]);
const [
isShowOrganizationPrefixSuggest,
setIsShowOrganizationPrefixSuggest,
] = useState<boolean>(false);
const [isShowHostNameSuggest, setIsShowHostNameSuggest] =
useState<boolean>(false);
const [isConfirmAdminPassword, setIsConfirmAdminPassword] =
useState<boolean>(true);
const adminPasswordInputRef = useRef<InputForwardedRefContent<'string'>>(
{},
@ -99,6 +119,58 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
const hostNumberInputRef = useRef<InputForwardedRefContent<'number'>>({});
const hostNameInputRef = useRef<InputForwardedRefContent<'string'>>({});
const setInputMessage = useCallback((index: number, message?: Message) => {
setInputMessages((previous) => {
previous.splice(index, 1, message);
return [...previous];
});
}, []);
const setOrganizationPrefixInputMessage = useCallback(
(message?: Message) => setInputMessage(1, message),
[setInputMessage],
);
const setHostNumberInputMessage = useCallback(
(message?: Message) => setInputMessage(2, message),
[setInputMessage],
);
const setDomainNameInputMessage = useCallback(
(message?: Message) => setInputMessage(3, message),
[setInputMessage],
);
const setHostNameInputMessage = useCallback(
(message?: Message) => setInputMessage(4, message),
[setInputMessage],
);
const setAdminPasswordInputMessage = useCallback(
(message?: Message) => setInputMessage(5, message),
[setInputMessage],
);
const setConfirmAdminPasswordInputMessage = useCallback(
(message?: Message) => setInputMessage(6, message),
[setInputMessage],
);
const createInputTestMessages = useCallback(
() =>
inputMessages.map((message, index) => {
let messageElement;
if (message) {
const { children, type = 'warning' } = message;
messageElement = (
<MessageBox
key={`input-test-message-${INPUT_TEST_MESSAGE_KEYS[index]}`}
type={type}
>
{children}
</MessageBox>
);
}
return messageElement;
}),
[inputMessages],
);
const populateOrganizationPrefixInput = useCallback(
({
organizationName = organizationNameInputRef.current.getValue?.call(
@ -191,6 +263,136 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
[],
);
const inputTests: InputTestBatches = useMemo(
() => ({
adminPassword: {
defaults: {
onSuccess: () => {
setAdminPasswordInputMessage(undefined);
},
},
tests: [
{
onFailure: () => {
setAdminPasswordInputMessage({
children:
'Admin password cannot contain single-quote, double-quote, slash, backslash, angle brackets, and curly brackets.',
});
},
test: ({ value }) => !/['"/\\><}{]/g.test(value as string),
},
],
},
confirmAdminPassword: {
defaults: {
onSuccess: () => {
setConfirmAdminPasswordInputMessage(undefined);
},
},
tests: [
{
onFailure: () => {
setConfirmAdminPasswordInputMessage({
children: 'Admin password confirmation failed.',
});
},
test: ({ value, compare }) => value === compare,
},
],
},
domainName: {
defaults: {
onSuccess: () => {
setDomainNameInputMessage(undefined);
},
},
tests: [
{
onFailure: () => {
setDomainNameInputMessage({
children:
'Domain name can only contain lowercase alphanumeric, hyphen, and decimal characters.',
});
},
test: ({ value }) => REP_DN_CHAR.test(value as string),
},
],
},
hostName: {
defaults: {
onSuccess: () => {
setHostNameInputMessage(undefined);
},
},
tests: [
{
onFailure: () => {
setHostNameInputMessage({
children:
'Host name can only contain lowercase alphanumeric, hyphen, and decimal characters.',
});
},
test: ({ value }) => REP_DN_CHAR.test(value as string),
},
],
},
hostNumber: {
defaults: {
onSuccess: () => {
setHostNumberInputMessage(undefined);
},
},
tests: [
{
onFailure: () => {
setHostNumberInputMessage({
children: 'Host number can only contain digits.',
});
},
test: ({ value }) => /^\d+$/.test(value as string),
},
],
},
organizationName: {
tests: [{ test: testNotBlank }],
},
organizationPrefix: {
defaults: {
onSuccess: () => {
setOrganizationPrefixInputMessage(undefined);
},
},
tests: [
{
onFailure: ({ max, min }) => {
setOrganizationPrefixInputMessage({
children: `Organization prefix must be ${min} to ${max} characters.`,
});
},
test: testLength,
},
{
onFailure: () => {
setOrganizationPrefixInputMessage({
children:
'Organization prefix can only contain lowercase alphanumeric characters.',
});
},
test: ({ value }) => /^[a-z0-9]+$/.test(value as string),
},
],
},
}),
[
setAdminPasswordInputMessage,
setConfirmAdminPasswordInputMessage,
setDomainNameInputMessage,
setHostNameInputMessage,
setHostNumberInputMessage,
setOrganizationPrefixInputMessage,
],
);
useImperativeHandle(ref, () => ({
get: () => ({
adminPassword: adminPasswordInputRef.current.getValue?.call(null),
@ -215,9 +417,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
inputProps={{
onBlur: populateOrganizationPrefixInputOnBlur,
}}
inputLabelProps={{ isNotifyRequired: true }}
label="Organization name"
onChange={({ target: { value } }) => {
testInput({
inputs: { organizationName: { value } },
tests: inputTests,
});
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.',
),
@ -254,14 +463,26 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
},
onBlur: populateHostNameInputOnBlur,
}}
inputLabelProps={{ isNotifyRequired: true }}
label="Prefix"
onChange={() => {
onChange={({ target: { value } }) => {
testInput({
inputs: {
organizationPrefix: {
max: MAX_ORGANIZATION_PREFIX_LENGTH,
min: MIN_ORGANIZATION_PREFIX_LENGTH,
value,
},
},
tests: inputTests,
});
setIsShowOrganizationPrefixSuggest(
isOrganizationPrefixPrereqFilled(),
);
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
"Alphanumberic short-form of the organization name. It's used as the prefix for host names.",
),
@ -284,9 +505,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
},
onBlur: populateHostNameInputOnBlur,
}}
label="Host #"
inputLabelProps={{ isNotifyRequired: true }}
label="Striker #"
onChange={({ target: { value } }) => {
testInput({
inputs: { hostNumber: { value } },
tests: inputTests,
});
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
"Number or count of this striker; this should be '1' for the first striker, '2' for the second striker, and such.",
),
@ -309,9 +537,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
inputProps={{
onBlur: populateHostNameInputOnBlur,
}}
inputLabelProps={{ isNotifyRequired: true }}
label="Domain name"
onChange={({ target: { value } }) => {
testInput({
inputs: { domainName: { value } },
tests: inputTests,
});
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
"Domain name for this striker. It's also the default domain used when creating new install manifests.",
),
@ -334,12 +569,18 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
/>
),
}}
inputLabelProps={{ isNotifyRequired: true }}
label="Host name"
onChange={() => {
onChange={({ target: { value } }) => {
testInput({
inputs: { hostName: { value } },
tests: inputTests,
});
setIsShowHostNameSuggest(isHostNamePrereqFilled());
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
"Host name for this striker. It's usually a good idea to use the auto-generated value.",
),
@ -369,12 +610,24 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
id="striker-init-general-admin-password"
inputProps={{
inputProps: {
type: 'password',
type: INPUT_TYPES.password,
},
onPasswordVisibilityAppend: (inputType) => {
setIsConfirmAdminPassword(
inputType === INPUT_TYPES.password,
);
},
}}
inputLabelProps={{ isNotifyRequired: true }}
label="Admin password"
onChange={({ target: { value } }) => {
testInput({
inputs: { adminPassword: { value } },
tests: inputTests,
});
}}
onHelp={() => {
setHelpText(
setHelpMessage(
buildHelpMessage(
"Password use to login to this Striker and connect to its database. Don't provide an used password here because it'll be stored as plaintext.",
),
@ -385,29 +638,49 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
ref={adminPasswordInputRef}
/>
</MUIGrid>
<MUIGrid item xs={1}>
<InputWithRef
input={
<OutlinedInputWithLabel
id="striker-init-general-confirm-admin-password"
inputProps={{
inputProps: {
type: 'password',
},
}}
label="Confirm password"
/>
}
ref={confirmAdminPasswordInputRef}
/>
</MUIGrid>
{isConfirmAdminPassword && (
<MUIGrid item xs={1}>
<InputWithRef
input={
<OutlinedInputWithLabel
id="striker-init-general-confirm-admin-password"
inputProps={{
inputProps: {
type: INPUT_TYPES.password,
},
}}
inputLabelProps={{
isNotifyRequired: isConfirmAdminPassword,
}}
label="Confirm password"
onChange={({ target: { value } }) => {
testInput({
inputs: {
confirmAdminPassword: {
value,
compare:
adminPasswordInputRef.current.getValue?.call(
null,
),
},
},
tests: inputTests,
});
}}
/>
}
ref={confirmAdminPasswordInputRef}
/>
</MUIGrid>
)}
</MUIGrid>
</MUIGrid>
</MUIGrid>
{createInputTestMessages()}
{helpMessage && (
<MessageBox
onClose={() => {
setHelpText(undefined);
setHelpMessage(undefined);
}}
>
{helpMessage}

Loading…
Cancel
Save