Fakultas Ilmu Komputer UI

Commit b25b507d authored by Bagus Prabowo's avatar Bagus Prabowo
Browse files

Chore: Form validator

parent d4dc9533
......@@ -3,6 +3,8 @@ import { View, StyleSheet, TextInput, TouchableOpacity } from "react-native";
import Colors from "../../constants/Colors";
import { Text } from "../Themed";
import { MaterialIcons } from "@expo/vector-icons";
import { validateEmpty } from "../../helpers/Validators";
import Spacer from "../Spacer/Spacer";
type props = {
formTitle?: string;
......@@ -10,9 +12,10 @@ type props = {
setText: React.Dispatch<React.SetStateAction<string>>;
placeholder: string;
disabled?: boolean;
validator?: (a: string) => boolean;
iconType: React.ComponentProps<typeof MaterialIcons>["name"];
iconType?: React.ComponentProps<typeof MaterialIcons>["name"];
password?: boolean;
search?: boolean;
errorMessage?: string;
};
const IconForm = ({
......@@ -23,9 +26,13 @@ const IconForm = ({
disabled = false,
iconType,
password = false,
search = false,
errorMessage,
}: props) => {
const [isFocused, setIsFocused] = useState(false);
const [showPass, setShowPass] = useState(password);
const [isError, setIsError] = useState(false);
let backgroundColor: string;
if (disabled) {
backgroundColor = Colors.form.disabled.bg;
......@@ -33,6 +40,44 @@ const IconForm = ({
backgroundColor =
isFocused || text ? Colors.form.filled.bg : Colors.form.empty.bg;
}
let borderColor: string;
if (isFocused) {
borderColor = Colors.form.active.border;
} else if (!isFocused) {
if (isError) {
borderColor = Colors.button.warning.bg;
} else {
borderColor = Colors.form.filled.border;
}
}
const endEdit = () => {
setIsFocused(false);
if (search || disabled) {
setIsError(false);
} else {
if (!validateEmpty(text)) {
setIsError(true);
} else {
setIsError(false);
}
}
};
let iconName: React.ComponentProps<typeof MaterialIcons>["name"];
if (search) {
iconName = "search";
} else if (password) {
if (showPass) {
iconName = "visibility";
} else if (!showPass) {
iconName = "visibility-off";
}
} else {
iconName = iconType;
}
return (
<View style={styles.container} testID="Icon">
{formTitle.length > 0 && (
......@@ -46,9 +91,7 @@ const IconForm = ({
style={{
...styles.formContainer,
backgroundColor: backgroundColor,
borderColor: isFocused
? Colors.form.active.border
: Colors.form.filled.border,
borderColor: borderColor,
shadowColor: isFocused
? "rgba(7, 145, 249, 0.12)"
: "rgba(0, 0, 0, 0)",
......@@ -63,7 +106,7 @@ const IconForm = ({
<View style={styles.textContainer} testID="TextIcon">
<TextInput
onFocus={() => setIsFocused(true)}
onEndEditing={() => setIsFocused(false)}
onEndEditing={endEdit}
placeholder={placeholder}
value={text}
onChangeText={(n: string): void => setText(n)}
......@@ -72,6 +115,7 @@ const IconForm = ({
editable={!disabled}
secureTextEntry={showPass}
testID="Input"
autoCapitalize={password ? "none" : "sentences"}
/>
</View>
<TouchableOpacity
......@@ -79,15 +123,21 @@ const IconForm = ({
testID="Touchable"
>
<MaterialIcons
name={
password ? (showPass ? "visibility" : "visibility-off") : iconType
}
name={iconName}
size={21}
color={Colors.text.disabled}
testID="MaterialIcon"
/>
</TouchableOpacity>
</View>
{isError && (
<View testID="ErrorContainer">
<Spacer variant={"s"} />
<Text style={styles.errorMessage} testID="ErrorMessage">
{errorMessage}
</Text>
</View>
)}
</View>
);
};
......@@ -127,6 +177,12 @@ const styles = StyleSheet.create({
textContainer: {
flex: 1,
},
errorMessage: {
fontSize: 14,
fontWeight: "300",
textAlign: "left",
color: Colors.button.warning.bg,
},
});
export default IconForm;
......@@ -2,6 +2,8 @@ import React, { useState } from "react";
import { View, StyleSheet, TextInput } from "react-native";
import Colors from "../../constants/Colors";
import { Text } from "../Themed";
import { validateEmail, validateEmpty } from "../../helpers/Validators";
import Spacer from "../Spacer/Spacer";
type props = {
formTitle?: string;
......@@ -10,6 +12,8 @@ type props = {
placeholder: string;
disabled?: boolean;
phone?: boolean;
email?: boolean;
errorMessage?: string;
};
const PlainForm = ({
......@@ -19,8 +23,12 @@ const PlainForm = ({
placeholder,
disabled = false,
phone = false,
errorMessage,
email = false,
}: props) => {
const [isFocused, setIsFocused] = useState(false);
const [isError, setIsError] = useState(false);
let backgroundColor: string;
if (disabled) {
backgroundColor = Colors.form.disabled.bg;
......@@ -28,6 +36,39 @@ const PlainForm = ({
backgroundColor =
isFocused || text ? Colors.form.filled.bg : Colors.form.empty.bg;
}
let borderColor: string;
if (isFocused) {
borderColor = Colors.form.active.border;
} else if (!isFocused) {
if (isError) {
borderColor = Colors.button.warning.bg;
} else {
borderColor = Colors.form.filled.border;
}
}
const endEdit = () => {
setIsFocused(false);
if (disabled) {
setIsError(false);
} else {
if (email) {
if (!validateEmail(text)) {
setIsError(true);
} else {
setIsError(false);
}
} else {
if (!validateEmpty(text)) {
setIsError(true);
} else {
setIsError(false);
}
}
}
};
return (
<View style={styles.container} testID="Plain">
{formTitle.length > 0 && (
......@@ -41,9 +82,7 @@ const PlainForm = ({
style={{
...styles.formContainer,
backgroundColor: backgroundColor,
borderColor: isFocused
? Colors.form.active.border
: Colors.form.filled.border,
borderColor: borderColor,
shadowColor: isFocused
? "rgba(7, 145, 249, 0.12)"
: "rgba(0, 0, 0, 0)",
......@@ -57,7 +96,7 @@ const PlainForm = ({
>
<TextInput
onFocus={() => setIsFocused(true)}
onEndEditing={() => setIsFocused(false)}
onEndEditing={endEdit}
placeholder={placeholder}
value={text}
onChangeText={(n: string): void => setText(n)}
......@@ -66,8 +105,17 @@ const PlainForm = ({
editable={!disabled}
keyboardType={phone ? "phone-pad" : "default"}
testID="Input"
autoCapitalize={email ? "none" : "sentences"}
/>
</View>
{isError && (
<View testID="ErrorContainer">
<Spacer variant={"s"} />
<Text style={styles.errorMessage} testID="ErrorMessage">
{errorMessage}
</Text>
</View>
)}
</View>
);
};
......@@ -98,6 +146,12 @@ const styles = StyleSheet.create({
textAlign: "left",
color: Colors.text.subtitle,
},
errorMessage: {
fontSize: 14,
fontWeight: "300",
textAlign: "left",
color: Colors.button.warning.bg,
},
});
export default PlainForm;
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import React, { useState } from "react";
import { cleanup, render, fireEvent } from "@testing-library/react-native";
import IconForm from "../Forms/IconForm";
afterEach(cleanup);
const IconFormWrapper = (props) => {
const [text, setText] = useState("Hello");
return (
<IconForm text={text} setText={setText} iconType="search" {...props} />
);
};
describe("Icon Form Test", () => {
it("Should detect a form", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("Icon")).not.toBeNull();
});
it("Should detect a title container", () => {
const { getByTestId } = render(<IconFormWrapper formTitle={"Title"} />);
expect(getByTestId("TitleContainer")).not.toBeNull();
});
it("Should detect a title if given", () => {
const { getByTestId } = render(<IconFormWrapper formTitle={"Title"} />);
expect(getByTestId("Title")).not.toBeNull();
});
it("Should detect a form container", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("Container")).not.toBeNull();
});
it("Should detect a container for input and icon", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("TextIcon")).not.toBeNull();
});
it("Should detect a text input", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("Input")).not.toBeNull();
});
it("Should detect a touchable", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("Touchable")).not.toBeNull();
});
it("Should detect a MaterialIcon", () => {
const { getByTestId } = render(<IconFormWrapper />);
expect(getByTestId("MaterialIcon")).not.toBeNull();
});
it("Should not detect a container title", () => {
const { queryByTestId } = render(<IconFormWrapper />);
expect(queryByTestId("TitleContainer")).toBeNull();
});
it("Should not detect a form title", () => {
const { queryByTestId } = render(<IconFormWrapper />);
expect(queryByTestId("Title")).toBeNull();
});
it("Should detect a send form text", () => {
const { getByTestId } = render(<IconFormWrapper />);
fireEvent.changeText(getByTestId("Input"), "Hello");
expect(getByTestId("Input").props.value).toEqual("Hello");
});
});
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import React, { useState } from "react";
import { cleanup, render, fireEvent } from "@testing-library/react-native";
import PlainForm from "../Forms/PlainForm";
afterEach(cleanup);
const PlainFormWrapper = (props) => {
const [text, setText] = useState("Hello");
return <PlainForm text={text} setText={setText} {...props} />;
};
describe("Plain Form Test", () => {
it("Should detect a form", () => {
const { getByTestId } = render(<PlainFormWrapper />);
expect(getByTestId("Plain")).not.toBeNull();
});
it("Should detect a title container", () => {
const { getByTestId } = render(<PlainFormWrapper formTitle={"Title"} />);
expect(getByTestId("TitleContainer")).not.toBeNull();
});
it("Should detect a title if given", () => {
const { getByTestId } = render(<PlainFormWrapper formTitle={"Title"} />);
expect(getByTestId("Title")).not.toBeNull();
});
it("Should detect a form container", () => {
const { getByTestId } = render(<PlainFormWrapper />);
expect(getByTestId("Container")).not.toBeNull();
});
it("Should detect a text input", () => {
const { getByTestId } = render(<PlainFormWrapper />);
expect(getByTestId("Input")).not.toBeNull();
});
it("Should not detect a container title", () => {
const { queryByTestId } = render(<PlainFormWrapper />);
expect(queryByTestId("TitleContainer")).toBeNull();
});
it("Should not detect a form title", () => {
const { queryByTestId } = render(<PlainFormWrapper />);
expect(queryByTestId("Title")).toBeNull();
});
it("Should detect a send form text", () => {
const { getByTestId } = render(<PlainFormWrapper />);
fireEvent.changeText(getByTestId("Input"), "Hello");
expect(getByTestId("Input").props.value).toEqual("Hello");
});
});
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import React from "react";
import { cleanup, render } from "@testing-library/react-native";
import Spacer from "../Spacer/Spacer";
afterEach(cleanup);
const SpacerWrapper = () => {
return <Spacer variant="s" />;
};
describe("Spacer Test", () => {
it("Should detect a spacer", () => {
const { getByTestId } = render(<SpacerWrapper />);
expect(getByTestId("Spacer")).not.toBeNull();
});
});
import * as React from "react";
import renderer from "react-test-renderer";
import { MonoText } from "../StyledText";
it(`renders correctly`, () => {
const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
expect(tree).toMatchSnapshot();
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
<Text
style={
Array [
Array [
undefined,
Object {
"fontFamily": "space-mono",
},
],
]
}
>
Snapshot test!
</Text>
`;
import React from "react";
import { Text, View, StyleSheet, TouchableOpacity } from "react-native";
import { Text, StyleSheet, TouchableOpacity } from "react-native";
import Colors from "../../constants/Colors";
type props = {
......
export const validateEmail = (email: string): boolean => {
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
export const validateEmpty = (str: string): boolean => {
if (str.length > 0) {
return true;
} else {
return false;
}
};
......@@ -8,6 +8,7 @@ import OnBoardingScreen from "../screens/auth/OnBoardingScreen";
import LandingScreen from "../screens/auth/LandingScreen";
import ForgotPasswordInput from "../screens/auth/ForgotPasswordDone";
import ForgotPasswordDone from "../screens/auth/ForgotPasswordDone";
import InputForgotPassword from "../screens/auth/InputForgotPassword";
const AuthStack = createNativeStackNavigator<AuthStackParamList>();
const AuthStackNavigator = () => {
......@@ -48,6 +49,11 @@ const AuthStackNavigator = () => {
component={ForgotPasswordDone}
options={{ title: "Forgot Password Done" }}
/>
<AuthStack.Screen
name="InputForgotPassword"
component={InputForgotPassword}
options={{ title: "Forgot Password Input" }}
/>
</AuthStack.Navigator>
);
};
......
......@@ -26,11 +26,13 @@ export default function ForgotPasswordDone({
</Text>
</View>
<Spacer variant="xl" />
<MainButton
text={"Kembali"}
colors={"Primary"}
onPress={() => nav.navigate("Auth", { screen: "Login" })}
/>
<View style={styles.componentWrapper}>
<MainButton
text={"Kembali"}
colors={"Primary"}
onPress={() => nav.navigate("Auth", { screen: "Login" })}
/>
</View>
</View>
);
}
......@@ -53,4 +55,7 @@ const styles = StyleSheet.create({
fontWeight: "300",
color: Colors.text.subtitle,
},
componentWrapper: {
width: "100%",
},
});
......@@ -9,8 +9,9 @@ import Spacer from "../../components/Spacer/Spacer";
import { MaterialIcons } from "@expo/vector-icons";
import MainButton from "../../components/button/MainButton";
import firebase from "firebase";
import { validateEmail } from "../../helpers/Validators";
export default function ForgotPasswordInput({
export default function InputForgotPassword({
navigation,
}: RootTabScreenProps<"TabOne">) {
const [email, setEmail] = useState("");
......@@ -29,20 +30,29 @@ export default function ForgotPasswordInput({
</Text>
</View>
<Spacer variant="xl" />
<PlainForm
formTitle={"Email"}
placeholder={"Masukkan Email"}
text={email}
setText={setEmail}
/>
<View style={styles.componentWrapper}>
<PlainForm
formTitle={"Email"}
placeholder={"Masukkan Email"}
text={email}
setText={setEmail}
errorMessage={"Silahkan masukkan email yang benar"}
email
/>
</View>
<Spacer variant="xl" />
<MainButton
text={"Reset Password"}
colors={"Primary"}
onPress={() => {
firebase.auth().sendPasswordResetEmail(email);
}}
/>
<View style={styles.componentWrapper}>
<MainButton
text={"Reset Password"}
colors={"Primary"}
onPress={() => {
if (validateEmail(email)) {
firebase.auth().sendPasswordResetEmail(email);
nav.navigate("Auth", { screen: "ForgotPasswordDone" });
}
}}
/>
</View>
</View>
);
}
......@@ -65,4 +75,7 @@ const styles = StyleSheet.create({
fontWeight: "300",
color: Colors.text.subtitle,
},
componentWrapper: {
width: "100%",
},
});
......@@ -16,44 +16,50 @@ const LoginScreen = ({ navigation }: RootTabScreenProps<"TabOne">) => {
const [password, setPassword] = useState("");
return (
<View style={styles.container}>
<View style={styles.textWrapper}>
<View style={styles.componentWrapper}>
<Text style={styles.title}>Halo!</Text>
</View>
<Spacer variant={"xl"} />
<View style={[styles.textWrapper, { paddingRight: 44 }]}>
<View style={[styles.componentWrapper, { paddingRight: 44 }]}>
<Text style={styles.subtitle}>
Silahkan masukan email anda untuk masuk ke Click
</Text>
</View>
<Spacer variant={"xl"} />
<PlainForm
formTitle={"Email"}
text={email}
setText={setEmail}
placeholder={"Masukkan Email"}
/>
<View style={styles.componentWrapper}>
<PlainForm
formTitle={"Email"}
text={email}
setText={setEmail}
placeholder={"Masukkan Email"}
email
errorMessage={"Silahkan masukkan email yang benar"}
/>
</View>
<Spacer variant={"l"} />