diff --git a/client/public/index.html b/client/public/index.html
new file mode 100644
index 0000000..f996e58
--- /dev/null
+++ b/client/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Hetic VS. EEMI
+
+
+
+ We're sorry but this website doesn't work properly without JavaScript enabled. Please enable it to continue.
+
+
+
+
+
diff --git a/client/public/robots.txt b/client/public/robots.txt
new file mode 100644
index 0000000..eb05362
--- /dev/null
+++ b/client/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/client/src/App.vue b/client/src/App.vue
index d8ed3c9..43c2c1f 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -24,4 +24,19 @@
}
}
}
+
+h3 {
+ margin: 40px 0 0;
+}
+ul {
+ list-style-type: none;
+ padding: 0;
+}
+li {
+ display: inline-block;
+ margin: 0 10px;
+}
+a {
+ color: #42b983;
+}
diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png
deleted file mode 100644
index f3d2503..0000000
Binary files a/client/src/assets/logo.png and /dev/null differ
diff --git a/client/src/components/Quiz.vue b/client/src/components/Quiz.vue
new file mode 100644
index 0000000..6c164e4
--- /dev/null
+++ b/client/src/components/Quiz.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/ScoreScreen.vue b/client/src/components/ScoreScreen.vue
index 2259dd2..a6c995a 100644
--- a/client/src/components/ScoreScreen.vue
+++ b/client/src/components/ScoreScreen.vue
@@ -1,18 +1,23 @@
-
Score: {{ score }}/{{ questions.length }}
+ Score: {{ score }}/{{ count }}
{{ text() }}
- Recommencer
+ Recommencer
-
-
+
\ No newline at end of file
diff --git a/client/src/router.ts b/client/src/router.ts
index 37eeef5..d318c83 100644
--- a/client/src/router.ts
+++ b/client/src/router.ts
@@ -1,6 +1,7 @@
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
+import ScoreScreen from './views/ScoreScreen.vue';
Vue.use(Router);
@@ -13,5 +14,14 @@ export default new Router({
name: 'home',
component: Home,
},
+ {
+ path: '/end',
+ name: 'scoreScreen',
+ component: ScoreScreen,
+ },
+ {
+ path: '*',
+ redirect: { name: 'home' },
+ },
],
});
diff --git a/client/src/store/modules/questions.ts b/client/src/store/modules/questions.ts
index dcec3e2..53c504b 100644
--- a/client/src/store/modules/questions.ts
+++ b/client/src/store/modules/questions.ts
@@ -21,15 +21,6 @@ IStoreQuestions,
questions: [],
},
getters: {
- index(state): number {
- return state.index;
- },
- score(state): number {
- return state.score;
- },
- questions(state): IQuestion[] {
- return state.questions;
- },
currentQuestion(state): IQuestion | {} {
return state.questions.length ? state.questions[state.index] : {};
},
diff --git a/client/src/store/modules/schools.ts b/client/src/store/modules/schools.ts
index a66f508..b9be5b0 100644
--- a/client/src/store/modules/schools.ts
+++ b/client/src/store/modules/schools.ts
@@ -13,11 +13,7 @@ const schools: Module = {
state: {
schools: [],
},
- getters: {
- schools(state): ISchool[] {
- return state.schools;
- },
- },
+ getters: {},
mutations: {
setSchools(state, payload): ISchool[] {
return (state.schools = payload);
diff --git a/client/src/views/Home.vue b/client/src/views/Home.vue
index 8dd76e4..19eab76 100644
--- a/client/src/views/Home.vue
+++ b/client/src/views/Home.vue
@@ -1,68 +1,17 @@
diff --git a/client/tests/e2e/specs/home.js b/client/tests/e2e/specs/home.js
index c6c4c16..f0e6cd6 100644
--- a/client/tests/e2e/specs/home.js
+++ b/client/tests/e2e/specs/home.js
@@ -1,90 +1,132 @@
-describe("Home page", () => {
- it("should have content displayed", () => {
- cy.server({ status: 200 });
- cy.route("/schools", {
+describe('Home page', () => {
+ describe('content', () => {
+ it('should display quizz component', () => {
+ cy.get('#quizz_input').should('be.visible');
+ });
+
+ it('should display title', () => {
+ cy.get('h1#title').should('be.visible');
+ cy.contains('h1#title', 'HETIC vs EEMI');
+ });
+
+ it('should display progress', () => {
+ cy.get('span.progress').should('be.visible');
+ cy.contains('span.progress', '1/2');
+ });
+
+ it('should display choice text', () => {
+ cy.get('p').should('be.visible');
+ cy.contains('p', 'Plus Éemien ou Héticien ?');
+ });
+
+ it('should display question', () => {
+ cy.server();
+ cy.route('/questions', {
+ questions: [{ text: 'test_cypress', answer: 2 }],
+ });
+
+ cy.get('h2#question_text').should('be.visible');
+ cy.contains('h2#question_text', 'test_cypress');
+ });
+
+ it('should display schools', () => {
+ cy.server();
+ cy.route('/schools', {
+ schools: [
+ { id: 1, name: 'school1' },
+ { id: 2, name: 'school2' },
+ { id: 3, name: 'school3' },
+ ],
+ });
+
+ cy.get('.choice-btn').should('be.visible');
+ cy.get('.choice-btn').should('have.length', 2);
+ cy.get('.choice-btn')
+ .first()
+ .should('have.text', 'school1');
+ });
+ });
+
+ it('should finish game with score 0', () => {
+ cy.server();
+ cy.route('/schools', {
schools: [
- { id: 1, name: "school1" },
- { id: 2, name: "school2" },
- { id: 3, name: "school3" }
- ]
+ { id: 1, name: 'school1' },
+ { id: 2, name: 'school2' },
+ ],
});
- cy.route("/questions", {
- questions: [{ text: "test_cypress", answer: 2 }]
+ cy.route('/questions', {
+ questions: [
+ { text: 'test_cypress', answer: 2 },
+ { text: 'test_cypress', answer: 2 },
+ ],
});
- cy.visit("/");
+ cy.visit('/');
- // Elements are visible
- cy.get("h1#title").should("be.visible");
- cy.get("h2#question_text").should("be.visible");
- cy.get("span.progress").should("be.visible");
- cy.get("p").should("be.visible");
- cy.get(".choice-btn").should("be.visible");
- cy.get("#quizz_input").should("be.visible");
- cy.get("#score_screen").should("not.be.visible");
+ cy.contains('#question_text', 'test_cypress');
- // Elements contain right content
- cy.contains("#title", "HETIC vs EEMI");
- cy.get("h2#question_text").should("not.be.empty");
- cy.contains("p", "Plus Éemien ou Héticien ?");
- cy.get(".choice-btn").should("have.length", 3);
- cy.get(".choice-btn")
- .first()
- .should("have.text", "school1");
- });
-
- it("should finish game with score 0", () => {
- cy.server({ status: 200 });
- cy.route("/schools", {
- schools: [{ id: 1, name: "school1" }, { id: 2, name: "school2" }]
- });
- cy.route("/questions", {
- questions: [{ text: "test_cypress", answer: 2 }]
- });
-
- cy.visit("/");
-
- cy.contains("#question_text", "test_cypress");
-
- cy.get(".choice-btn")
+ cy.get('.choice-btn')
.first()
.click();
- cy.contains("Score: 0/1");
+ cy.get('.choice-btn')
+ .first()
+ .click();
+
+ cy.location().should((loc) => {
+ expect(loc.pathname).to.eq('/end');
+ });
+
+ cy.contains('Score: 0/2');
cy.contains("T'as pas lu les questions avoues.");
- cy.contains("Recommencer");
+ cy.contains('Recommencer');
- cy.get("#quizz_input").should("not.be.visible"); // Quizz is hidden
- cy.get("#score_screen").should("be.visible"); // Score screen is displayed
+ cy.get('#quizz_input').should('not.be.visible'); // Quizz is hidden
+ cy.get('#score_screen').should('be.visible'); // Score screen is displayed
});
- it("should finish game with score 1 and retry", () => {
- cy.server({ status: 200 });
- cy.route("/schools", {
- schools: [{ id: 1, name: "school1" }, { id: 2, name: "school2" }]
+ it('should finish game with score 1 and retry', () => {
+ cy.server();
+ cy.route('/schools', {
+ schools: [
+ { id: 1, name: 'school1' },
+ { id: 2, name: 'school2' },
+ ],
});
- cy.route("/questions", {
- questions: [{ text: "test_cypress", answer: 1 }]
+ cy.route('/questions', {
+ questions: [
+ { text: 'test_cypress', answer: 1 },
+ { text: 'test_cypress', answer: 1 },
+ ],
});
- cy.visit("/");
+ cy.visit('/');
- cy.contains("#question_text", "test_cypress");
+ cy.contains('#question_text', 'test_cypress');
- cy.get(".choice-btn")
+ cy.get('.choice-btn')
.first()
.click();
- cy.contains("Score: 1/1");
+ cy.get('.choice-btn')
+ .first()
+ .click();
+
+ cy.location().should((loc) => {
+ expect(loc.pathname).to.eq('/end');
+ });
+
+ cy.contains('Score: 2/2');
cy.contains("Bon bah c'est pas tip top tout ça.");
- cy.contains("Recommencer");
+ cy.contains('Recommencer');
- cy.get("#quizz_input").should("not.be.visible"); // Quizz is hidden
- cy.get("#score_screen").should("be.visible"); // Score screen is displayed
+ cy.get('#quizz_input').should('not.be.visible'); // Quizz is hidden
+ cy.get('#score_screen').should('be.visible'); // Score screen is displayed
- cy.get(".replay-btn").click();
+ cy.get('.replay-btn').click();
- cy.get("#quizz_input").should("be.visible"); // Quizz is visible
- cy.get("#score_screen").should("not.be.visible"); // Score screen is hidden
+ cy.get('#quizz_input').should('be.visible'); // Quizz is visible
+ cy.get('#score_screen').should('not.be.visible'); // Score screen is hidden
});
});
diff --git a/client/tests/unit/components/Quiz.spec.ts b/client/tests/unit/components/Quiz.spec.ts
new file mode 100644
index 0000000..03793ff
--- /dev/null
+++ b/client/tests/unit/components/Quiz.spec.ts
@@ -0,0 +1,124 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import Quiz from '../../../src/components/Quiz.vue';
+import router from '@/router';
+
+const localVue = createLocalVue();
+
+localVue.use(Vuex);
+
+const checkAnswerMock = jest.fn();
+
+let isLastQuestionMock = false;
+let $store: any;
+let wrapper: any;
+
+describe('Component - Quiz', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ jest.restoreAllMocks();
+
+ $store = new Vuex.Store({
+ modules: {
+ questions: {
+ namespaced: true,
+ state: {
+ index: 0,
+ score: 0,
+ questions: [{ answer: 1, text: 'test' }],
+ },
+ getters: {
+ currentQuestion: () => ({
+ text: 'currentQuestion',
+ }),
+ checkAnswer: () => checkAnswerMock,
+ isLastQuestion: () => isLastQuestionMock,
+ },
+ },
+ schools: {
+ namespaced: true,
+ state: {
+ schools: [{ id: 1, name: 'school1' }],
+ },
+ getters: {},
+ },
+ },
+ });
+
+ wrapper = shallowMount(Quiz, {
+ mocks: { $store },
+ });
+ });
+
+ it('should display question', () => {
+ expect(wrapper.find('#question_text').text()).toBe('currentQuestion');
+ });
+
+ it('should display question', () => {
+ expect(wrapper.find('span.progress').text()).toBe('(1/1)');
+ });
+
+ it('should only increase index', () => {
+ const dispatchMock = spyOn($store, 'dispatch');
+ const routerMock = spyOn(router, 'push');
+
+ checkAnswerMock.mockImplementation(() => false);
+ isLastQuestionMock = false;
+
+ wrapper
+ .findAll('.choice-btn')
+ .at(0)
+ .trigger('click');
+
+ expect(checkAnswerMock).toBeCalledTimes(1);
+ expect(checkAnswerMock).toBeCalledWith({ answer: 1 });
+
+ expect(dispatchMock).toBeCalledTimes(1);
+ expect(dispatchMock).toBeCalledWith('questions/increaseIndex');
+
+ expect(routerMock).toBeCalledTimes(0);
+ });
+
+ it('should increase score and index', () => {
+ const dispatchMock = spyOn($store, 'dispatch');
+ const routerMock = spyOn(router, 'push');
+
+ checkAnswerMock.mockImplementation(() => true);
+ isLastQuestionMock = false;
+
+ wrapper
+ .findAll('.choice-btn')
+ .at(0)
+ .trigger('click');
+
+ expect(checkAnswerMock).toBeCalledTimes(1);
+ expect(checkAnswerMock).toBeCalledWith({ answer: 1 });
+
+ expect(dispatchMock).toBeCalledTimes(2);
+ expect(dispatchMock).nthCalledWith(1, 'questions/increaseScore');
+ expect(dispatchMock).nthCalledWith(2, 'questions/increaseIndex');
+
+ expect(routerMock).toBeCalledTimes(0);
+ });
+
+ it('should to score screen', () => {
+ const dispatchMock = spyOn($store, 'dispatch');
+ const routerMock = spyOn(router, 'push');
+
+ checkAnswerMock.mockImplementation(() => false);
+ isLastQuestionMock = true;
+
+ wrapper
+ .findAll('.choice-btn')
+ .at(0)
+ .trigger('click');
+
+ expect(checkAnswerMock).toBeCalledTimes(1);
+ expect(checkAnswerMock).toBeCalledWith({ answer: 1 });
+
+ expect(dispatchMock).toBeCalledTimes(0);
+
+ expect(routerMock).toBeCalledTimes(1);
+ expect(routerMock).toBeCalledWith({ name: 'scoreScreen' });
+ });
+});
diff --git a/client/tests/unit/components/ScoreScreen.spec.ts b/client/tests/unit/components/ScoreScreen.spec.ts
new file mode 100644
index 0000000..423d72f
--- /dev/null
+++ b/client/tests/unit/components/ScoreScreen.spec.ts
@@ -0,0 +1,55 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import ScoreScreen from '../../../src/components/ScoreScreen.vue';
+import router from '@/router';
+
+const localVue = createLocalVue();
+
+localVue.use(Vuex);
+
+let $store: any;
+
+let wrapper: any;
+
+describe('Component - ScoreScreen', () => {
+ beforeEach(() => {
+ $store = new Vuex.Store({
+ modules: {
+ questions: {
+ namespaced: true,
+ state: {
+ index: 0,
+ score: 0,
+ questions: [],
+ },
+ },
+ },
+ });
+ wrapper = shallowMount(ScoreScreen, {
+ mocks: { $store },
+ propsData: {
+ score: 2,
+ count: 5,
+ },
+ });
+ });
+
+ it('should display message based on score', () => {
+ expect(wrapper.find('h3').text()).toBe(
+ 'Bon bah c\'est pas tip top tout ça.',
+ );
+ });
+
+ it('should call reset state function', () => {
+ const dispatchMock = spyOn($store, 'dispatch');
+ const routerMock = spyOn(router, 'push');
+
+ wrapper.find('button.replay-btn').trigger('click');
+
+ expect(dispatchMock).toBeCalledTimes(1);
+ expect(dispatchMock).toBeCalledWith('questions/resetState');
+
+ expect(routerMock).toBeCalledTimes(1);
+ expect(routerMock).toBeCalledWith({ name: 'home' });
+ });
+});
diff --git a/client/tests/unit/store/questions.spec.ts b/client/tests/unit/store/questions.spec.ts
index d084bd9..e269342 100644
--- a/client/tests/unit/store/questions.spec.ts
+++ b/client/tests/unit/store/questions.spec.ts
@@ -21,36 +21,6 @@ describe('Store - Questions', () => {
});
describe('Getters', () => {
- describe('#index', () => {
- it('should get index', () => {
- state.index = 5;
-
- const index = getters.index(state, null, null, null);
-
- expect(index).toEqual(5);
- });
- });
-
- describe('#score', () => {
- it('should get score', () => {
- state.score = 5;
-
- const score = getters.score(state, null, null, null);
-
- expect(score).toEqual(5);
- });
- });
-
- describe('#questions', () => {
- it('should get questions', () => {
- state.questions = [{ text: 'test', answer: 1 }];
-
- const data = getters.questions(state, null, null, null);
-
- expect(data).toStrictEqual(state.questions);
- });
- });
-
describe('#currentQuestion', () => {
it('should get current question', () => {
state.index = 1;
@@ -77,7 +47,12 @@ describe('Store - Questions', () => {
it('should check answer and get true', () => {
state.questions = [{ text: 'test', answer: 1 }];
- const isCorrect = getters.checkAnswer(state, null, null, null)({
+ const isCorrect = getters.checkAnswer(
+ state,
+ null,
+ null,
+ null,
+ )({
answer: 1,
});
@@ -87,7 +62,12 @@ describe('Store - Questions', () => {
it('should check answer and get false', () => {
state.questions = [{ text: 'test', answer: 2 }];
- const isCorrect = getters.checkAnswer(state, null, null, null)({
+ const isCorrect = getters.checkAnswer(
+ state,
+ null,
+ null,
+ null,
+ )({
answer: 1,
});
diff --git a/client/tests/unit/store/schools.spec.ts b/client/tests/unit/store/schools.spec.ts
index 8be088b..7f37786 100644
--- a/client/tests/unit/store/schools.spec.ts
+++ b/client/tests/unit/store/schools.spec.ts
@@ -16,19 +16,6 @@ describe('Store - Questions', () => {
};
});
- describe('Getters', () => {
- describe('#schools', () => {
- it('should get schools', () => {
- state.schools = [{ id: '1', name: 'test' }, { id: '2', name: 'test2' }];
-
- const data = getters.schools(state, null, null, null);
-
- expect(data).toStrictEqual(state.schools);
- expect(data.length).toBe(2);
- });
- });
- });
-
describe('Mutations', () => {
describe('#setSchools', () => {
it('should set schools state', () => {
diff --git a/client/tests/unit/views/Home.spec.ts b/client/tests/unit/views/Home.spec.ts
index 75883e8..15f0a39 100644
--- a/client/tests/unit/views/Home.spec.ts
+++ b/client/tests/unit/views/Home.spec.ts
@@ -4,7 +4,7 @@ import Home from '../../../src/views/Home.vue';
import axios from 'axios';
import config from '@/config';
import questions from '@/store/modules/questions';
-import schools from '@/store/modules/questions';
+import schools from '@/store/modules/schools';
const localVue = createLocalVue();
@@ -18,17 +18,13 @@ const $store = new Vuex.Store({
});
const axiosMock = jest
.spyOn(axios, 'get')
- .mockResolvedValue({ data: { questions: [] } } as any);
-const wrapper = shallowMount(Home, { mocks: { $store } });
+ .mockResolvedValueOnce({ data: { questions: [] } } as any)
+ .mockResolvedValueOnce({ data: { schools: [] } } as any);
-describe.skip('Views - Home', () => {
- it('should ', () => {
- const storeDispatchMock = spyOn($store, 'dispatch');
+shallowMount(Home, { mocks: { $store } });
- // wrapper.find('.todoList__removeDone').trigger('click');
- // expect(storeDispatchMock).toBeCalledTimes(2);
- // expect(storeDispatchMock).toHaveBeenNthCalledWith(1, 'fetchQuestions');
- // expect(storeDispatchMock).toHaveBeenNthCalledWith(2, 'fetchSchools');
+describe('Views - Home', () => {
+ it('should fetch questions and shools', () => {
expect(axiosMock).toBeCalledTimes(2);
expect(axiosMock).toHaveBeenNthCalledWith(1, `${config.apiUrl}/questions`);
expect(axiosMock).toHaveBeenNthCalledWith(2, `${config.apiUrl}/schools`);
diff --git a/client/tests/unit/views/ScoreScreen.spec.ts b/client/tests/unit/views/ScoreScreen.spec.ts
new file mode 100644
index 0000000..88f771b
--- /dev/null
+++ b/client/tests/unit/views/ScoreScreen.spec.ts
@@ -0,0 +1,53 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import ScoreScreen from '../../../src/views/ScoreScreen.vue';
+import router from '@/router';
+
+const localVue = createLocalVue();
+
+localVue.use(Vuex);
+
+let wrapper: any;
+
+const mountIt = (index = 0, score = 0, questions = []) => {
+ wrapper = shallowMount(ScoreScreen, {
+ mocks: {
+ $store: new Vuex.Store({
+ modules: {
+ questions: {
+ namespaced: true,
+ state: {
+ index,
+ score,
+ questions,
+ },
+ },
+ },
+ }),
+ },
+ });
+};
+
+describe('Views - ScoreScreen', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ jest.restoreAllMocks();
+ });
+
+ it('should redirect to home on index 0', () => {
+ const routerMock = spyOn(router, 'push');
+
+ mountIt();
+
+ expect(routerMock).toBeCalledTimes(1);
+ expect(routerMock).toBeCalledWith({ name: 'home' });
+ });
+
+ it('should not redirect to home', () => {
+ mountIt(1);
+
+ const routerMock = spyOn(router, 'push');
+
+ expect(routerMock).toBeCalledTimes(0);
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index c3e7bd3..fdc245f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "HETICvsEEMI",
+ "name": "hetic-vs-eemi",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
diff --git a/package.json b/package.json
index 00b326a..0ded26c 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "HETICvsEEMI",
+ "name": "hetic-vs-eemi",
"version": "1.0.0",
"description": "Plus Eemien ou Héticien ? Le jeu qui ne fait pas rire les élèves.",
"main": "index.js",
@@ -16,8 +16,6 @@
"type": "git",
"url": "git+https://github.com/sundowndev/HETICvsEEMI.git"
},
- "keywords": [],
- "author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/sundowndev/HETICvsEEMI/issues"
diff --git a/server/package.json b/server/package.json
index 272cba9..f630c43 100644
--- a/server/package.json
+++ b/server/package.json
@@ -2,7 +2,6 @@
"name": "server",
"private": false,
"version": "1.0.0",
- "description": "",
"main": "index.js",
"dependencies": {
"compression": "^1.7.4",
@@ -10,11 +9,7 @@
"express": "^4.17.1",
"morgan": "^1.9.1"
},
- "devDependencies": {},
"scripts": {
"start": "node index.js"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
+ }
}