diff --git a/package-lock.json b/package-lock.json index 8e7dbe69..0df16a38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "express": "^4.19.2", "i18n": "^0.15.1", "i18next": "^23.10.1", + "jquery": "^3.7.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.3.0", "react-countdown": "^2.3.5", @@ -20,6 +21,7 @@ "react-router-dom": "^6.22.1" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "jest": "^29.7.0" } }, @@ -130,6 +132,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", @@ -146,6 +160,29 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", + "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -180,6 +217,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.24.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", @@ -211,6 +260,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", @@ -220,6 +281,23 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", @@ -232,6 +310,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", @@ -383,6 +473,25 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -530,10 +639,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { + "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -545,13 +654,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -560,15 +669,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { + "node_modules/@babel/plugin-syntax-typescript": { "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -3831,6 +3938,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index e7b64080..48ad91a2 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "express": "^4.19.2", "i18n": "^0.15.1", "i18next": "^23.10.1", + "jquery": "^3.7.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.3.0", "react-countdown": "^2.3.5", @@ -15,6 +16,7 @@ "react-router-dom": "^6.22.1" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "jest": "^29.7.0" } } diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 41859609..9ecc289d 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -32,6 +32,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "axios-mock-adapter": "^1.22.0", "expect-puppeteer": "^9.0.2", "jest": "^29.3.1", @@ -669,9 +670,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1915,6 +1924,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/webapp/package.json b/webapp/package.json index 55422652..e843dbb5 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -53,6 +53,7 @@ ] }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "axios-mock-adapter": "^1.22.0", "expect-puppeteer": "^9.0.2", "jest": "^29.3.1", diff --git a/webapp/public/correct.mp3 b/webapp/public/correct.mp3 new file mode 100644 index 00000000..37e3cac5 Binary files /dev/null and b/webapp/public/correct.mp3 differ diff --git a/webapp/public/incorrect.mp3 b/webapp/public/incorrect.mp3 new file mode 100644 index 00000000..f5a83099 Binary files /dev/null and b/webapp/public/incorrect.mp3 differ diff --git a/webapp/public/tictac.mp3 b/webapp/public/tictac.mp3 new file mode 100644 index 00000000..e0525154 Binary files /dev/null and b/webapp/public/tictac.mp3 differ diff --git a/webapp/src/components/Instructions.js b/webapp/src/components/Instructions.js index 160978ab..83ec9bf8 100644 --- a/webapp/src/components/Instructions.js +++ b/webapp/src/components/Instructions.js @@ -4,12 +4,14 @@ import {useTranslation} from "react-i18next"; + function Instructions() { const[t] = useTranslation("global"); return (
+

{t("instructions.title")}

@@ -57,7 +59,9 @@ function Instructions() {
+
+ ); } diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index cddffd1b..d6938b13 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -30,7 +30,9 @@ function Navbar() {
-

{t("navBar.title")}

+ +

{t("navBar.title")}

+
@@ -53,7 +55,11 @@ function Navbar() { } function Profile() { - return App logo; + return ( + + App logo + + ); } function Help() { diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 0714bd67..629f9c40 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -148,6 +148,8 @@ const AddUser = () => { + +
diff --git a/webapp/src/components/loginAndRegistration/Login.js b/webapp/src/components/loginAndRegistration/Login.js index d7381d5f..ae209ddd 100644 --- a/webapp/src/components/loginAndRegistration/Login.js +++ b/webapp/src/components/loginAndRegistration/Login.js @@ -29,10 +29,14 @@ const Login = () => { return (
- +
+
+
+ +

{t("login.title")}

{ {t("login.remember_me")}
- + + +
@@ -78,6 +84,8 @@ function LinkRegister() { ); } + + export default Login; // // src/components/Login.js diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 28038332..32facf1a 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,6 +9,34 @@ class QuestionGenerator{ } async generateQuestions(lang) { + + // try { + // //const response = await fetch(this.apiUrl); + // //const receivedQuestions = await response.json(); + + // //Mockup + // // console.log("type: "+type+" amount: "+amount) + // const receivedQuestions = JSON.parse('{"0":{"question":"¿Cuál es la población de Oviedo?","answers":["225089","191325","220587","121548"]},'+ + // '"1":{"question":"¿Which is the population of Gijon?","answers":["275274","159658","233982","305554"]},'+ + // '"2":{"question":"¿Cuál es la población de Avilés?","answers":["82568","115595","41284","122200"]},'+ + // '"3":{"question":"¿Cuál es la capital de Asturias?","answers":["Ciudad de Oviedo","a","b","c"]},'+ + // '"4":{"question":"¿Cuál es la capital de España?","answers":["Madrid","a","b","c"]},'+ + // '"5":{"question":"¿Cuál es la capital de Turquía?","answers":["Ankara","a","b","c"]}}') + + // let i = 0; + // var questions = []; + // for (const key in receivedQuestions) { + // questions[i] = new Question(receivedQuestions[key]); + // i += 1; + // } + // console.log(questions); + // return questions; + // } catch (error) { + // throw new Error(error); + // } + + + try { const response = await axios.get(this.apiUrl + '/' + lang); const receivedQuestions = await response.data; @@ -22,9 +50,9 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } + } } -export default QuestionGenerator; - +export default QuestionGenerator; \ No newline at end of file diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 96d0978c..ca598e11 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -14,10 +14,13 @@ const creationHistoricalRecord = new CreationHistoricalRecord(); const questionGenerator = new QuestionGenerator(); var points = 0; function QuestionView(){ + const [numQuestion, setnumQuestion] = useState(-1); const [questions, setQuestions] = useState(null); const[t, i18n] = useTranslation("global"); const {user} = useUserContext(); + const [audio] = useState(new Audio('/tictac.mp3')); + const generateQuestions = async (numQuestion) => { if (numQuestion < 0) { @@ -33,24 +36,34 @@ function QuestionView(){ } } - function revealColorsForAnswers(){ - let colorCorrectAnswer='#6EF26E';//green - let colorIncorrectAnswer='#FF6666'; //red + function revealColorsForAnswers(correctAnswer, answerGiven){ + let colorCorrectAnswer = '#6EF26E'; // verde + let colorIncorrectAnswer = '#FF6666'; // rojo + let audioCorrect = new Audio('/correct.mp3'); + let audioIncorrect = new Audio('/incorrect.mp3'); + $('.answerButton').each(function() { var dataValue = $(this).attr('data-value'); - if (dataValue === false || dataValue === "false") + if (dataValue === false || dataValue === "false") { $(this).css('background-color', colorIncorrectAnswer); // Cambia el color de fondo del botón actual a rojo - - else{ + } else { $(this).css({ 'background-color': colorCorrectAnswer, - 'text-decoration': 'underline'// Underline the text of the button for correct answers + 'text-decoration': 'underline' // Subraya el texto del botón para respuestas correctas }); } + if(answerGiven===correctAnswer){ + audio.pause(); + audioCorrect.play(); // Reproduce el sonido de respuesta incorrecta + } + else{ + audio.pause(); + audioIncorrect.play(); // Reproduce el sonido de respuesta correcta + } $(this).css('pointer-events', 'none'); - }); - + }); } + function setColorsBackToNormal() { let colorOriginal = '#9f97ff'; $('.answerButton').each(function() { @@ -64,8 +77,10 @@ function QuestionView(){ function computePointsForQuestion(correctAnswer, answerGiven){ if(answerGiven===correctAnswer){ points+=100; + audio.pause(); }else if(points-50>=0){ points-=50; + audio.pause(); }else{ points = 0; } @@ -81,7 +96,7 @@ function QuestionView(){ computePointsForQuestion(questions[numQuestion].getCorrectAnswer(), text); //reveal answer to user for 1 sec - revealColorsForAnswers(); + revealColorsForAnswers(questions[numQuestion].getCorrectAnswer(), text); setTimeout(function() { //after one second set colors back to normal setColorsBackToNormal(); @@ -90,6 +105,7 @@ function QuestionView(){ //Last question sends the record if(!(numQuestion < questions.length - 1)){ + audio.pause(); creationHistoricalRecord.setDate(Date.now()); creationHistoricalRecord.setPoints(points); creationHistoricalRecord.sendRecord(user.username); @@ -105,18 +121,59 @@ function QuestionView(){ return (
{numQuestion >= 0 ? - : + :

{t("questionView.no_questions_message")}

}
); } -function QuestionComponent({questions, numQuestion, handleClick, t, points}){ +function QuestionComponent({questions, numQuestion, handleClick, t, points, audio, language}){ + const speakQuestion = () => { + const speech = new SpeechSynthesisUtterance(); + speech.lang = language; + console.log(language); + getVoicesForLanguage(language) + .then(voices => { + // const voice = voices.find(voice => voice.lang === language); + // speech.voice = voice || voices[0]; // If there is no voice for the lang, choose the first one + window.speechSynthesis.speak(speech); + }) + .catch(error => { + console.error("Error al obtener las voces para el idioma:", error); + }); + }; + + // Función para obtener las voces disponibles para un idioma + const getVoicesForLanguage = (language) => { + return new Promise((resolve, reject) => { + const speech = new SpeechSynthesisUtterance(); + speech.text = questions[numQuestion].getQuestion(); + speech.lang = language; + + speech.addEventListener("error", reject); + + speech.addEventListener("end", () => { + const voices = window.speechSynthesis.getVoices(); + if (voices.length > 0) + resolve(voices); + }); + + window.speechSynthesis.speak(speech); + }); + }; + + + const renderer = ({seconds, completed }) => { if (completed) { + audio.pause(); return {t("questionView.end_countdown")}; // Rendered when countdown completes } else { + if (audio.paused) { + audio.loop = true; // Loop of tiktak + audio.play(); + } return {seconds} {t("questionView.seconds")}; // Render countdown } }; @@ -127,7 +184,7 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points}){
-

{questions[numQuestion].getQuestion()}

+

{questions[numQuestion].getQuestion()}

@@ -145,6 +202,7 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points}){ ) : ( <> +

{t("questionView.finished_game")}

{points} {t("questionView.point")}

    < RecordList record={creationHistoricalRecord.getRecord().game}/>
diff --git a/webapp/src/components/questionView/QuestionView.test.js b/webapp/src/components/questionView/QuestionView.test.js index 25392c62..a3a34531 100644 --- a/webapp/src/components/questionView/QuestionView.test.js +++ b/webapp/src/components/questionView/QuestionView.test.js @@ -10,12 +10,34 @@ import MockAdapter from 'axios-mock-adapter'; import {configure} from '@testing-library/dom'; +// Función para configurar el mock de global.Audio +const setupAudioMock = () => { + jest.spyOn(global, 'Audio').mockImplementation(() => ({ + play: jest.fn(), + pause: jest.fn(), + loop: true + })); +}; + +// Mock the SpeechSynthesisUtterance and window.speechSynthesis APIs +global.SpeechSynthesisUtterance = jest.fn(() => ({ + lang: '', + text: '', + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), +})); + +global.window.speechSynthesis = { + getVoices: jest.fn(() => []), + speak: jest.fn(), +}; + configure({ testIdAttribute: 'data-value', }); const mockAxios = new MockAdapter(axios); - +jest.setTimeout(10000); i18en.use(initReactI18next).init({ @@ -40,6 +62,26 @@ describe('Question View component', () => { // Wait for questions to load }); + + // Test for sound functionality + it('speaks the question when the speaker button is clicked', async () => { + const questionText = "What is the population of Oviedo?"; + mockAxios.onGet('http://localhost:8000/questions/en').reply(200, + [{question: questionText, + answers: ["225089","272357","267855","231841"]}]); + + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByText('🔊')); + + // Check if the SpeechSynthesisUtterance is called with the correct text + expect(global.SpeechSynthesisUtterance).toHaveBeenCalledWith(); + + + }); + it('shows a question and answers',async () => { mockAxios.onGet('http://localhost:8000/questions/en').reply(200, @@ -60,7 +102,8 @@ describe('Question View component', () => { expect(screen.getByText('231841')).toBeInTheDocument() }); - it('shows colors to reveal correct answer', async () => { + it('shows colors to reveal correct answer and it sounds', async () => { + setupAudioMock(); mockAxios.onGet('http://localhost:8000/questions/en').reply(200, [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); @@ -70,6 +113,8 @@ describe('Question View component', () => { }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); fireEvent.click(screen.getByTestId('true'));//clicamos en la respuesta correcta + expect(global.Audio).toHaveBeenCalledWith('/correct.mp3'); + // Esperar un segundo antes de continuar await waitFor(() => { // Clic en un botón de respuesta con data-value=true @@ -78,7 +123,8 @@ describe('Question View component', () => { expect(correctAnswerButton).toHaveStyle('background-color: #6EF26E'); }, { timeout: 1000 }); // Esperar 1 segundo }); - it('shows colors to reveal false answer', async () => { + it('shows colors to reveal false answer and it sounds', async () => { + setupAudioMock() mockAxios.onGet('http://localhost:8000/questions/en').reply(200, [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); @@ -87,16 +133,19 @@ describe('Question View component', () => { }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); - fireEvent.click(screen.getAllByTestId('false')[0]); + fireEvent.click(screen.getAllByTestId('false')[1]);//clicamos en la respuesta incorrecta + expect(global.Audio).toHaveBeenCalledWith('/incorrect.mp3'); // Esperar un segundo antes de continuar await waitFor(() => { // Clic en un botón de respuesta con data-value=true - const incorrectAnswerButton = screen.getAllByTestId('false')[0]; + const incorrectAnswerButton = screen.getAllByTestId('false')[1]; // Verificar que el botón tenga el color esperado expect(incorrectAnswerButton).toHaveStyle('background-color: #FF6666'); }, { timeout: 1000 }); // Esperar 1 segundo }); - it('shows timer', async () => { + + it('shows timer and tiktak sound', async () => { + setupAudioMock() mockAxios.onGet('http://localhost:8000/questions/en').reply(200, [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); @@ -105,8 +154,32 @@ describe('Question View component', () => { }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); + expect(global.Audio).toHaveBeenCalledWith('/tictac.mp3'); + const timerElement = screen.getByText(new RegExp(`(\\d+) ${i18en.t('questionView.seconds')}`)); expect(timerElement).toBeInTheDocument(); // Verificar que el temporizador esté presente en el DOM - }); + }); + + + // it('renders end message when countdown completes', async() => { + + // setupAudioMock() + // mockAxios.onGet('http://localhost:8000/questions/en').reply(200, + // [{question: "What is the population of Oviedo?", + // answers: ["225089","272357","267855","231841"]}]); + // await act(async () =>{ + // await render(); + + // }) + // await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); + + // const timerElement = screen.getByText(new RegExp(`(\\d+) ${i18en.t('questionView.seconds')}`)); + // expect(timerElement).toBeInTheDocument(); // Verificar que el temporizador esté presente en el DOM + + + // await waitFor(() => { + // expect(screen.getByText("Time's up!")).toBeInTheDocument(); + // }, { timeout: 9800 }); // Esperar 10 segundos + // }); }); \ No newline at end of file diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 7abd6447..4718020d 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -272,6 +272,80 @@ button[type="submit"]:hover { box-shadow: inset 0px 0px 25px #3700ff; } +.button-back { + display: block; + position: relative; + width: 56px; + height: 56px; + margin: 0; + overflow: hidden; + outline: none; + background-color: transparent; + cursor: pointer; + border: 0; +} + +.button-back:before, +.button-back:after { + content: ""; + position: absolute; + border-radius: 50%; + inset: 7px; +} + +.button-back:before { + border: 4px solid #f0eeef; + transition: opacity 0.4s cubic-bezier(0.77, 0, 0.175, 1) 80ms, + transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955) 80ms; +} + +.button-back:after { + border: 4px solid #96daf0; + transform: scale(1.3); + transition: opacity 0.4s cubic-bezier(0.165, 0.84, 0.44, 1), + transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); + opacity: 0; +} + +.button-back:hover:before, +.button-back:focus:before { + opacity: 0; + transform: scale(0.7); + transition: opacity 0.4s cubic-bezier(0.165, 0.84, 0.44, 1), + transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.button-back:hover:after, +.button-back:focus:after { + opacity: 1; + transform: scale(1); + transition: opacity 0.4s cubic-bezier(0.77, 0, 0.175, 1) 80ms, + transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955) 80ms; +} + +.button-box { + display: flex; + position: absolute; + top: 0; + left: 0; +} + +.button-elem { + display: block; + width: 20px; + height: 20px; + margin: 17px 18px 0 18px; + transform: rotate(180deg); + fill: #f0eeef; +} + +.button-back :hover .button-box, +.button-back :focus .button-box { + transition: 0.4s; + transform: translateX(-56px); +} + + /*---------------------------Instructions---------------------------*/ .instructions_title { font-size: 35px; @@ -1242,6 +1316,21 @@ svg { max-width: 200px; } +.altavoz { + margin-right: 10px; + background: #000000; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px #9f97ff; + cursor: pointer; + font-size: 21px; + color: black; + font-weight: 700; + margin: 0.5em; + max-width: 200px; +} + .questionContainer p, span{ font-size: 20px; }