Skip to content

Commit

Permalink
Merge pull request #84 from manh-t/feature/30-integrate-question-page
Browse files Browse the repository at this point in the history
[#30] Fill the data into the Question page
  • Loading branch information
manh-t authored Sep 12, 2023
2 parents 37bf717 + 29a92e8 commit 7551322
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 65 deletions.
112 changes: 112 additions & 0 deletions src/components/Answer/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react';

import { render, screen } from '@testing-library/react';

import { appSliderDataTestIds } from 'components/AppSlider';
import { dropdownDataTestIds } from 'components/Dropdown';
import { multiChoiceDataTestIds } from 'components/MultiChoice';
import { multiInputsDataTestIds } from 'components/MultiInputs';
import { npsDataTestIds } from 'components/Nps';
import { ratingDataTestIds } from 'components/Rating';
import { textAreaDataTestIds } from 'components/TextArea';
import { questionFabricator } from 'tests/fabricator';

import Answer, { answerDataTestIds } from '.';

describe('Answer', () => {
describe('given the display type is star', () => {
it('renders Rating component', () => {
const question = questionFabricator();

render(<Answer question={question} />);

const star = screen.getByTestId(ratingDataTestIds.base);

expect(star).toBeVisible();
});
});

describe('given the display type is choice', () => {
it('renders MultiChoice component', () => {
const question = questionFabricator({ displayType: 'choice' });

render(<Answer question={question} />);

const multiChoice = screen.getByTestId(multiChoiceDataTestIds.base);

expect(multiChoice).toBeVisible();
});
});

describe('given the display type is nps', () => {
it('renders Nps component', () => {
const question = questionFabricator({ displayType: 'nps' });

render(<Answer question={question} />);

const nps = screen.getByTestId(npsDataTestIds.base);

expect(nps).toBeVisible();
});
});

describe('given the display type is textarea', () => {
it('renders TextArea component', () => {
const question = questionFabricator({ displayType: 'textarea' });

render(<Answer question={question} />);

const textArea = screen.getByTestId(textAreaDataTestIds.base);

expect(textArea).toBeVisible();
});
});

describe('given the display type is textfield', () => {
it('renders MultiInputs component', () => {
const question = questionFabricator({ displayType: 'textfield' });

render(<Answer question={question} />);

const multiInputs = screen.getByTestId(multiInputsDataTestIds.base);

expect(multiInputs).toBeVisible();
});
});

describe('given the display type is dropdown', () => {
it('renders Dropdown component', () => {
const question = questionFabricator({ displayType: 'dropdown' });

render(<Answer question={question} />);

const dropdown = screen.getByTestId(dropdownDataTestIds.base);

expect(dropdown).toBeVisible();
});
});

describe('given the display type is slider', () => {
it('renders Dropdown component', () => {
const question = questionFabricator({ displayType: 'slider' });

render(<Answer question={question} />);

const slider = screen.getByTestId(appSliderDataTestIds.base);

expect(slider).toBeVisible();
});
});

describe('given the display type is intro', () => {
it('does NOT render any components', () => {
const question = questionFabricator({ displayType: 'intro' });

render(<Answer question={question} />);

const intro = screen.getByTestId(answerDataTestIds.base);

expect(intro).toBeEmptyDOMElement();
});
});
});
68 changes: 68 additions & 0 deletions src/components/Answer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';

import AppSlider from 'components/AppSlider';
import Dropdown from 'components/Dropdown';
import MultiChoice from 'components/MultiChoice';
import MultiInputs from 'components/MultiInputs';
import Nps from 'components/Nps';
import Rating from 'components/Rating';
import TextArea from 'components/TextArea';
import { Answer as AnswerType } from 'types/answer';
import { DisplayType, Question, getDisplayTypeEnum } from 'types/question';
import { AnswerRequest } from 'types/request/surveySubmitRequest';

export const answerDataTestIds = {
base: 'answer__base',
};

interface AnswerProps {
question: Question;
}
const Answer = ({ question }: AnswerProps): JSX.Element => {
const displayTypeEnum = getDisplayTypeEnum(question);

const onValueChanged = (answer: number | AnswerType | AnswerRequest) => {
// TODO
console.log(answer);
};

const onValuesChanged = (answers: AnswerType[] | AnswerRequest[]) => {
// TODO
console.log(answers);
};

const answerComponent = (): JSX.Element => {
switch (displayTypeEnum) {
case DisplayType.Heart:
case DisplayType.Smiley:
case DisplayType.Thumbs:
case DisplayType.Star:
return <Rating items={question.answers} displayType={displayTypeEnum} onValueChanged={onValueChanged} />;
case DisplayType.Choice:
return <MultiChoice items={question.answers} isPickOne={question?.pick === 'one'} onValuesChanged={onValuesChanged} />;
case DisplayType.Nps:
return <Nps items={question.answers} onValuesChanged={onValuesChanged} />;
case DisplayType.Textarea:
return <TextArea items={question.answers} onValueChange={onValueChanged} />;
case DisplayType.Textfield:
return <MultiInputs questionId={question.id} items={question.answers} onValuesChanged={onValuesChanged} />;
case DisplayType.Dropdown:
return <Dropdown items={question.answers} onValueChanged={onValueChanged} />;
case DisplayType.Slider:
return <AppSlider min={0} max={question.answers.length} onValueChanged={onValueChanged} />;
case DisplayType.Unknown:
case DisplayType.Intro:
case DisplayType.Outro:
default:
return <></>;
}
};

return (
<div key={question.id} data-test-id={answerDataTestIds.base}>
{answerComponent()}
</div>
);
};

export default Answer;
10 changes: 5 additions & 5 deletions src/components/AppSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';

import Slider from 'rc-slider';

Expand All @@ -14,9 +14,9 @@ interface SliderProps {
}

const AppSlider = ({ min = 1, max = 100, step = 1, onValueChanged }: SliderProps): JSX.Element => {
const handleOnChange = (value: number) => {
onValueChanged(value);
};
useEffect(() => {
onValueChanged(min);
}, [min, onValueChanged]);

return (
<div className="flex w-full h-[56px] justify-center items-center" data-test-id={appSliderDataTestIds.base}>
Expand Down Expand Up @@ -45,7 +45,7 @@ const AppSlider = ({ min = 1, max = 100, step = 1, onValueChanged }: SliderProps
min={min}
max={max}
step={step}
onChange={(value) => handleOnChange(value as number)}
onChange={(value) => onValueChanged(value as number)}
/>
</div>
);
Expand Down
2 changes: 0 additions & 2 deletions src/components/Dropdown/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe('Dropdown', () => {

it('renders a dropdown component', () => {
const dropdownProps = {
questionId: 'question-id',
items: answers,
onValueChanged: () => {
// Do nothing
Expand All @@ -33,7 +32,6 @@ describe('Dropdown', () => {
selectedValue = answer.text ?? '';
};
const dropdownProps = {
questionId: 'question-id',
items: answers,
onValueChanged: onValueChanged,
};
Expand Down
12 changes: 4 additions & 8 deletions src/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';

import { ReactComponent as ArrowDropdown } from 'assets/images/icons/arrow-dropdown.svg';
import { Answer } from 'types/answer';
Expand All @@ -8,11 +8,11 @@ export const dropdownDataTestIds = {
};

interface DropdownProps {
questionId: string;
items: Answer[];
onValueChanged: (value: Answer) => void;
}
const Dropdown = ({ questionId, items, onValueChanged }: DropdownProps): JSX.Element => {

const Dropdown = ({ items, onValueChanged }: DropdownProps): JSX.Element => {
const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(items[0].text);

Expand All @@ -26,10 +26,6 @@ const Dropdown = ({ questionId, items, onValueChanged }: DropdownProps): JSX.Ele
toggleDropdown();
};

useEffect(() => {
setSelectedValue(items[0].text);
}, [questionId, items]);

return (
<div className="relative flex flex-col items-center w-full rounded-lg" data-test-id={dropdownDataTestIds.base}>
<button
Expand All @@ -40,7 +36,7 @@ const Dropdown = ({ questionId, items, onValueChanged }: DropdownProps): JSX.Ele
<ArrowDropdown className={isOpen ? 'rotate-180' : ''} />
</button>
{isOpen && (
<div className="bg-white bg-opacity-[.18] absolute top-20 flex flex-col items-start rounded-[12px] p-2 w-full overflow-y-auto max-h-[400px]">
<div className="bg-white bg-opacity-[.18] absolute top-20 flex flex-col items-start rounded-[12px] p-2 w-full overflow-y-auto max-h-[250px]">
{items.map((item) => (
<button
className="flex w-full justify-between hover:bg-white hover:bg-opacity-30 cursor-pointer py-2 rounded-r-[12px] border-l-transparent hover:border-l-white border-l-4 text-white text-regular tracking-survey-tight"
Expand Down
2 changes: 1 addition & 1 deletion src/components/ElevatedButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ElevatedButton = ({ children, isFullWidth, ...rest }: ElevatedButtonProps)
'bg-white text-black-chinese font-bold text-regular tracking-survey-tight rounded-[10px] focus:outline-none focus:shadow-outline h-14';

return (
<button type="button" className={classNames(DEFAULT_CLASS_NAMES, { 'w-full': isFullWidth })} {...rest}>
<button type="button" className={classNames(DEFAULT_CLASS_NAMES, { 'w-full': isFullWidth, 'px-8': !isFullWidth })} {...rest}>
{children}
</button>
);
Expand Down
30 changes: 30 additions & 0 deletions src/components/MultiInputs/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';

import { render, screen, within } from '@testing-library/react';

import { answerFabricator } from 'tests/fabricator';

import MultiInputs, { multiInputsDataTestIds } from '.';

describe('MultiInputs', () => {
it('renders a multi inputs component', () => {
const answers = answerFabricator.times(3);

render(
<MultiInputs
questionId="question id"
items={answers}
onValuesChanged={() => {
// Do nothing
}}
/>
);

const multiInputs = screen.getByTestId(multiInputsDataTestIds.base);

expect(multiInputs).toBeVisible();
expect(within(multiInputs).getByPlaceholderText(answers[0].text)).toBeVisible();
expect(within(multiInputs).getByPlaceholderText(answers[1].text)).toBeVisible();
expect(within(multiInputs).getByPlaceholderText(answers[2].text)).toBeVisible();
});
});
48 changes: 48 additions & 0 deletions src/components/MultiInputs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState } from 'react';

import TextInput from 'components/TextInput';
import { Answer } from 'types/answer';
import { AnswerRequest } from 'types/request/surveySubmitRequest';

export const multiInputsDataTestIds = {
base: 'multi-inputs__base',
};

interface MultiInputProps {
questionId: string;
items: Answer[];
onValuesChanged: (answers: AnswerRequest[]) => void;
}
const MultiInputs = ({ items, onValuesChanged }: MultiInputProps): JSX.Element => {
const [selectedValues, setSelectedValues] = useState<AnswerRequest[]>([]);

const handleValuesChanged = (answer: Answer, content: string) => {
const newSelectedValues = selectedValues;
const itemIndex = newSelectedValues.findIndex((value) => value.id === answer.id);
if (itemIndex !== -1) {
newSelectedValues[itemIndex].answer = content;
}
setSelectedValues(newSelectedValues);
onValuesChanged(newSelectedValues);
};

return (
<div data-test-id={multiInputsDataTestIds.base}>
{items.map((item) => (
<div key={item.id}>
<TextInput
inputAttributes={{
id: `answer-text-input__${item.id}`,
required: true,
type: 'text',
placeholder: item.text,
onChange: (event) => handleValuesChanged(item, event.target.value),
}}
/>
</div>
))}
</div>
);
};

export default MultiInputs;
9 changes: 2 additions & 7 deletions src/components/Rating/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';

import { Answer } from 'types/answer';
import { DisplayType } from 'types/question';
Expand All @@ -8,13 +8,12 @@ export const ratingDataTestIds = {
};

interface RatingProps {
questionId: string;
items: Answer[];
displayType: DisplayType;
onValueChanged: (answer: Answer) => void;
}

const Rating = ({ questionId, items, displayType, onValueChanged }: RatingProps): JSX.Element => {
const Rating = ({ items, displayType, onValueChanged }: RatingProps): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState(0);

const handleOnSelectRating = (index: number, answer: Answer) => {
Expand Down Expand Up @@ -47,10 +46,6 @@ const Rating = ({ questionId, items, displayType, onValueChanged }: RatingProps)
}
};

useEffect(() => {
setSelectedIndex(0);
}, [questionId]);

return (
<div
className="flex justify-center text-large font-extrabold tracking-survey-tighter gap-4"
Expand Down
5 changes: 4 additions & 1 deletion src/components/TextArea/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import TextArea, { textAreaDataTestIds } from '.';
describe('TextArea', () => {
it('renders a text area component', () => {
const answers: Answer[] = answerFabricator.times(2);
const onValueChanged = () => {
// Do nothing
};

render(<TextArea items={answers} />);
render(<TextArea items={answers} onValueChange={onValueChanged} />);

const textArea = screen.getByTestId(textAreaDataTestIds.base);

Expand Down
Loading

0 comments on commit 7551322

Please sign in to comment.