Skip to content

Commit c117b96

Browse files
committed
done mvp, init jest
1 parent 6d68e9b commit c117b96

File tree

18 files changed

+9338
-10344
lines changed

18 files changed

+9338
-10344
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@typescript-eslint/no-use-before-define": 0,
2828
"@typescript-eslint/no-explicit-any": 0,
2929
"import/no-extraneous-dependencies": 0,
30+
"@typescript-eslint/no-shadow": 0,
3031
"indent": [
3132
"error",
3233
2,

jest.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
preset: "ts-jest",
3+
testEnvironment: "jsdom",
4+
moduleFileExtensions: ["js", "jsx", "ts", "tsx"],
5+
roots: ["<rootDir>/src"],
6+
moduleNameMapper: {
7+
"^components/(.*)$": "<rootDir>/src/components/$1",
8+
"^types/(.*)$": "<rootDir>/src/types/$1",
9+
"^api/(.*)$": "<rootDir>/src/api/$1"
10+
},
11+
globals: {
12+
"ts-jest": {
13+
tsconfig: "./tsconfig.json"
14+
}
15+
}
16+
};

package-lock.json

Lines changed: 9098 additions & 10292 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
"private": true,
55
"dependencies": {
66
"@fluentui/react": "^8.101.2",
7-
"@testing-library/jest-dom": "^5.16.5",
8-
"@testing-library/react": "^13.4.0",
9-
"@testing-library/user-event": "^13.5.0",
10-
"@types/jest": "^27.5.2",
117
"@types/node": "^16.18.3",
128
"@types/react": "^18.0.25",
139
"@types/react-dom": "^18.0.9",
@@ -22,7 +18,8 @@
2218
"scripts": {
2319
"start": "react-scripts start",
2420
"build": "react-scripts build",
25-
"test": "react-scripts test",
21+
"test": "jest",
22+
"clear-test-cache": "jest --clearCache",
2623
"eject": "react-scripts eject",
2724
"lint": "eslint .",
2825
"prettier:check": "prettier --check \"src/**/*.{ts,tsx,json}\"",
@@ -47,6 +44,10 @@
4744
]
4845
},
4946
"devDependencies": {
47+
"@testing-library/jest-dom": "^5.16.5",
48+
"@testing-library/react": "^13.4.0",
49+
"@testing-library/user-event": "^13.5.0",
50+
"@types/jest": "^27.5.2",
5051
"@typescript-eslint/eslint-plugin": "^5.54.1",
5152
"@typescript-eslint/parser": "^5.54.1",
5253
"eslint": "^8.35.0",
@@ -55,6 +56,8 @@
5556
"eslint-import-resolver-alias": "^1.1.2",
5657
"eslint-plugin-import": "^2.27.5",
5758
"eslint-plugin-react": "^7.32.2",
58-
"prettier": "^2.8.4"
59+
"jest": "^27.5.0",
60+
"prettier": "^2.8.4",
61+
"ts-jest": "^27.0.7"
5962
}
60-
}
63+
}

src/App.test.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { render } from "@testing-library/react";
2-
import App from "./App";
2+
import { BrowserRouter } from "react-router-dom";
3+
import { Survey } from "./pages/Survey";
34

4-
test("Correctly calculate overall score", () => {
5-
const { getByTestId } = render(<App />);
6-
expect(getByTestId("happinessScore").textContent).toMatch(/47/);
7-
});
8-
9-
test("Correctly group data and show table", () => {
10-
const { getByTestId } = render(<App />);
11-
expect(getByTestId("FreeTextTable").textContent).toMatch(/What data is NOT always reliable and correct\?\(6\)/);
5+
describe("Survey component", () => {
6+
it("renders without crashing", () => {
7+
render(
8+
<BrowserRouter>
9+
<Survey />
10+
</BrowserRouter>
11+
);
12+
});
1213
});

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
3-
import Survey from "pages/Survey/Survey";
3+
import { Survey } from "pages/Survey";
44

55
const App: React.FC = () => {
66
return (

src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./survey";

src/api/survey.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import surveyData from "../data/survey_results.json";
2+
import { Question, SurveyResult } from "types";
3+
4+
export const getSurveyResult = (): SurveyResult => {
5+
const response: SurveyResult = surveyData;
6+
const questions: Question[] = response.questions.map((question: Question) => ({
7+
question_text: question.question_text,
8+
type: question.type,
9+
responses: question.responses,
10+
}));
11+
12+
const surveyResult: SurveyResult = {
13+
survey_title: response.survey_title,
14+
created_at: response.created_at,
15+
questions: questions,
16+
};
17+
18+
return surveyResult;
19+
};
Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,92 @@
1-
import { CheckboxVisibility, DetailsList, Stack } from "@fluentui/react";
2-
import { FunctionComponent } from "react";
1+
import React, { useState, useEffect, useMemo } from "react";
2+
import {
3+
DetailsList,
4+
CheckboxVisibility,
5+
IColumn,
6+
IGroup
7+
}
8+
from "@fluentui/react";
9+
import { Question } from "types";
310

4-
const SurveyFreeText: FunctionComponent = () => {
5-
const items = ["First item in list", "another one"];
11+
interface SurveyFreeTextProps {
12+
textQuestions: Question[];
13+
}
14+
15+
interface Answer {
16+
key: string;
17+
text: string;
18+
}
19+
20+
const SurveyFreeText: React.FC<SurveyFreeTextProps> = ({ textQuestions }) => {
21+
const [answers, setAnswers] = useState<Answer[]>([]);
22+
const [groups, setGroups] = useState<IGroup[]>([]);
23+
24+
const columns: IColumn[] = useMemo(
25+
() => [
26+
{
27+
key: "text_answer",
28+
name: "Text answers",
29+
fieldName: "answer",
30+
minWidth: 100,
31+
maxWidth: 300,
32+
isResizable: true,
33+
onRender: (answer: Answer) => <div data-is-focusable>{answer.text || ""}</div>,
34+
},
35+
],
36+
[]
37+
);
38+
39+
useEffect(() => {
40+
const initAnswers = () => {
41+
const answers: Answer[] = [];
42+
const groups: IGroup[] = [];
43+
let startIndex = 0;
44+
45+
textQuestions.forEach((question: Question, index: number) => {
46+
const { question_text, responses } = question;
47+
const group: IGroup = {
48+
key: `${index}`,
49+
name: question_text,
50+
startIndex,
51+
count: responses.length,
52+
level: 0,
53+
isCollapsed: true
54+
};
55+
startIndex += responses.length;
56+
57+
const textResponses: string[] = responses as string[];
58+
const questionAnswers: Answer[] = textResponses.map((response: string, i: number) => ({
59+
key: `${index}-${i}`,
60+
text: response,
61+
}));
62+
63+
groups.push(group);
64+
answers.push(...questionAnswers);
65+
});
66+
67+
setGroups(groups);
68+
setAnswers(answers);
69+
};
70+
71+
if (textQuestions?.length) {
72+
initAnswers();
73+
}
74+
}, [textQuestions]);
675

7-
const _onRenderColumn = (item?: any) => {
8-
return <div data-is-focusable={true}>{item}</div>;
9-
};
1076
return (
11-
<Stack data-testid="FreeTextTable">
12-
<DetailsList
13-
checkboxVisibility={CheckboxVisibility.hidden}
14-
items={items}
15-
columns={[{ key: "Free text", name: "Free text", minWidth: 200 }]}
16-
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
17-
ariaLabelForSelectionColumn="Toggle selection"
18-
checkButtonAriaLabel="select row"
19-
checkButtonGroupAriaLabel="select section"
20-
groupProps={{
21-
isAllGroupsCollapsed: true,
22-
showEmptyGroups: true,
23-
}}
24-
onRenderItemColumn={_onRenderColumn}
25-
compact={true}
26-
/>
27-
</Stack>
77+
<DetailsList
78+
checkboxVisibility={CheckboxVisibility.hidden}
79+
items={answers}
80+
groups={groups}
81+
columns={columns}
82+
compact
83+
groupProps={{ isAllGroupsCollapsed: true, showEmptyGroups: true }}
84+
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
85+
ariaLabelForSelectionColumn="Toggle selection"
86+
checkButtonAriaLabel="select row"
87+
checkButtonGroupAriaLabel="select section"
88+
/>
2889
);
2990
};
3091

31-
export default SurveyFreeText;
92+
export default SurveyFreeText;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { default as SurveyFreeText } from "./SurveyFreeText";
1+
export { default as SurveyFreeText } from "./SurveyFreeText";

src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ code {
1111
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
1212
monospace;
1313
}
14+
15+
.ms-DetailsList {
16+
overflow: hidden;
17+
}

src/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import ReactDOM from "react-dom/client";
33
import "./index.css";
44
import App from "./App";
55
import reportWebVitals from "./reportWebVitals";
6-
import { initializeIcons } from "@fluentui/react/lib/Icons";
76

87
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
98
root.render(

src/pages/Survey/Survey.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { render } from "@testing-library/react";
2+
import Survey from "./Survey";
3+
4+
test("Correctly calculate overall score", () => {
5+
const { getByTestId } = render(<Survey />);
6+
expect(getByTestId("happinessScore").textContent).toMatch(/61/);
7+
});

src/pages/Survey/Survey.tsx

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,79 @@
1+
import { useEffect, useState } from "react";
12
import { FontIcon, initializeIcons, Stack, Text } from "@fluentui/react";
2-
import { SurveyFreeText } from "components/SurveyFreeText/";
3+
import moment from "moment";
4+
import { SurveyFreeText } from "components/SurveyFreeText";
5+
import { getSurveyResult } from "api/index";
6+
import { Question, SurveyResult } from "types";
7+
38
initializeIcons();
49

5-
const Survey = () => {
6-
const happinessScore = 73;
10+
const Survey: React.FC = () => {
11+
const [survey, setSurvey] = useState<SurveyResult>({
12+
survey_title: "",
13+
created_at: "",
14+
questions: [],
15+
});
16+
17+
useEffect(() => {
18+
const result = getSurveyResult();
19+
setSurvey(result);
20+
}, []);
21+
22+
const getCreatedAtFormatted = (): string => {
23+
return moment(survey?.created_at).format("DD.MM.YYYY");
24+
};
25+
26+
const getTextQuestions = (): Question[] => {
27+
return survey?.questions.filter((question: Question) => question.type === "text");
28+
};
29+
30+
const getScaleQuestions = (): Question[] => {
31+
return survey?.questions.filter((question: Question) => question.type !== "text");
32+
};
33+
34+
const getTotalPeople = (): number => {
35+
const textQuestionsCount = getTextQuestions().reduce(
36+
(total: number, question: Question) => (total += question.responses.length),
37+
0
38+
);
39+
const scaleQuestionsCount = getScaleQuestions().reduce(
40+
(total: number, question: Question) => (total += question.responses.length),
41+
0
42+
);
43+
return textQuestionsCount + scaleQuestionsCount;
44+
};
45+
46+
const getHappinessScore = (): number => {
47+
let score = 0;
48+
let count = 0;
49+
getScaleQuestions().forEach((question: Question) => {
50+
const responses: number[] = question.responses as number[];
51+
const sum = responses.reduce((total: number, num: number) => (total += num), 0);
52+
score += sum;
53+
count += question.responses.length;
54+
});
55+
const happinessScore = (score / (count * 5)) * 100;
56+
return Math.round(happinessScore);
57+
};
758

859
return (
960
<Stack style={{ margin: 20 }}>
1061
<h1>
1162
<FontIcon iconName="ClipboardList" style={{ marginRight: "5px" }} />
12-
Insert survey title here
63+
{survey?.survey_title}
1364
</h1>
14-
65+
<Text variant="medium" style={{ fontWeight: "600" }}>
66+
{`The survey was started on ${getCreatedAtFormatted()}. Overall, ${getTotalPeople()} people participated in the survey.`}
67+
</Text>
1568
<h1 data-testid="happinessScore">
1669
<FontIcon iconName="ChatBot" style={{ marginRight: "5px" }} />
17-
{happinessScore} / 100
70+
{getHappinessScore()} / 100
1871
</h1>
1972
<Stack>
20-
<SurveyFreeText />
73+
<SurveyFreeText textQuestions={getTextQuestions()} />
2174
</Stack>
2275
</Stack>
2376
);
24-
}
77+
};
2578

2679
export default Survey;

src/pages/Survey/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as Survey } from "./Survey";

src/types/apiTypes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* There is an issue in survey_results.json in line 78: `numebr` => `number`.
3+
* `type` can be `"text" | "number"` if the issue is fixed.
4+
*/
5+
export type Question = {
6+
question_text: string;
7+
type: string;
8+
responses: number[] | string[];
9+
};
10+
11+
export type SurveyResult = {
12+
survey_title: string;
13+
created_at: string;
14+
questions: Question[];
15+
};

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./apiTypes";

tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
"components/*": [
2525
"components/*"
2626
],
27+
"types/*": [
28+
"types/*.ts"
29+
],
30+
"api/*": [
31+
"api/*.ts"
32+
]
2733
}
2834
},
2935
"include": [

0 commit comments

Comments
 (0)