Fakultas Ilmu Komputer UI

Commit 24b8a4ed authored by Wulan Mantiri's avatar Wulan Mantiri
Browse files

Implement Wizard Container and Dietela Quiz page UI layout

parent 5c82ffdf
import 'react-native';
import React from 'react';
import { create } from 'react-test-renderer';
import { render } from '@testing-library/react-native';
import { HeaderLeft } from './styles';
import App from '.';
jest.useFakeTimers();
describe('Application', () => {
it('renders correctly', () => {
create(<App />);
render(<App />);
});
test('header left button renders correctly', () => {
create(<HeaderLeft />);
render(<HeaderLeft />);
});
});
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import WizardContainer from '.';
import { Text } from 'react-native';
describe('WizardContainer component', () => {
const components = [<Text>hai</Text>, <Text>hello</Text>, <Text>hei</Text>];
const props = {
components,
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).toBeDefined();
});
it('can go back and forth when "Kembali" or "Lanjut" button is pressed', () => {
const { getByText } = render(<WizardContainer {...props} />);
const nextButton = getByText(/Lanjut/i);
fireEvent.press(nextButton);
const helloText = getByText(/hello/i);
expect(helloText).toBeDefined();
const backButton = getByText(/Kembali/i);
expect(backButton).toBeDefined();
fireEvent.press(backButton);
const haiText = getByText(/hai/i);
expect(haiText).toBeDefined();
});
it('shows finish button in the last component', () => {
const { findByText } = render(
<WizardContainer {...props} step={components.length} />,
);
const finishButton = findByText(/Selesai/i);
expect(finishButton).toBeDefined();
});
});
import React, { FC, useState, useEffect } from 'react';
import { ScrollView, View } from 'react-native';
import { Button } from 'react-native-elements';
import { colors } from 'styles';
import { styles } from './styles';
import { NextButtonProps, Props } from './types';
const NextButton: FC<NextButtonProps> = ({ goNext }) => (
<Button
icon={{
name: 'caretright',
type: 'antdesign',
color: colors.primary,
}}
title="Lanjut"
type="clear"
onPress={goNext}
iconRight
titleStyle={{
color: colors.primary,
}}
/>
);
const WizardContainer: FC<Props> = ({
components,
step,
finishButtonLabel,
onFinish,
}) => {
const [currentStep, setCurrentStep] = useState(1);
useEffect(() => {
setCurrentStep(step ?? 1);
}, [step]);
const goBack = () => {
setCurrentStep(currentStep - 1);
};
const goNext = () => {
setCurrentStep(currentStep + 1);
};
return (
<ScrollView>
{components[currentStep - 1]}
{currentStep > 1 ? (
<View style={styles.bottomContainer}>
<Button
icon={{
name: 'caretleft',
type: 'antdesign',
color: colors.primary,
}}
title="Kembali"
type="clear"
onPress={goBack}
titleStyle={{
color: colors.primary,
}}
/>
{currentStep === components.length ? (
<Button
title={finishButtonLabel || 'Selesai'}
onPress={onFinish}
buttonStyle={styles.finishButton}
/>
) : (
<NextButton goNext={goNext} />
)}
</View>
) : (
<View style={styles.flexEnd}>
<NextButton goNext={goNext} />
</View>
)}
</ScrollView>
);
};
export default WizardContainer;
import { StyleSheet } from 'react-native';
import { colors } from 'styles';
export const styles = StyleSheet.create({
bottomContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
padding: 10,
},
flexEnd: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
padding: 10,
},
finishButton: {
backgroundColor: colors.primary,
paddingHorizontal: 20,
marginRight: 10,
},
});
import { ReactNode } from 'react';
export interface Props {
components: ReactNode[];
step?: number;
finishButtonLabel?: string;
onFinish: () => void;
}
export interface NextButtonProps {
goNext: () => void;
}
export { default as WizardContainer } from './WizardContainer';
import 'react-native';
import React from 'react';
import { create } from 'react-test-renderer';
import { render } from '@testing-library/react-native';
import HomePage from '.';
import FormLabel from '.';
describe('Home page', () => {
describe('FormLabel component', () => {
it('renders correctly', () => {
create(<HomePage />);
render(<FormLabel />);
});
});
import React, { FC } from 'react';
import { Text } from 'react-native-elements';
import { styles } from './styles';
import { Props } from './types';
const FormLabel: FC<Props> = ({ label, required }) => (
<Text style={styles.label}>
{label}
{required ? <Text style={styles.red}> *</Text> : null}
</Text>
);
export default FormLabel;
import { StyleSheet } from 'react-native';
import { typography } from 'styles';
export const styles = StyleSheet.create({
red: {
color: 'red',
},
label: {
fontWeight: '600',
marginBottom: 4,
...typography.bodyMedium,
},
});
import { ReactNode } from 'react';
export interface Props {
label?: ReactNode;
required?: boolean;
}
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import MultipleCheckbox from '.';
describe('MultipleCheckbox component', () => {
const props = {
questionNumber: 1,
questionLabel: 'Question',
totalQuestions: 2,
choices: [
{
label: 'Choice 1',
value: 1,
},
{
label: 'Choice 2',
value: 2,
},
],
};
it('is able to select multiple', () => {
let value: Array<string | number> = [];
const { getByText } = render(
<MultipleCheckbox
{...props}
value={value}
onChange={(v: typeof value) => (value = v)}
/>,
);
expect(value).toStrictEqual([]);
fireEvent.press(getByText(/Choice 1/i));
expect(value).toContain(1);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toContain(2);
});
it('is able to unselect checkbox', async () => {
let value: Array<string | number> = [2];
const { getByText } = render(
<MultipleCheckbox
{...props}
value={value}
onChange={(v: typeof value) => (value = v)}
/>,
);
expect(value).toContain(2);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toStrictEqual([]);
});
});
import React, { FC } from 'react';
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';
const MultipleCheckbox: FC<Props> = ({
questionNumber,
totalQuestions,
questionLabel,
helperText,
choices,
value,
onChange,
}) => {
const handlePress = (choice: Choice) => {
if (value.includes(choice.value)) {
onChange(value.filter((item: number) => item !== choice.value));
} else {
onChange([...value, choice.value]);
}
};
return (
<View>
<Text style={[typographyStyles.overlineBig, styles.spacing]}>
Pertanyaan {questionNumber} / {totalQuestions}
</Text>
<Text style={[typographyStyles.headingLarge, styles.spacing]}>
{questionLabel}
</Text>
<Text style={[typographyStyles.bodyMedium, styles.bigSpacing]}>
{helperText || 'Pilih semua yang berlaku'}
</Text>
{choices.map((choice) => (
<CheckBox
key={choice.value}
title={choice.label}
checked={value.includes(choice.value)}
onPress={() => handlePress(choice)}
/>
))}
</View>
);
};
export default MultipleCheckbox;
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
view: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
spacing: {
marginBottom: 14,
},
bigSpacing: {
marginBottom: 24,
},
});
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;
}
import React from 'react';
import { create, act } from 'react-test-renderer';
import { render, fireEvent } from '@testing-library/react-native';
import { CheckBox } from 'react-native-elements';
import MultipleChoice from '.';
describe('MultipleChoice component', () => {
let value: number | null = null;
let value: string | number | null = null;
const props = {
questionNumber: 1,
......@@ -22,28 +21,16 @@ describe('MultipleChoice component', () => {
},
],
value: value,
onChange: (v: number | null) => (value = v),
onChange: (v: typeof value) => (value = v),
};
it('is controlled from external', () => {
const component = create(<MultipleChoice {...props} />);
const instance = component.root;
it('only selects one choice at a time', () => {
const { getByText } = render(<MultipleChoice {...props} />);
const radioButton = instance.findAllByType(CheckBox)[0];
act(() => radioButton.props.onPress());
expect(value).toBe(1);
});
it('executes onPress props if any radio button is pressed', () => {
let isPressed = false;
const component = create(
<MultipleChoice {...props} onPress={() => (isPressed = true)} />,
);
const instance = component.root;
fireEvent.press(getByText(/Choice 1/i));
expect(value).toEqual(1);
const radioButton = instance.findAllByType(CheckBox)[0];
act(() => radioButton.props.onPress());
expect(isPressed).toBe(true);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toEqual(2);
});
});
......@@ -15,7 +15,6 @@ const MultipleChoice: FC<Props> = ({
choices,
value,
onChange,
onPress,
}) => (
<View>
<Text style={[typographyStyles.overlineBig, styles.spacing]}>
......@@ -32,12 +31,7 @@ const MultipleChoice: FC<Props> = ({
key={choice.value}
title={choice.label}
checked={value === choice.value}
onPress={() => {
onChange(choice.value);
if (onPress) {
onPress();
}
}}
onPress={() => onChange(choice.value)}
/>
))}
</View>
......
......@@ -3,19 +3,13 @@ export interface Choice {
value: number;
}
export interface MCQuestion {
questionLabel: string;
helperText?: string;
choiceList: string[];
}
export interface Props {
questionNumber: number;
totalQuestions: number;
questionLabel: string;
helperText?: string;
choices: Choice[];
value: number | null;
onChange: (_: number | null) => void;
value: any;
onChange: (_: any) => void;
onPress?: () => void;
}
import React from 'react';
import { create } from 'react-test-renderer';
import { render, fireEvent } from '@testing-library/react-native';
import RadioButton from '.';
import RadioButton, { RadioButtonGroup } from '.';
describe('RadioButton component', () => {
it('renders correctly', () => {
create(<RadioButton />);
render(<RadioButton />);
});
});
describe('RadioButtonGroup component', () => {
let value: any = null;
const props = {
choices: [
{
label: 'Choice 1',
value: 1,
},
{
label: 'Choice 2',
value: 2,
},
],
value: value,
onChange: (v: any) => (value = v),
};
it('only selects one choice at a time', () => {
const { getByText } = render(<RadioButtonGroup {...props} />);
fireEvent.press(getByText(/Choice 1/i));
expect(value).toEqual(1);
fireEvent.press(getByText(/Choice 2/i));
expect(value).toEqual(2);
});
});
import React, { FC } from 'react';
import { View } from 'react-native';
import { CheckBox, CheckBoxProps } from 'react-native-elements';
import { styles } from './styles';
import FormLabel from '../FormLabel';
import { RadioButtonGroupProps } from './types';
const RadioButton: FC<CheckBoxProps> = (props) => (
<CheckBox {...props} checkedIcon="dot-circle-o" uncheckedIcon="circle-o" />
);
export const RadioButtonGroup: FC<RadioButtonGroupProps> = ({
choices,
label,
required,
value,
onChange,
}) => (
<View>
<FormLabel label={label} required={required} />
<View style={styles.container}>
{choices.map((choice) => (
<RadioButton
key={choice.value}
title={choice.label}
checked={value === choice.value}
onPress={() => onChange(choice.value)}
containerStyle={styles.radioButton}
/>
))}
</View>
</View>
);
export default RadioButton;
Markdown is supported
0% or .