Fakultas Ilmu Komputer UI
Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
ppl-fasilkom-ui
2021
Kelas D
PT Gizi Sehat - Dietela
Dietela Mobile
Commits
e51d405d
Commit
e51d405d
authored
Mar 28, 2021
by
Wulan Mantiri
Browse files
Implement Dietela Quiz form validation
parent
3dd7f403
Changes
26
Hide whitespace changes
Inline
Side-by-side
jest.config.js
View file @
e51d405d
module
.
exports
=
{
preset
:
'
react-native
'
,
moduleFileExtensions
:
[
'
ts
'
,
'
tsx
'
,
'
js
'
,
'
jsx
'
,
'
json
'
,
'
node
'
],
setupFiles
:
[
'
./node_modules/react-native-gesture-handler/jestSetup.js
'
],
setupFiles
:
[
'
./jestSetup.js
'
,
'
./node_modules/react-native-gesture-handler/jestSetup.js
'
,
],
moduleNameMapper
:
{
'
.+
\\
.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$
'
:
'
identity-obj-proxy
'
,
...
...
jestSetup.js
0 → 100644
View file @
e51d405d
import
{
jest
}
from
'
@jest/globals
'
;
jest
.
mock
(
'
react-native/Libraries/Animated/src/NativeAnimatedHelper
'
);
src/app/index.tsx
View file @
e51d405d
...
...
@@ -19,7 +19,7 @@ const App: FC = () => {
<
ThemeProvider
theme
=
{
theme
}
>
<
NavigationContainer
>
<
Stack
.
Navigator
initialRouteName
=
{
ROUTES
.
home
}
initialRouteName
=
{
ROUTES
.
initial
}
screenOptions
=
{
screenOptions
}
>
{
navigation
.
map
((
nav
,
i
)
=>
(
<
Stack
.
Screen
...
...
src/components/core/WizardContainer/index.test.tsx
View file @
e51d405d
...
...
@@ -6,37 +6,43 @@ import { Text } from 'react-native';
describe
(
'
WizardContainer component
'
,
()
=>
{
const
components
=
[<
Text
>
hai
</
Text
>,
<
Text
>
hello
</
Text
>,
<
Text
>
hei
</
Text
>];
let
currentStep
=
1
;
const
props
=
{
components
,
currentStep
,
setCurrentStep
:
(
v
:
typeof
currentStep
)
=>
(
currentStep
=
v
),
onFinish
:
jest
.
fn
(),
};
it
(
'
displays first component as default if step props is not given
'
,
()
=>
{
const
{
getByText
}
=
render
(<
WizardContainer
{
...
props
}
/>);
const
haiText
=
getByText
(
/hai/i
);
expect
(
haiText
).
toBe
Defined
();
expect
(
haiText
).
toBe
Truthy
();
});
it
(
'
can go back and forth when "Kembali" or "Lanjut" button is pressed
'
,
()
=>
{
const
{
getByText
}
=
render
(<
WizardContainer
{
...
props
}
/>);
const
{
getByText
,
rerender
}
=
render
(<
WizardContainer
{
...
props
}
/>);
const
nextButton
=
getByText
(
/Lanjut/i
);
fireEvent
.
press
(
nextButton
);
rerender
(<
WizardContainer
{
...
props
}
currentStep
=
{
currentStep
}
/>);
const
helloText
=
getByText
(
/hello/i
);
expect
(
helloText
).
toBe
Defined
();
expect
(
helloText
).
toBe
Truthy
();
const
backButton
=
getByText
(
/Kembali/i
);
expect
(
backButton
).
toBeDefined
();
expect
(
backButton
).
toBeTruthy
();
fireEvent
.
press
(
backButton
);
rerender
(<
WizardContainer
{
...
props
}
currentStep
=
{
currentStep
}
/>);
const
haiText
=
getByText
(
/hai/i
);
expect
(
haiText
).
toBe
Defined
();
expect
(
haiText
).
toBe
Truthy
();
});
it
(
'
shows finish button in the last component
'
,
()
=>
{
const
{
findByText
}
=
render
(
<
WizardContainer
{
...
props
}
s
tep
=
{
components
.
length
}
/>,
<
WizardContainer
{
...
props
}
currentS
tep
=
{
components
.
length
}
/>,
);
const
finishButton
=
findByText
(
/Selesai/i
);
expect
(
finishButton
).
toBe
Defined
();
expect
(
finishButton
).
toBe
Truthy
();
});
});
src/components/core/WizardContainer/index.tsx
View file @
e51d405d
import
React
,
{
FC
,
useState
,
useEffect
}
from
'
react
'
;
import
React
,
{
FC
}
from
'
react
'
;
import
{
ScrollView
,
View
}
from
'
react-native
'
;
import
{
Button
}
from
'
react-native-elements
'
;
import
{
colors
}
from
'
styles
'
;
...
...
@@ -25,16 +25,11 @@ const NextButton: FC<NextButtonProps> = ({ goNext }) => (
const
WizardContainer
:
FC
<
Props
>
=
({
components
,
step
,
currentStep
,
setCurrentStep
,
finishButtonLabel
,
onFinish
,
})
=>
{
const
[
currentStep
,
setCurrentStep
]
=
useState
(
1
);
useEffect
(()
=>
{
setCurrentStep
(
step
??
1
);
},
[
step
]);
const
goBack
=
()
=>
{
setCurrentStep
(
currentStep
-
1
);
};
...
...
src/components/core/WizardContainer/types.ts
View file @
e51d405d
...
...
@@ -2,9 +2,10 @@ import { ReactNode } from 'react';
export
interface
Props
{
components
:
ReactNode
[];
s
tep
?
:
number
;
currentS
tep
:
number
;
finishButtonLabel
?:
string
;
onFinish
:
()
=>
void
;
setCurrentStep
:
(
_
:
number
)
=>
void
;
}
export
interface
NextButtonProps
{
...
...
src/components/form/MultipleCheckbox/index.test.tsx
View file @
e51d405d
...
...
@@ -40,7 +40,7 @@ describe('MultipleCheckbox component', () => {
expect
(
value
).
toContain
(
2
);
});
it
(
'
is able to unselect checkbox
'
,
async
()
=>
{
it
(
'
is able to unselect checkbox
'
,
()
=>
{
let
value
:
Array
<
string
|
number
>
=
[
2
];
const
{
getByText
}
=
render
(
...
...
@@ -56,4 +56,18 @@ describe('MultipleCheckbox component', () => {
fireEvent
.
press
(
getByText
(
/Choice 2/i
));
expect
(
value
).
toStrictEqual
([]);
});
it
(
'
shows red helper text when there is error message props
'
,
()
=>
{
const
{
getByText
}
=
render
(
<
MultipleCheckbox
{
...
props
}
value
=
{
[]
}
onChange
=
{
jest
.
fn
()
}
errorMessage
=
"error"
/>,
);
const
helperText
=
getByText
(
/Pilih semua yang berlaku/i
);
expect
(
helperText
).
toBeTruthy
();
});
});
src/components/form/MultipleCheckbox/index.tsx
View file @
e51d405d
...
...
@@ -3,14 +3,15 @@ import { View } from 'react-native';
import
{
CheckBox
,
Text
}
from
'
react-native-elements
'
;
import
{
typographyStyles
}
from
'
styles
'
;
import
{
styles
}
from
'
./styles
'
;
import
{
Props
,
Choice
}
from
'
./types
'
;
import
{
styles
}
from
'
.
./MultipleChoice
/styles
'
;
import
{
Props
,
Choice
}
from
'
.
./MultipleChoice
/types
'
;
const
MultipleCheckbox
:
FC
<
Props
>
=
({
questionNumber
,
totalQuestions
,
questionLabel
,
helperText
,
errorMessage
,
choices
,
value
,
onChange
,
...
...
@@ -31,7 +32,12 @@ const MultipleCheckbox: FC<Props> = ({
<
Text
style
=
{
[
typographyStyles
.
headingLarge
,
styles
.
spacing
]
}
>
{
questionLabel
}
</
Text
>
<
Text
style
=
{
[
typographyStyles
.
bodyMedium
,
styles
.
bigSpacing
]
}
>
<
Text
style
=
{
[
typographyStyles
.
bodyMedium
,
styles
.
bigSpacing
,
errorMessage
?
styles
.
red
:
null
,
]
}
>
{
helperText
||
'
Pilih semua yang berlaku
'
}
</
Text
>
{
choices
.
map
((
choice
)
=>
(
...
...
src/components/form/MultipleCheckbox/styles.ts
deleted
100644 → 0
View file @
3dd7f403
import
{
StyleSheet
}
from
'
react-native
'
;
export
const
styles
=
StyleSheet
.
create
({
spacing
:
{
marginBottom
:
14
,
},
bigSpacing
:
{
marginBottom
:
24
,
},
});
src/components/form/MultipleCheckbox/types.ts
deleted
100644 → 0
View file @
3dd7f403
import
{
Choice
}
from
'
../MultipleChoice/types
'
;
export
type
{
Choice
};
export
interface
Props
{
questionNumber
:
number
;
totalQuestions
:
number
;
questionLabel
:
string
;
helperText
?:
string
;
choices
:
Choice
[];
value
:
any
;
onChange
:
(
_
:
any
)
=>
void
;
}
src/components/form/MultipleChoice/index.test.tsx
View file @
e51d405d
...
...
@@ -33,4 +33,13 @@ describe('MultipleChoice component', () => {
fireEvent
.
press
(
getByText
(
/Choice 2/i
));
expect
(
value
).
toEqual
(
2
);
});
it
(
'
shows red helper text when there is error message props
'
,
()
=>
{
const
{
getByText
}
=
render
(
<
MultipleChoice
{
...
props
}
errorMessage
=
"error"
/>,
);
const
helperText
=
getByText
(
/Pilih satu yang paling cocok/i
);
expect
(
helperText
).
toBeTruthy
();
});
});
src/components/form/MultipleChoice/index.tsx
View file @
e51d405d
...
...
@@ -12,6 +12,7 @@ const MultipleChoice: FC<Props> = ({
totalQuestions
,
questionLabel
,
helperText
,
errorMessage
,
choices
,
value
,
onChange
,
...
...
@@ -21,9 +22,14 @@ const MultipleChoice: FC<Props> = ({
Pertanyaan
{
questionNumber
}
/
{
totalQuestions
}
</
Text
>
<
Text
style
=
{
[
typographyStyles
.
headingLarge
,
styles
.
spacing
]
}
>
{
questionLabel
}
{
questionLabel
}
<
Text
style
=
{
styles
.
red
}
>
*
</
Text
>
</
Text
>
<
Text
style
=
{
[
typographyStyles
.
bodyMedium
,
styles
.
bigSpacing
]
}
>
<
Text
style
=
{
[
typographyStyles
.
bodyMedium
,
styles
.
bigSpacing
,
errorMessage
?
styles
.
red
:
null
,
]
}
>
{
helperText
||
'
Pilih satu yang paling cocok
'
}
</
Text
>
{
choices
.
map
((
choice
)
=>
(
...
...
src/components/form/MultipleChoice/styles.ts
View file @
e51d405d
...
...
@@ -7,4 +7,7 @@ export const styles = StyleSheet.create({
bigSpacing
:
{
marginBottom
:
24
,
},
red
:
{
color
:
'
red
'
,
},
});
src/components/form/MultipleChoice/types.ts
View file @
e51d405d
...
...
@@ -8,8 +8,8 @@ export interface Props {
totalQuestions
:
number
;
questionLabel
:
string
;
helperText
?:
string
;
errorMessage
?:
any
;
choices
:
Choice
[];
value
:
any
;
onChange
:
(
_
:
any
)
=>
void
;
onPress
?:
()
=>
void
;
}
src/components/form/RadioButton/index.test.tsx
View file @
e51d405d
...
...
@@ -36,4 +36,14 @@ describe('RadioButtonGroup component', () => {
fireEvent
.
press
(
getByText
(
/Choice 2/i
));
expect
(
value
).
toEqual
(
2
);
});
it
(
'
shows error message when there is error message props
'
,
()
=>
{
const
errorMessage
=
'
error
'
;
const
{
getByText
}
=
render
(
<
RadioButtonGroup
{
...
props
}
errorMessage
=
{
errorMessage
}
/>,
);
const
errorText
=
getByText
(
errorMessage
);
expect
(
errorText
).
toBeTruthy
();
});
});
src/components/form/RadioButton/index.tsx
View file @
e51d405d
import
React
,
{
FC
}
from
'
react
'
;
import
{
View
}
from
'
react-native
'
;
import
{
CheckBox
,
CheckBoxProps
}
from
'
react-native-elements
'
;
import
{
Text
,
CheckBox
,
CheckBoxProps
}
from
'
react-native-elements
'
;
import
{
styles
}
from
'
./styles
'
;
import
FormLabel
from
'
../FormLabel
'
;
import
{
RadioButtonGroupProps
}
from
'
./types
'
;
import
{
typographyStyles
}
from
'
styles
'
;
const
RadioButton
:
FC
<
CheckBoxProps
>
=
(
props
)
=>
(
<
CheckBox
{
...
props
}
checkedIcon
=
"dot-circle-o"
uncheckedIcon
=
"circle-o"
/>
...
...
@@ -14,6 +15,7 @@ export const RadioButtonGroup: FC<RadioButtonGroupProps> = ({
choices
,
label
,
required
,
errorMessage
,
value
,
onChange
,
})
=>
(
...
...
@@ -30,6 +32,9 @@ export const RadioButtonGroup: FC<RadioButtonGroupProps> = ({
/>
))
}
</
View
>
{
errorMessage
?
(
<
Text
style
=
{
[
typographyStyles
.
caption
,
styles
.
red
]
}
>
{
errorMessage
}
</
Text
>
)
:
null
}
</
View
>
);
...
...
src/components/form/RadioButton/styles.ts
View file @
e51d405d
...
...
@@ -10,4 +10,7 @@ export const styles = StyleSheet.create({
radioButton
:
{
flex
:
0.48
,
},
red
:
{
color
:
'
red
'
,
},
});
src/components/form/RadioButton/types.ts
View file @
e51d405d
...
...
@@ -3,6 +3,7 @@ import { Choice } from '../MultipleChoice/types';
export
interface
RadioButtonGroupProps
extends
FormLabelProps
{
choices
:
Choice
[];
errorMessage
?:
any
;
value
:
any
;
onChange
:
(
_
:
any
)
=>
void
;
}
src/components/form/TextField/types.ts
View file @
e51d405d
...
...
@@ -2,4 +2,5 @@ import { InputProps } from 'react-native-elements';
export
interface
Props
extends
InputProps
{
required
?:
boolean
;
errorMessage
?:
any
;
}
src/hooks/useForm/index.ts
View file @
e51d405d
import
{
FormikConfig
,
useFormik
}
from
'
formik
'
;
import
{
useState
}
from
'
react
'
;
const
useForm
=
<
T
>
(
formConfig
:
FormikConfig
<
T
>
)
=>
{
const
[
shouldValidate
,
setShouldValidate
]
=
useState
(
false
);
const
{
handleChange
,
handleBlur
,
setFieldValue
,
values
,
errors
,
validateForm
,
...
methods
}
=
useFormik
(
formConfig
);
const
getTextInputProps
=
(
name
:
keyof
T
)
=>
({
onChangeText
:
handleChange
(
name
),
onBlur
:
handleBlur
(
name
),
value
:
values
[
name
],
errorMessage
:
errors
[
name
],
}
=
useFormik
({
validateOnChange
:
shouldValidate
,
validateOnBlur
:
shouldValidate
,
...
formConfig
,
});
const
getFormFieldProps
=
(
name
:
string
)
=>
({
onChange
:
(
value
:
any
)
=>
setFieldValue
(
name
,
value
),
value
:
values
[
name
as
keyof
T
],
});
const
getTextInputProps
=
(
fieldName
:
string
)
=>
{
const
name
=
fieldName
as
keyof
T
;
return
{
onChangeText
:
handleChange
(
name
),
onBlur
:
handleBlur
(
name
),
value
:
`
${
values
[
name
]}
`
,
errorMessage
:
errors
[
name
],
};
};
const
getFormFieldProps
=
(
fieldName
:
string
)
=>
{
const
name
=
fieldName
as
keyof
T
;
return
{
onChange
:
(
value
:
any
)
=>
setFieldValue
(
fieldName
,
value
),
value
:
values
[
name
],
errorMessage
:
errors
[
name
],
};
};
const
getFirstErrorIndex
=
(
err
:
any
=
errors
)
=>
Object
.
keys
(
formConfig
.
initialValues
).
findIndex
(
(
name
)
=>
name
===
Object
.
keys
(
err
)[
0
],
);
const
validate
=
async
()
=>
{
setShouldValidate
(
true
);
const
error
=
await
validateForm
();
return
{
isValid
:
Object
.
keys
(
error
).
length
===
0
,
error
,
};
};
return
{
getTextInputProps
,
getFormFieldProps
,
getFirstErrorIndex
,
errors
,
values
,
validateForm
:
validate
,
...
methods
,
};
};
...
...
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment