diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1ecd2c6..87732c1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -23,5 +23,8 @@ module.exports = { ], parserOptions: { ecmaVersion: 'latest' + }, + "rules": { + "vue/multi-word-component-names": "off" } } diff --git a/.gitignore b/.gitignore index 8ee54e8..213da15 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,25 @@ coverage *.sw? *.tsbuildinfo + +#amplify-do-not-edit-begin +amplify/\#current-cloud-backend +amplify/.config/local-* +amplify/logs +amplify/mock-data +amplify/mock-api-resources +amplify/backend/amplify-meta.json +amplify/backend/.temp +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplifyconfiguration.dart +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig +.secret-* +**.sample +#amplify-do-not-edit-end diff --git a/Quizzypeak_quoi-tester_Partie-1_final.txt b/Quizzypeak_quoi-tester_Partie-1_final.txt new file mode 100644 index 0000000..298f1cb --- /dev/null +++ b/Quizzypeak_quoi-tester_Partie-1_final.txt @@ -0,0 +1,145 @@ +Analyse finale +Projet : QuizzyPeak + +Partie 1 + +Tests réalisés: +• Quand la catégorie est sélectionné on affiche le tableau des scores +• Quand il n'y a pas de score a affiché pour la catégorie un message particulier s'affiche +• Quand il n'y a pas de catégorie sélectionné un message particulier s'affiche +• Quand le quiz commence la bar de progression affiche la bonne valeur +• Quand on passe a la question suivant la bar de progression affiche la bonne valeur +• Quand on complete le quiz la bar de progression affiche la bonne valeur +• Quand on est a l'état initial le timer affiche la bonne valeur +• Quand le temps diminue le timer affiche la bonne valeur +• Quand le composant est unmounted le timer s'arrete +• Quand on appelle la méthode stop le timer s'arrete +• Quand on passe a une question, la question et les choix s'affichent correctement +• Quand un choix de réponse est sélectionné un evenement est émis +• Quand le quiz est complété un événement est émis + +Autres test potentiels trouvé initialement +• Quand on charge la page les images s'affichent bien + +Tester les inputs du quiz +• Quand un clique sur l'input name le champs devient selectionne avec une bordure +• Quand on clique sur l'input name le curseur se place au debut du champs +• Quand on clique sur un menu deroulant il s'affiche +• Quand on clique sur start sans avoir rempli tous les champs une alerte apparait + +Tester le bouton start quiz +• Quand on clique sur le bouton start on est amené a la page de quiz +• Quand on est sur la page quiz le timer affiche le bon temps selectionné +• Quand on est sur la page quiz le nombre de questions affichés est celui sélectionné +• Quand on est sur la page quiz la bar de progres affiche la bonne valeur +• Quand on complete le quiz la section quiz result s'affiche correctement +  Le score +  La question +  La reponse +  Le bouton restart quiz +• Quand on clique sur le bouton restart quiz nous sommes amené a la page principale + +Tester le bouton edit questions +• Quand on clique sur le bouton edit nous sommes amené a la page edit + +Tester la page edit +• Quand on clique sur le bouton back nous sommes ramené sur la page principale +• Quand on clique le champs New question text un encadré s'affiche +• Quand on clique le champs Select a category le menu deroulant s'affiche +• Quand on clique le champs Choice une bordure s'affiche +• Quand on clique le bouton add choice un champs Choice s'affiche +• Quand on clique le champs Correct Answer une bordure s'affiche +• Quand on clique le bouton add question une alerte confirme l'ajout +• Quand on clique le bouton Add question une question est ajouté au bas de la page +• Quand on clique le bouton delete sur une question un modal de confirmation s'affiche +• Quand on clique ok sur le modal la question est supprimé +• Quand on clique anulé sur le modal la question n'est pas supprimé +• Quand on clique un champs d'input sur une question il devient encadré en noir +• Quand on clique sur le champs menu deroulant de la category d'une question la liste de category s'affiche +• Quand on clique sur le bouton Save Questions un modal de confirmation apparait + +Tester l’affichage de la section leaderboard +• Quand on complete un test les bonnes valeurs s'affichent dans la section leaderboard +  Category +  Nom +  Score +• Quand on complete un test dans une autre Category le resultat s'affiche pour la categorie en cours + +Failles potentielles et bugs + +Failles de performance : + +• La page ne permet pas la restoration du cache +• Les script javscript pourait etre minifier pour de meilleures performances +• Les images devraient être au format webP plutot que jpeg ou png +• Les images pourrait spécifié une hauteur ou une largeur +• L'image du logo est trop grosse/lourde +• Utiliser le lazy loading pour eviter de charger des scripts javascript qui ne sont pas utiliser tout de suite +• Eviter d'utiliser des scripts javscript qui ne sont pas jour (onloadwff.js:71, h1-check.js:107, web-client-content-script.js:2) + +Faille d'accessibilité : + +• Les couleurs d'arrière-plan et de premier plan ne sont pas suffisamment contrastées. +• Certains éléments ne sont associés à aucun élément de libellé. +• Les éléments d'en-tête ne sont pas classés séquentiellement par ordre décroissant. + +Failles de sécurité : + +• Garantir l'efficacité de la CSP contre les attaques XSS. + +Autres failles : + +• Le document ne contient pas d'attribut "meta description" (BONNES PRATIQUES RELATIVES AU CONTENU). + +Bugs + +• Quand le timer s'arrete on peut continuer a repondre au questionnaire +• Certaines categories nom pas le bon nombre de question et donc le nombre de question choisit pour le quiz ne correspond pas au nombre choisit (ex. categorie art a 5 questions, le quiz n'aura que 3 questions) +• On peut continuer de répondre a la dernière question du quiz, meme lorsque le quiz est terminé +• Lorsque l'on réponde a la dernière question du quiz apres l'avoir complété le score est modifié et peut meme passé au dessus de 100% (ex. categorie art, le résultat pourra passé a 4/3 de résultat final) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/amplify/.config/project-config.json b/amplify/.config/project-config.json new file mode 100644 index 0000000..e515cc8 --- /dev/null +++ b/amplify/.config/project-config.json @@ -0,0 +1,17 @@ +{ + "providers": [ + "awscloudformation" + ], + "projectName": "quizly", + "version": "3.1", + "frontend": "javascript", + "javascript": { + "framework": "vue", + "config": { + "SourceDir": "src", + "DistributionDir": "dist", + "BuildCommand": "npm.cmd run-script build", + "StartCommand": "npm.cmd run-script serve" + } + } +} \ No newline at end of file diff --git a/amplify/backend/api/quizly/cli-inputs.json b/amplify/backend/api/quizly/cli-inputs.json new file mode 100644 index 0000000..688b82a --- /dev/null +++ b/amplify/backend/api/quizly/cli-inputs.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "serviceConfiguration": { + "apiName": "quizly", + "serviceName": "AppSync", + "defaultAuthType": { + "mode": "API_KEY", + "keyDescription": "api key description", + "expirationTime": 30 + }, + "additionalAuthTypes": [ + { + "mode": "AWS_IAM" + } + ], + "conflictResolution": { + "defaultResolutionStrategy": { + "type": "NONE" + } + } + } +} \ No newline at end of file diff --git a/amplify/backend/api/quizly/parameters.json b/amplify/backend/api/quizly/parameters.json new file mode 100644 index 0000000..1463ce6 --- /dev/null +++ b/amplify/backend/api/quizly/parameters.json @@ -0,0 +1,5 @@ +{ + "AppSyncApiName": "quizly", + "DynamoDBBillingMode": "PAY_PER_REQUEST", + "DynamoDBEnableServerSideEncryption": false +} \ No newline at end of file diff --git a/amplify/backend/api/quizly/resolvers/README.md b/amplify/backend/api/quizly/resolvers/README.md new file mode 100644 index 0000000..89e564c --- /dev/null +++ b/amplify/backend/api/quizly/resolvers/README.md @@ -0,0 +1,2 @@ +Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud. +For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers) \ No newline at end of file diff --git a/amplify/backend/api/quizly/schema.graphql b/amplify/backend/api/quizly/schema.graphql new file mode 100644 index 0000000..8c68fbc --- /dev/null +++ b/amplify/backend/api/quizly/schema.graphql @@ -0,0 +1,7 @@ +type Questions @model @auth(rules: [{allow: public}]) { + id: ID! + text: String + choices: [String] + answer: String + category: String +} diff --git a/amplify/backend/api/quizly/stacks/CustomResources.json b/amplify/backend/api/quizly/stacks/CustomResources.json new file mode 100644 index 0000000..f95feea --- /dev/null +++ b/amplify/backend/api/quizly/stacks/CustomResources.json @@ -0,0 +1,58 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "An auto-generated nested stack.", + "Metadata": {}, + "Parameters": { + "AppSyncApiId": { + "Type": "String", + "Description": "The id of the AppSync API associated with this project." + }, + "AppSyncApiName": { + "Type": "String", + "Description": "The name of the AppSync API", + "Default": "AppSyncSimpleTransform" + }, + "env": { + "Type": "String", + "Description": "The environment name. e.g. Dev, Test, or Production", + "Default": "NONE" + }, + "S3DeploymentBucket": { + "Type": "String", + "Description": "The S3 bucket containing all deployment assets for the project." + }, + "S3DeploymentRootKey": { + "Type": "String", + "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory." + } + }, + "Resources": { + "EmptyResource": { + "Type": "Custom::EmptyResource", + "Condition": "AlwaysFalse" + } + }, + "Conditions": { + "HasEnvironmentParameter": { + "Fn::Not": [ + { + "Fn::Equals": [ + { + "Ref": "env" + }, + "NONE" + ] + } + ] + }, + "AlwaysFalse": { + "Fn::Equals": ["true", "false"] + } + }, + "Outputs": { + "EmptyOutput": { + "Description": "An empty output. You may delete this if you have at least one resource above.", + "Value": "" + } + } +} diff --git a/amplify/backend/api/quizly/transform.conf.json b/amplify/backend/api/quizly/transform.conf.json new file mode 100644 index 0000000..6dbae49 --- /dev/null +++ b/amplify/backend/api/quizly/transform.conf.json @@ -0,0 +1,5 @@ +{ + "Version": 5, + "ElasticsearchWarning": true, + "ResolverConfig": {} +} \ No newline at end of file diff --git a/amplify/backend/backend-config.json b/amplify/backend/backend-config.json new file mode 100644 index 0000000..0a1320d --- /dev/null +++ b/amplify/backend/backend-config.json @@ -0,0 +1,25 @@ +{ + "api": { + "quizly": { + "dependsOn": [], + "output": { + "authConfig": { + "additionalAuthenticationProviders": [ + { + "authenticationType": "AWS_IAM" + } + ], + "defaultAuthentication": { + "apiKeyConfig": { + "apiKeyExpirationDays": 30, + "description": "api key description" + }, + "authenticationType": "API_KEY" + } + } + }, + "providerPlugin": "awscloudformation", + "service": "AppSync" + } + } +} \ No newline at end of file diff --git a/amplify/backend/tags.json b/amplify/backend/tags.json new file mode 100644 index 0000000..b9321d7 --- /dev/null +++ b/amplify/backend/tags.json @@ -0,0 +1,10 @@ +[ + { + "Key": "user:Stack", + "Value": "{project-env}" + }, + { + "Key": "user:Application", + "Value": "{project-name}" + } +] \ No newline at end of file diff --git a/amplify/backend/types/amplify-dependent-resources-ref.d.ts b/amplify/backend/types/amplify-dependent-resources-ref.d.ts new file mode 100644 index 0000000..768ce22 --- /dev/null +++ b/amplify/backend/types/amplify-dependent-resources-ref.d.ts @@ -0,0 +1,9 @@ +export type AmplifyDependentResourcesAttributes = { + "api": { + "quizly": { + "GraphQLAPIEndpointOutput": "string", + "GraphQLAPIIdOutput": "string", + "GraphQLAPIKeyOutput": "string" + } + } +} \ No newline at end of file diff --git a/amplify/cli.json b/amplify/cli.json new file mode 100644 index 0000000..5298a7a --- /dev/null +++ b/amplify/cli.json @@ -0,0 +1,61 @@ +{ + "features": { + "graphqltransformer": { + "addmissingownerfields": true, + "improvepluralization": false, + "validatetypenamereservedwords": true, + "useexperimentalpipelinedtransformer": true, + "enableiterativegsiupdates": true, + "secondarykeyasgsi": true, + "skipoverridemutationinputtypes": true, + "transformerversion": 2, + "suppressschemamigrationprompt": true, + "securityenhancementnotification": false, + "showfieldauthnotification": false, + "usesubusernamefordefaultidentityclaim": true, + "usefieldnameforprimarykeyconnectionfield": false, + "enableautoindexquerynames": true, + "respectprimarykeyattributesonconnectionfield": true, + "shoulddeepmergedirectiveconfigdefaults": false, + "populateownerfieldforstaticgroupauth": true + }, + "frontend-ios": { + "enablexcodeintegration": true + }, + "auth": { + "enablecaseinsensitivity": true, + "useinclusiveterminology": true, + "breakcirculardependency": true, + "forcealiasattributes": false, + "useenabledmfas": true + }, + "codegen": { + "useappsyncmodelgenplugin": true, + "usedocsgeneratorplugin": true, + "usetypesgeneratorplugin": true, + "cleangeneratedmodelsdirectory": true, + "retaincasestyle": true, + "addtimestampfields": true, + "handlelistnullabilitytransparently": true, + "emitauthprovider": true, + "generateindexrules": true, + "enabledartnullsafety": true, + "generatemodelsforlazyloadandcustomselectionset": false + }, + "appsync": { + "generategraphqlpermissions": true + }, + "latestregionsupport": { + "pinpoint": 1, + "translate": 1, + "transcribe": 1, + "rekognition": 1, + "textract": 1, + "comprehend": 1 + }, + "project": { + "overrides": true + } + }, + "debug": {} +} \ No newline at end of file diff --git a/amplify/team-provider-info.json b/amplify/team-provider-info.json new file mode 100644 index 0000000..33aeb02 --- /dev/null +++ b/amplify/team-provider-info.json @@ -0,0 +1,28 @@ +{ + "production": { + "awscloudformation": { + "AuthRoleName": "amplify-quizly-production-153115-authRole", + "UnauthRoleArn": "arn:aws:iam::851725361329:role/amplify-quizly-production-153115-unauthRole", + "AuthRoleArn": "arn:aws:iam::851725361329:role/amplify-quizly-production-153115-authRole", + "Region": "us-east-1", + "DeploymentBucketName": "amplify-quizly-production-153115-deployment", + "UnauthRoleName": "amplify-quizly-production-153115-unauthRole", + "StackName": "amplify-quizly-production-153115", + "StackId": "arn:aws:cloudformation:us-east-1:851725361329:stack/amplify-quizly-production-153115/f030df00-c116-11ee-9184-0e20c0a80dbf", + "AmplifyAppId": "d35khdacc8zaaq" + } + }, + "staging": { + "awscloudformation": { + "AuthRoleName": "amplify-amplify3a9c46796ed44-staging-144243-authRole", + "UnauthRoleArn": "arn:aws:iam::851725361329:role/amplify-amplify3a9c46796ed44-staging-144243-unauthRole", + "AuthRoleArn": "arn:aws:iam::851725361329:role/amplify-amplify3a9c46796ed44-staging-144243-authRole", + "Region": "us-east-1", + "DeploymentBucketName": "amplify-amplify3a9c46796ed44-staging-144243-deployment", + "UnauthRoleName": "amplify-amplify3a9c46796ed44-staging-144243-unauthRole", + "StackName": "amplify-amplify3a9c46796ed44-staging-144243", + "StackId": "arn:aws:cloudformation:us-east-1:851725361329:stack/amplify-amplify3a9c46796ed44-staging-144243/28d52980-c110-11ee-a252-12bf363b2859", + "AmplifyAppId": "d35khdacc8zaaq" + } + } +} \ No newline at end of file diff --git a/cypress/e2e/Quizly.cy.ts b/cypress/e2e/Quizly.cy.ts new file mode 100644 index 0000000..a9f7eb1 --- /dev/null +++ b/cypress/e2e/Quizly.cy.ts @@ -0,0 +1,40 @@ +describe('Quizzy Peak', () => { + it('should allow entering a name', () => { + cy.visit('https://develop.d35khdacc8zaaq.amplifyapp.com/') + cy.get('input.input-field').type('John Wick') + cy.get('input.input-field').should('have.value', 'John Wick') + }) + + it('should allow selecting a category', () => { + cy.visit('https://develop.d35khdacc8zaaq.amplifyapp.com/') + cy.get('select.input-field').first().select('History'); + cy.get('select.input-field').first().should('have.value', 'History'); + }) + + it('should allow selecting the quiz duration', () => { + cy.visit('https://develop.d35khdacc8zaaq.amplifyapp.com/') + cy.get('#startOptions > :nth-child(3)').select('1 minute') + cy.get('#startOptions > :nth-child(3)').should('have.value', '60') + }) + + it('should allow selecting the number of questions', () => { + cy.visit('https://develop.d35khdacc8zaaq.amplifyapp.com/') + cy.get('#startOptions > :nth-child(4)').select('5 questions') + cy.get('#startOptions > :nth-child(4)').should('have.value', '5') + }) + + it('should start the quiz', () => { + cy.visit('https://develop.d35khdacc8zaaq.amplifyapp.com/') + cy.get('input.input-field').type('John Wick') + cy.get('input.input-field').should('have.value', 'John Wick') + cy.get('select.input-field').first().select('Geography'); + cy.get('select.input-field').first().should('have.value', 'Geography'); + cy.get('#startOptions > :nth-child(3)').select('5 minutes') + cy.get('#startOptions > :nth-child(3)').should('have.value', '300') + cy.get('#startOptions > :nth-child(4)').select('10 questions') + cy.get('#startOptions > :nth-child(4)').should('have.value', '10') + cy.get('.start-button').click() + cy.get('.timer').should('be.visible') + }) + + }) diff --git a/package-lock.json b/package-lock.json index 07bc115..cea470f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "vue-router": "^4.2.5" }, "devDependencies": { + "@cypress/vite-dev-server": "^5.0.7", + "@cypress/vue": "^6.0.0", "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.2", "@types/node": "^18.19.2", @@ -21,7 +23,7 @@ "@vue/eslint-config-typescript": "^12.0.0", "@vue/test-utils": "^2.4.4", "@vue/tsconfig": "^0.4.0", - "cypress": "^13.6.1", + "cypress": "^13.7.0", "eslint": "^8.49.0", "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-vue": "^9.17.0", @@ -29,7 +31,7 @@ "npm-run-all2": "^6.1.1", "prettier": "^3.0.3", "start-server-and-test": "^2.0.3", - "typescript": "~5.2.0", + "typescript": "^5.4.2", "vite": "^5.0.5", "vitest": "^1.2.2", "vue-tsc": "^1.8.25" @@ -272,6 +274,119 @@ "node": ">= 6" } }, + "node_modules/@cypress/vite-dev-server": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@cypress/vite-dev-server/-/vite-dev-server-5.0.7.tgz", + "integrity": "sha512-OeVsEvDtoWV/CnvnUEjWny7tsuw84DF78tvnibOWNTOuZzav62U3A07QKK2JDuGAoXQlVTiDWzcCObKIEcOGGg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "find-up": "6.3.0", + "node-html-parser": "5.3.3", + "semver": "^7.5.3" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@cypress/vite-dev-server/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/vue": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@cypress/vue/-/vue-6.0.0.tgz", + "integrity": "sha512-KMfRw8y/kXn/RJqaDdFnYnW7YLk47313UE3Yip+sLDjILJ2kA0WEiEa6MYKe58v8TNRtwcRpUH5xAYVNs1N6/A==", + "dev": true, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "@cypress/webpack-dev-server": "*", + "cypress": ">=7.0.0", + "vue": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@cypress/webpack-dev-server": { + "optional": true + } + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -2417,6 +2532,34 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2447,9 +2590,9 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.6.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.3.tgz", - "integrity": "sha512-d/pZvgwjAyZsoyJ3FOsJT5lDsqnxQ/clMqnNc++rkHjbkkiF2h9s0JsZSyyH4QXhVFW3zPFg82jD25roFLOdZA==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz", + "integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2460,7 +2603,7 @@ "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", @@ -2478,7 +2621,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -2637,6 +2780,70 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -4549,6 +4756,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-html-parser": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.3.3.tgz", + "integrity": "sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==", + "dev": true, + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, "node_modules/nopt": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", @@ -6052,9 +6269,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "devOptional": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 21bd9d6..7dfb397 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "vue-router": "^4.2.5" }, "devDependencies": { + "@cypress/vite-dev-server": "^5.0.7", + "@cypress/vue": "^6.0.0", "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.2", "@types/node": "^18.19.2", @@ -29,7 +31,7 @@ "@vue/eslint-config-typescript": "^12.0.0", "@vue/test-utils": "^2.4.4", "@vue/tsconfig": "^0.4.0", - "cypress": "^13.6.1", + "cypress": "^13.7.0", "eslint": "^8.49.0", "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-vue": "^9.17.0", @@ -37,7 +39,7 @@ "npm-run-all2": "^6.1.1", "prettier": "^3.0.3", "start-server-and-test": "^2.0.3", - "typescript": "~5.2.0", + "typescript": "^5.4.2", "vite": "^5.0.5", "vitest": "^1.2.2", "vue-tsc": "^1.8.25" diff --git a/src/components/__tests__/Leaderboards.spec.js b/src/components/__tests__/Leaderboards.spec.js new file mode 100644 index 0000000..823ccc3 --- /dev/null +++ b/src/components/__tests__/Leaderboards.spec.js @@ -0,0 +1,44 @@ +import { mount } from '@vue/test-utils'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import Leaderboards from '@/components/Leaderboards.vue'; + + +describe('Tests d/intégration pour Leaderboards.vue', () => { + let wrapper; + + beforeEach(() => { + // Montez le composant avec une catégorie sélectionnée + wrapper = mount(Leaderboards, { + props: { + selectedCategory: 'math' + } + }); + }); + + afterEach(() => { + // Démontez le composant après chaque test + wrapper.unmount(); + }); + + it('affiche le tableau des scores pour la catégorie sélectionnée', () => { + // Vérifiez que le composant affiche les scores pour la catégorie 'math' + expect(wrapper.html()).toContain('Category: math'); + }); + + it('affiche un message si aucun score n/est disponible pour la catégorie sélectionnée', () => { + // Changez la catégorie sélectionnée pour une catégorie sans scores + wrapper.setProps({ selectedCategory: 'history' }); + + // Vérifiez que le composant affiche un message approprié + expect(wrapper.html()).toContain('No high scores available for this category yet.'); + }); + + it('affiche un message si aucune catégorie n/est sélectionnée', async () => { + // Changez la catégorie sélectionnée pour une catégorie vide + wrapper.setProps({ selectedCategory: '' }); + await wrapper.vm.$nextTick() + const htmlPTag = wrapper.find('p') + // Vérifiez que le composant affiche un message approprié + expect(htmlPTag.text()).toContain('Select a category to view the leaderboard.'); + }); +}); \ No newline at end of file diff --git a/src/components/__tests__/Leaderboards.unit.spec.js b/src/components/__tests__/Leaderboards.unit.spec.js new file mode 100644 index 0000000..f2b3b65 --- /dev/null +++ b/src/components/__tests__/Leaderboards.unit.spec.js @@ -0,0 +1,42 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Leaderboards from '@/components/Leaderboards.vue'; + +describe('Leaderboards.vue', () => { + let wrapper; + + beforeEach(() => { + vi.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => { + if (key === 'highScores') { + return JSON.stringify({ + Math: [{ username: 'Alice', score: 5 }] + }); + } + }); + + wrapper = mount(Leaderboards, { + props: { + selectedCategory: '' + } + }); + }); + + + it('displays a message to select a category when none is selected', () => { + expect(wrapper.text()).toContain('Select a category to view the leaderboard.'); + }); + + it('shows the category and high scores when selected', async () => { + await wrapper.setProps({ selectedCategory: 'Math' }); + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Category: Math'); + expect(wrapper.text()).toContain('Alice: 5'); + }); + + + it('displays a message when there are no high scores for the selected category', async () => { + await wrapper.setProps({ selectedCategory: 'Science', highScores: { Science: [] } }); + expect(wrapper.text()).toContain('Category: Science'); + expect(wrapper.text()).toContain('No high scores available for this category yet.'); + }); +}); diff --git a/src/components/__tests__/ProgressBar.spec.js b/src/components/__tests__/ProgressBar.spec.js index e905df4..96f1e95 100644 --- a/src/components/__tests__/ProgressBar.spec.js +++ b/src/components/__tests__/ProgressBar.spec.js @@ -1,27 +1,46 @@ -import { mount } from '@vue/test-utils' -import { describe, it, expect } from 'vitest' -import ProgressBar from '@/components/ProgressBar.vue' +import { describe, it, expect } from 'vitest'; +import { mount } from '@vue/test-utils'; +import ProgressBar from '@/components/ProgressBar.vue'; -describe('ProgressBar.vue', () => { - it('computes progressLabel correctly', () => { +describe('ProgressBar Component Integration Tests', () => { + // Test the initial state of the progress bar + it('shows correct initial progress', () => { + const wrapper = mount(ProgressBar, { + props: { + currentQuestionIndex: 0, + totalQuestions: 5 + } + }); + + expect(wrapper.find('.progress-bar').attributes('style')).toContain('width: 20%'); + expect(wrapper.find('.progress-label').text()).toContain('Question 1 of 5'); + }); + + // Test progress update after answering a question + it('updates progress correctly after answering a question', async () => { const wrapper = mount(ProgressBar, { props: { currentQuestionIndex: 1, - totalQuestions: 4 + totalQuestions: 5 } - }) + }); - expect(wrapper.vm.progressLabel).toEqual('Question 2 of 4') - }) + await wrapper.vm.$nextTick(); + expect(wrapper.find('.progress-bar').attributes('style')).toContain('width: 40%'); + expect(wrapper.find('.progress-label').text()).toContain('Question 2 of 5'); + }); - it('computes progressBarWidth correctly', () => { + // Test the completion state of the progress bar + it('shows completion state correctly', async () => { const wrapper = mount(ProgressBar, { props: { - currentQuestionIndex: 2, + currentQuestionIndex: 4, totalQuestions: 5 } - }) + }); - expect(wrapper.vm.progressBarWidth).toEqual('60%') - }) -}) \ No newline at end of file + await wrapper.vm.$nextTick(); + expect(wrapper.find('.progress-bar').attributes('style')).toContain('width: 100%'); + expect(wrapper.find('.progress-label').text()).toContain('Question 5 of 5'); + }); +}); diff --git a/src/components/__tests__/ProgressBar.unit.spec.js b/src/components/__tests__/ProgressBar.unit.spec.js new file mode 100644 index 0000000..9d582ad --- /dev/null +++ b/src/components/__tests__/ProgressBar.unit.spec.js @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import Progressbar from '@/components/ProgressBar.vue' + +describe('Progressbar.vue', () => { + it('computes progressLabel correctly', () => { + const wrapper = mount(Progressbar, { + props: { + currentQuestionIndex: 1, + totalQuestions: 4 + } + }) + expect(wrapper.vm.progressLabel).toEqual('Question 2 of 4') + }) + it('computes the progress bar width correctly', () => { + const wrapper = mount(Progressbar, { + props: { + currentQuestionIndex: 2, + totalQuestions: 5 + } + }) + expect(wrapper.vm.progressBarWidth).toEqual('60%') + //Also check that the progress bar is at 100% in the DOM not just the variable + const progressBar = wrapper.find('.progress-bar') + expect(progressBar.attributes('style')).toContain('width: 60%') + }) + it('handles the progress at the start correctly', () => { + const wrapper = mount(Progressbar, { + props: { + currentQuestionIndex: 0, + totalQuestions: 10 + } + }) + expect(wrapper.vm.progressBarWidth).toEqual('10%') + //Also check that the progress bar is at 100% in the DOM not just the varaible + const progressBar = wrapper.find('.progress-bar') + expect(progressBar.attributes('style')).toContain('width: 10%') + expect(wrapper.vm.progressLabel).toEqual('Question 1 of 10') + }) + + it('handles the progress at the end correctly', () => { + const wrapper = mount(Progressbar, { + props: { + currentQuestionIndex: 9, + totalQuestions: 10 + } + }) + expect(wrapper.vm.progressBarWidth).toEqual('100%') + //Also check that the progress bar is at 100% in the DOM not just the varaible + const progressBar = wrapper.find('.progress-bar') + expect(progressBar.attributes('style')).toContain('width: 100%') + expect(wrapper.vm.progressLabel).toEqual('Question 10 of 10') + }) +}) diff --git a/src/components/__tests__/Quiz.spec.js b/src/components/__tests__/Quiz.spec.js new file mode 100644 index 0000000..640b890 --- /dev/null +++ b/src/components/__tests__/Quiz.spec.js @@ -0,0 +1,38 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Quiz from '@/components/Quiz.vue'; + +describe('Quiz.vue Integration Tests', () => { + let wrapper; + const questions = [ + { text: 'What is 2 + 2?', choices: ['3', '4', '5', '6'], answer: '4' }, + { text: 'What is the capital of France?', choices: ['Paris', 'London', 'Berlin', 'Madrid'], answer: 'Paris' } + ]; + + beforeEach(() => { + wrapper = mount(Quiz, { + props: { + questions: questions, + currentQuestionIndex: 0 + } + }); + }); + + it('renders the current question and its choices correctly', () => { + const questionText = wrapper.find('h2').text(); + expect(questionText).toContain(questions[0].text); + const choiceButtons = wrapper.findAll('.choice-button'); + expect(choiceButtons).toHaveLength(questions[0].choices.length); + }); + + it('emits an "answer-selected" event when a choice is selected', async () => { + await wrapper.find('.choice-button').trigger('click'); + expect(wrapper.emitted()['answer-selected']).toBeTruthy(); + }); + + it('emits a "quiz-completed" event when the last question is answered', async () => { + await wrapper.setProps({ currentQuestionIndex: questions.length - 1 }); + await wrapper.findAll('.choice-button')[0].trigger('click'); + expect(wrapper.emitted()['quiz-completed']).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/components/__tests__/Quiz.unit.spec.js b/src/components/__tests__/Quiz.unit.spec.js new file mode 100644 index 0000000..3592bb4 --- /dev/null +++ b/src/components/__tests__/Quiz.unit.spec.js @@ -0,0 +1,62 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Quiz from '@/components/Quiz.vue'; + +describe('Quiz.vue', () => { + let wrapper; + + const mockQuestions = [ + { + text: 'Question 1', + choices: ['Answer A', 'Answer B', 'Answer C', 'Answer D'], + }, + ]; + + beforeEach(() => { + wrapper = mount(Quiz, { + props: { + questions: mockQuestions, + currentQuestionIndex: 0, + }, + }); + }); + + it('does not render quiz content if there is no current question', () => { + const wrapper = mount(Quiz, { + props: { + questions: mockQuestions, + currentQuestionIndex: -1, + }, + }); + expect(wrapper.find('.quiz').exists()).toBeFalsy(); + expect(wrapper.find('.choice-button').exists()).toBeFalsy(); + }); + + it('renders correctly with a question', () => { + expect(wrapper.find('.quiz h2').text()).toContain('Question 1'); + expect(wrapper.findAll('.choice-button')).toHaveLength(4); + }); + + it('emits "quiz-completed" if the last question is answered', async () => { + const lastQuestionWrapper = mount(Quiz, { + props: { + questions: mockQuestions, + currentQuestionIndex: mockQuestions.length - 1, + }, + }); + + await lastQuestionWrapper.findAll('.choice-button')[0].trigger('click'); + expect(lastQuestionWrapper.emitted()).toHaveProperty('quiz-completed'); + }); + + it('correctly identifies the last question', () => { + const wrapper = mount(Quiz, { + props: { + questions: mockQuestions, + currentQuestionIndex: mockQuestions.length - 1, + }, + }); + expect(wrapper.vm.isLastQuestion).toBeTruthy(); + }); + +}); diff --git a/src/components/__tests__/Results.unit.spec.js b/src/components/__tests__/Results.unit.spec.js new file mode 100644 index 0000000..a5699b1 --- /dev/null +++ b/src/components/__tests__/Results.unit.spec.js @@ -0,0 +1,44 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Results from '@/components/Results.vue'; + +describe('Results.vue', () => { + let wrapper; + const mockQuestions = [ + { text: 'Question 1?', answer: 'Answer 1' }, + { text: 'Question 2?', answer: 'Answer 2' } + ]; + const mockUserAnswers = ['Answer 1', 'Wrong Answer']; + + beforeEach(() => { + wrapper = mount(Results, { + props: { + score: 1, + questions: mockQuestions, + userAnswers: mockUserAnswers + } + }); + }); + + it('renders the correct score', () => { + expect(wrapper.find('p').text()).toContain('Your score: 1/2'); + }); + + it('lists all the questions with the user answers and correct indication', () => { + const listItems = wrapper.findAll('li'); + expect(listItems).toHaveLength(mockQuestions.length); + + // Verify first question is correctly answered + expect(listItems[0].text()).toContain('Q1: Question 1?'); + expect(listItems[0].text()).toContain('Your answer: Answer 1 ✅'); + + // Verify second question is incorrectly answered + expect(listItems[1].text()).toContain('Q2: Question 2?'); + expect(listItems[1].text()).toContain('Your answer: Wrong Answer ❌ (Correct answer: Answer 2)'); + }); + + it('emits the "restart" event when the restart button is clicked', async () => { + await wrapper.find('.restart-button').trigger('click'); + expect(wrapper.emitted()).toHaveProperty('restart'); + }); +}); diff --git a/src/components/__tests__/StartOptions.unit.spec.js b/src/components/__tests__/StartOptions.unit.spec.js new file mode 100644 index 0000000..c1b9ba6 --- /dev/null +++ b/src/components/__tests__/StartOptions.unit.spec.js @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { shallowMount } from '@vue/test-utils'; +import StartOptions from '../StartOptions.vue'; + +describe('StartOptions', () => { + it('renders correctly', async () => { + const categories = ['Category1', 'Category2', 'Category3']; + const wrapper = shallowMount(StartOptions, { + props: { categories } + }); + + // Vérifie que les éléments sont rendus correctement + expect(wrapper.find('.input-field').exists()).toBe(true); + expect(wrapper.find('select').exists()).toBe(true); + expect(wrapper.find('.start-button').exists()).toBe(true); + expect(wrapper.find('.edit-questions-button').exists()).toBe(true); + }); + + it('emits start-quiz event when Start Quiz button is clicked', async () => { + const categories = ['Category1', 'Category2', 'Category3']; + const wrapper = shallowMount(StartOptions, { + props: { categories } + }); + + // Simule le remplissage des champs et le clic sur le bouton Start Quiz + wrapper.find('.input-field').setValue('John Doe'); + wrapper.find('select').setValue('Category1'); + wrapper.find('.start-button').trigger('click'); + + // Attend la prochaine mise à jour du DOM + await wrapper.vm.$nextTick(); + + // Vérifie que l'événement start-quiz a été émis avec les bonnes valeurs + expect(wrapper.emitted('start-quiz')).toBeTruthy(); + expect(wrapper.emitted('start-quiz')[0]).toEqual([{ + username: 'John Doe', + selectedCategory: 'Category1', + selectedTimeLimit: '60', + selectedNumQuestions: '5' + }]); + }); +}); \ No newline at end of file diff --git a/src/components/__tests__/Timer.spec.js b/src/components/__tests__/Timer.spec.js new file mode 100644 index 0000000..ae5a402 --- /dev/null +++ b/src/components/__tests__/Timer.spec.js @@ -0,0 +1,56 @@ +import { mount } from '@vue/test-utils'; +import Timer from '@/components/Timer.vue'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; + + + +describe('Timer', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(Timer, { + props: { + duration: 30, // 30 secondes + }, + }); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + it('renders the timer with initial time', () => { + expect(wrapper.text()).toContain('Time left: 0:30'); + }); + + it('decrements the time every second', async () => { + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Time left: 0:30'); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Time left: 0:29'); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Time left: 0:28'); + }); + + it('stops the timer when component is unmounted', async () => { + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Time left: 0:30'); + + wrapper.unmount(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(wrapper.text()).toContain('Time left: 0:30'); // Timer should stop + }); + + it('stops the timer when calling stop method', async () => { + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain('Time left: 0:30'); + + wrapper.vm.stop(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(wrapper.text()).toContain('Time left: 0:30'); // Timer should stop + }); +}); \ No newline at end of file diff --git a/src/components/__tests__/Timer.unit.spec.js b/src/components/__tests__/Timer.unit.spec.js new file mode 100644 index 0000000..29e8160 --- /dev/null +++ b/src/components/__tests__/Timer.unit.spec.js @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { shallowMount } from '@vue/test-utils'; +import Timer from '../Timer.vue'; + +// Définition des tests +describe('Timer', () => { + it('renders correctly with initial time', async () => { + const duration = 60; // 1 minute + const wrapper = shallowMount(Timer, { + props: { duration } + }); + + // Vérifie que les éléments sont rendus correctement + expect(wrapper.find('.timer').exists()).toBe(true); + expect(wrapper.text()).toContain('Time left:'); + }); + + it('updates time correctly after 1 second', async () => { + const duration = 5; // 5 secondes + const wrapper = shallowMount(Timer, { + props: { duration } + }); + + // Attends 1 seconde + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Vérifie que le temps restant a été mis à jour + expect(wrapper.text()).toContain('Time left: 0:04'); + }); + + it('stops the timer when stop method is called', async () => { + const duration = 10; // 10 secondes + const wrapper = shallowMount(Timer, { + props: { duration } + }); + + // Attend un peu + await new Promise(resolve => setTimeout(resolve, 500)); + + // Arrête le timer + wrapper.vm.stop(); + + // Retient le temps restant actuel + const timeLeftBeforeStop = wrapper.vm.timeLeft; + + // Attend un peu plus + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Vérifie que le temps restant n'a pas changé + expect(wrapper.vm.timeLeft).toBe(timeLeftBeforeStop); + }); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 22a166b..753bfb8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,14 +13,17 @@ ], "compilerOptions": { "outDir": "out", - "module": "commonjs", - "target": "es6", + // "module": "commonjs", Kept in case of a problem, but added to make Cypress work + // "target": "es6", Kept in case of a problem, but added to make Cypress work + "module": "ESNext", + "target": "ESNext", "moduleResolution": "node", "allowJs": true, "alwaysStrict": true, "noImplicitAny": true, "removeComments": true, - "sourceMap": true, + "sourceMap": false, + "inlineSourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true },