mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce unionizing functions for input validators
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
87b2c39f67
commit
8ecd19701f
@ -9,12 +9,13 @@ import { prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { observer } from "mobx-react";
|
||||
import { asyncInputValidator, Input, InputValidators } from "../input";
|
||||
import { Input, InputValidators } from "../input";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { TooltipPosition } from "../tooltip";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import { unionInputValidatorsAsync } from "../input/input_validators";
|
||||
|
||||
export interface InstallProps {
|
||||
installPath: string;
|
||||
@ -28,23 +29,14 @@ interface Dependencies {
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
const installInputValidator = asyncInputValidator({
|
||||
validate: async (value) => {
|
||||
if (
|
||||
InputValidators.isUrl.validate(value)
|
||||
|| InputValidators.isExtensionNameInstall.validate(value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return await InputValidators.isPath.validate(value);
|
||||
} catch {
|
||||
throw new Error("Invalid URL, absolute path, or extension name");
|
||||
}
|
||||
const installInputValidator = unionInputValidatorsAsync(
|
||||
{
|
||||
message: "Invalid URL, absolute path, or extension name",
|
||||
},
|
||||
debounce: InputValidators.isPath.debounce,
|
||||
});
|
||||
InputValidators.isUrl,
|
||||
InputValidators.isExtensionNameInstall,
|
||||
InputValidators.isPath,
|
||||
);
|
||||
|
||||
const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
|
||||
installPath,
|
||||
|
||||
@ -3,11 +3,86 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { isEmail, isUrl, systemName } from "../input_validators";
|
||||
import { isEmail, isUrl, systemName, unionInputValidators, unionInputValidatorsAsync } from "../input_validators";
|
||||
|
||||
type TextValidationCase = [string, boolean];
|
||||
|
||||
describe("input validation tests", () => {
|
||||
describe("unionInputValidators()", () => {
|
||||
const emailOrUrl = unionInputValidators(
|
||||
{
|
||||
message: "Not an email or URL",
|
||||
},
|
||||
isEmail,
|
||||
isUrl,
|
||||
);
|
||||
|
||||
it.each([
|
||||
"abc@news.com",
|
||||
"abc@news.co.uk",
|
||||
])("Given '%s' is a valid email, emailOrUrl matches", (input) => {
|
||||
expect(emailOrUrl.validate(input)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"https://github-production-registry-package-file-4f11e5.s3.amazonaws.com/307985088/68bbbf00-309f-11eb-8457-a15e4efe9e77?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201127%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201127T123754Z&X-Amz-Expires=300&X-Amz-Signature=9b8167f00685a20d980224d397892195abc187cdb2934cefb79edcd7ec600f78&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=0&response-content-disposition=filename%3Dstarboard-lens-extension-0.0.1-alpha.1-npm.tgz&response-content-type=application%2Foctet-stream",
|
||||
"http://www.google.com",
|
||||
])("Given '%s' is a valid url, emailOrUrl matches", (input) => {
|
||||
expect(emailOrUrl.validate(input)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"hello",
|
||||
"57",
|
||||
])("Given '%s' is neither a valid email nor URL, emailOrUrl does not match", (input) => {
|
||||
expect(emailOrUrl.validate(input)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unionInputValidatorsAsync()", () => {
|
||||
const emailOrUrl = unionInputValidatorsAsync(
|
||||
{
|
||||
message: "Not an email or URL",
|
||||
},
|
||||
isEmail,
|
||||
isUrl,
|
||||
);
|
||||
|
||||
it.each([
|
||||
"abc@news.com",
|
||||
"abc@news.co.uk",
|
||||
])("Given '%s' is a valid email, emailOrUrl matches", async (input) => {
|
||||
try {
|
||||
await emailOrUrl.validate(input);
|
||||
} catch {
|
||||
fail("Should not throw on valid input");
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
"https://github-production-registry-package-file-4f11e5.s3.amazonaws.com/307985088/68bbbf00-309f-11eb-8457-a15e4efe9e77?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201127%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201127T123754Z&X-Amz-Expires=300&X-Amz-Signature=9b8167f00685a20d980224d397892195abc187cdb2934cefb79edcd7ec600f78&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=0&response-content-disposition=filename%3Dstarboard-lens-extension-0.0.1-alpha.1-npm.tgz&response-content-type=application%2Foctet-stream",
|
||||
"http://www.google.com",
|
||||
])("Given '%s' is a valid url, emailOrUrl matches", async (input) => {
|
||||
try {
|
||||
await emailOrUrl.validate(input);
|
||||
} catch {
|
||||
fail("Should not throw on valid input");
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
"hello",
|
||||
"57",
|
||||
])("Given '%s' is neither a valid email nor URL, emailOrUrl does not match", async (input) => {
|
||||
try {
|
||||
await emailOrUrl.validate(input);
|
||||
fail("Should throw on invalid input");
|
||||
} catch {
|
||||
// We want this to happen
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("isEmail tests", () => {
|
||||
const tests: TextValidationCase[] = [
|
||||
["abc@news.com", true],
|
||||
|
||||
@ -16,13 +16,23 @@ import type { InputValidator, InputValidation, InputValidationResult, SyncValida
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
const { conditionalValidators, asyncInputValidator, inputValidator, inputValidatorWithRequiredProps, ...InputValidators } = Validators;
|
||||
const {
|
||||
conditionalValidators,
|
||||
asyncInputValidator,
|
||||
inputValidator,
|
||||
inputValidatorWithRequiredProps,
|
||||
isAsyncValidator,
|
||||
unionInputValidatorsAsync,
|
||||
...InputValidators
|
||||
} = Validators;
|
||||
|
||||
export {
|
||||
InputValidators,
|
||||
asyncInputValidator,
|
||||
inputValidator,
|
||||
inputValidatorWithRequiredProps,
|
||||
isAsyncValidator,
|
||||
unionInputValidatorsAsync,
|
||||
};
|
||||
export type {
|
||||
InputValidator,
|
||||
@ -87,10 +97,6 @@ const defaultProps: Partial<InputProps> = {
|
||||
blurOnEnter: true,
|
||||
};
|
||||
|
||||
function isAsyncValidator<RequireProps extends boolean>(validator: InputValidator<boolean, RequireProps>): validator is InputValidator<true, RequireProps> {
|
||||
return typeof validator.debounce === "number";
|
||||
}
|
||||
|
||||
export class Input extends React.Component<InputProps, State> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import type { InputProps } from "./input";
|
||||
import type React from "react";
|
||||
import fse from "fs-extra";
|
||||
import { TypedRegEx } from "typed-regex";
|
||||
import type { SetRequired } from "type-fest";
|
||||
|
||||
export type InputValidationResult<IsAsync extends boolean> =
|
||||
IsAsync extends true
|
||||
@ -48,6 +49,10 @@ export type InputValidator<IsAsync extends boolean = boolean, RequireProps exten
|
||||
}
|
||||
);
|
||||
|
||||
export function isAsyncValidator<RequireProps extends boolean>(validator: InputValidator<boolean, RequireProps>): validator is InputValidator<true, RequireProps> {
|
||||
return typeof validator.debounce === "number";
|
||||
}
|
||||
|
||||
export function asyncInputValidator(validator: InputValidator<true, false>): InputValidator<true, false> {
|
||||
return validator;
|
||||
}
|
||||
@ -60,6 +65,64 @@ export function inputValidatorWithRequiredProps(validator: InputValidator<false,
|
||||
return validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new input validator from a list of syncronous input validators. Will match as valid if
|
||||
* one of the input validators matches the input
|
||||
*/
|
||||
export function unionInputValidators(
|
||||
baseValidator: Pick<InputValidator<false, false>, "condition" | "message">,
|
||||
...validators: InputValidator<false, false>[]
|
||||
): InputValidator<false, false> {
|
||||
return inputValidator({
|
||||
...baseValidator,
|
||||
validate: (value, props) => validators.some(validator => validator.validate(value, props)),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new input validator from a list of syncronous or async input validators. Will match as
|
||||
* valid if one of the input validators matches the input
|
||||
*/
|
||||
export function unionInputValidatorsAsync(
|
||||
baseValidator: SetRequired<Pick<InputValidator<boolean, false>, "condition" | "message">, "message">,
|
||||
...validators: InputValidator<boolean, false>[]
|
||||
): InputValidator<true, false> {
|
||||
const longestDebounce = Math.max(
|
||||
...validators
|
||||
.filter(isAsyncValidator)
|
||||
.map(validator => validator.debounce),
|
||||
0,
|
||||
);
|
||||
|
||||
return asyncInputValidator({
|
||||
debounce: longestDebounce,
|
||||
validate: async (value, props) => {
|
||||
for (const validator of validators) {
|
||||
if (isAsyncValidator(validator)) {
|
||||
try {
|
||||
await validator.validate(value, props);
|
||||
|
||||
return;
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
if (validator.validate(value, props)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If no validator returns `true` then mark as invalid by throwing. The message will be
|
||||
* obtained from the `message` field.
|
||||
*/
|
||||
throw {};
|
||||
},
|
||||
...baseValidator,
|
||||
});
|
||||
}
|
||||
|
||||
export const isRequired = inputValidator({
|
||||
condition: ({ required }) => required,
|
||||
message: () => `This field is required`,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user