From 78d18034fabda709efcc3d947d82c1bb894831b8 Mon Sep 17 00:00:00 2001 From: Elia Bieri Date: Wed, 31 Jul 2024 11:45:42 +0200 Subject: [PATCH] Implement unified grading --- .../CompetenceCertificateDetailPage.vue | 26 +- .../CompetenceCertificateListPage.vue | 48 ++-- .../pages/competence/CompetenceIndexPage.vue | 41 ++-- client/src/pages/competence/utils.ts | 53 ++++ .../competenceCertificate.cy.js | 230 +++++++++++------- cypress/support/commands.js | 98 ++++---- package.json | 1 + .../core/management/commands/cypress_reset.py | 66 ++++- 8 files changed, 352 insertions(+), 211 deletions(-) diff --git a/client/src/pages/competence/CompetenceCertificateDetailPage.vue b/client/src/pages/competence/CompetenceCertificateDetailPage.vue index aa5dd79c..a83cbd3e 100644 --- a/client/src/pages/competence/CompetenceCertificateDetailPage.vue +++ b/client/src/pages/competence/CompetenceCertificateDetailPage.vue @@ -6,6 +6,8 @@ import { useCertificateQuery, useCurrentCourseSession } from "@/composables"; import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue"; import { getCertificates } from "@/services/competence"; import { getPreviousRoute } from "@/router/history"; +import { mergeCompetenceCertificates } from "./utils"; +import { useCourseSessionsStore } from "@/stores/courseSessions"; const props = defineProps<{ courseSlug: string; @@ -15,26 +17,24 @@ const props = defineProps<{ log.debug("CompetenceCertificateDetailPage setup", props); -const courseSession = useCurrentCourseSession(); -const certificatesQuery = useCertificateQuery( - props.userId, - props.courseSlug, - courseSession.value -).certificatesQuery; +const store = useCourseSessionsStore(); +const certificateQueries = store.allCourseSessions.map((courseSession) => { + return useCertificateQuery(props.userId, props.courseSlug, courseSession) + .certificatesQuery; +}); const certificate = computed(() => { - const certificates = getCertificates( - certificatesQuery.data.value, - props.userId ?? null - ); + const competenceCertificatesPerCs = certificateQueries.map((query) => { + return getCertificates(query.data.value, props.userId!) + ?.competence_certificates as unknown as CompetenceCertificate[]; + }); + const certificates = mergeCompetenceCertificates(competenceCertificatesPerCs.flat()); if (!certificates) { return null; } - return ( - (certificates.competence_certificates as unknown as CompetenceCertificate[]) ?? [] - ).find((cc) => cc.slug.endsWith(props.certificateSlug)); + return certificates.find((cc) => cc.slug.endsWith(props.certificateSlug)); }); diff --git a/client/src/pages/competence/CompetenceCertificateListPage.vue b/client/src/pages/competence/CompetenceCertificateListPage.vue index 5bd80975..8db065a8 100644 --- a/client/src/pages/competence/CompetenceCertificateListPage.vue +++ b/client/src/pages/competence/CompetenceCertificateListPage.vue @@ -2,11 +2,12 @@ import log from "loglevel"; import { computed, onMounted } from "vue"; import type { CompetenceCertificate } from "@/types"; -import { useCertificateQuery, useCurrentCourseSession } from "@/composables"; +import { useCertificateQuery } from "@/composables"; import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue"; import { assignmentsUserPoints, calcCompetencesTotalGrade, + mergeCompetenceCertificates, } from "@/pages/competence/utils"; import { useRoute } from "vue-router"; import { getCertificates } from "@/services/competence"; @@ -23,48 +24,29 @@ log.debug("CompetenceCertificateListPage setup", props); const route = useRoute(); const store = useCourseSessionsStore(); +console.log( + `Loading competence certificates from ${store.allCourseSessions.length} course sessions:`, + store.allCourseSessions.map((cs) => cs.title) +); const certificateQueries = store.allCourseSessions.map((courseSession) => { return useCertificateQuery(props.userId, props.courseSlug, courseSession) .certificatesQuery; }); -const competenceCertificates = computed(() => { - const competenceCertificates: Record = {}; - for (const query of certificateQueries) { - const certificates = - (getCertificates(query.data.value, props.userId ?? null) - ?.competence_certificates as unknown as CompetenceCertificate[]) ?? []; - for (const certificate of certificates) { - if (!competenceCertificates[certificate.id]) { - // Competence certificate does not exist yet - competenceCertificates[certificate.id] = certificate; - } else { - // Merge with assignment completions of existing competence certificate. If there are multiple completions for the same assignment, keep the latest one. - competenceCertificates[certificate.id].assignments.push( - ...certificate.assignments.filter((assignment) => { - const existingAssignment = competenceCertificates[ - certificate.id - ].assignments.find((a) => a.id === assignment.id); - return ( - !existingAssignment || - dayjs(assignment.completion?.evaluation_submitted_at).isAfter( - dayjs(existingAssignment.completion?.evaluation_submitted_at) - ) - ); - }) - ); - } - } - } - return Object.values(competenceCertificates); +const mergedCertificates = computed(() => { + const competenceCertificatesPerCs = certificateQueries.map((query) => { + return getCertificates(query.data.value, props.userId ?? null) + ?.competence_certificates as unknown as CompetenceCertificate[]; + }); + return mergeCompetenceCertificates(competenceCertificatesPerCs.flat()); }); const assignments = computed(() => { - return competenceCertificates?.value?.flatMap((cc) => cc.assignments); + return mergedCertificates?.value?.flatMap((cc) => cc.assignments); }); const totalGrade = computed(() => { - return calcCompetencesTotalGrade(competenceCertificates.value ?? []); + return calcCompetencesTotalGrade(mergedCertificates.value ?? []); }); const userPointsEvaluatedAssignments = computed(() => { @@ -121,7 +103,7 @@ onMounted(async () => {
cs.title) +); +const certificateQueries = store.allCourseSessions.map((courseSession) => { + return useCertificateQuery(undefined, props.courseSlug, courseSession) + .certificatesQuery; }); -const competenceCertificates = computed(() => { - return ( - (certificatesQuery.data.value?.competence_certificate_list - ?.competence_certificates as unknown as CompetenceCertificate[]) ?? [] - ); +const mergedCertificates = computed(() => { + const competenceCertificatesPerCs = certificateQueries.map((query) => { + return getCertificates(query.data.value, null) + ?.competence_certificates as unknown as CompetenceCertificate[]; + }); + + return mergeCompetenceCertificates(competenceCertificatesPerCs.flat()); }); const allAssignments = computed(() => { - return competenceCertificates.value.flatMap((cc) => cc.assignments); + return mergedCertificates.value.flatMap((cc) => cc.assignments); }); const userPointsEvaluatedAssignments = computed(() => { @@ -49,7 +54,7 @@ const userPointsEvaluatedAssignments = computed(() => { const currentCourseSession = useCurrentCourseSession(); -const isLoaded = computed(() => !certificatesQuery.fetching.value); +const isLoaded = computed(() => !certificateQueries.some((q) => q.fetching.value)); const router = useRouter(); @@ -68,7 +73,7 @@ const router = useRouter();
{{ $t("a.Erfahrungsnote üK") }}: - {{ calcCompetencesTotalGrade(competenceCertificates ?? []) }} + {{ calcCompetencesTotalGrade(mergedCertificates ?? []) }} @@ -83,7 +88,7 @@ const router = useRouter();
+ > = {}; + competenceCertificates.forEach((certificate) => { + if (!certificate) { + return; + } + if (!groupedCompetenceCertificates[certificate.id]) { + groupedCompetenceCertificates[certificate.id] = []; + } + groupedCompetenceCertificates[certificate.id].push(certificate); + }); + + console.log( + `Found ${Object.keys(groupedCompetenceCertificates).length} competence certificates over all course sessions` + ); + + return Object.values(groupedCompetenceCertificates).map((certificates) => { + const mergedCertificate: CompetenceCertificate = { + ...certificates[0], + assignments: [], + }; + certificates.forEach((certificate) => { + certificate.assignments.forEach((assignment) => { + const existingAssignment = mergedCertificate.assignments.find( + (a) => a.id === assignment.id + ); + if (!existingAssignment) { + mergedCertificate.assignments.push(assignment); + } else if ( + assignment.completion != null && + (existingAssignment.completion == null || + dayjs(existingAssignment.completion.evaluation_submitted_at).isBefore( + assignment.completion.evaluation_submitted_at + )) + ) { + mergedCertificate.assignments.splice( + mergedCertificate.assignments.findIndex((a) => a.id === assignment.id), + 1 + ); + mergedCertificate.assignments.push(assignment); + } + }); + }); + return mergedCertificate; + }); +} diff --git a/cypress/e2e/competenceNavi/competenceCertificate.cy.js b/cypress/e2e/competenceNavi/competenceCertificate.cy.js index aea07312..b038d630 100644 --- a/cypress/e2e/competenceNavi/competenceCertificate.cy.js +++ b/cypress/e2e/competenceNavi/competenceCertificate.cy.js @@ -1,26 +1,26 @@ -import { login } from "../helpers"; +import { login } from "../helpers" describe("competenceCertificate.cy.js", () => { - beforeEach(() => {}); + beforeEach(() => { }) it("check without points", () => { - cy.manageCommand("cypress_reset"); - login("test-student1@example.com", "test"); - cy.visit("/course/test-lehrgang/competence"); + cy.manageCommand("cypress_reset") + login("test-student1@example.com", "test") + cy.visit("/course/test-lehrgang/competence") cy.get('[data-cy="certificate-total-points-text"]').contains( "Der Punktestand wird zu einem späteren Zeitpunkt berechnet." - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' - ).and("contain", "0 von 2 Kompetenznachweis-Elementen"); + ).and("contain", "0 von 2 Kompetenznachweis-Elementen") // check on certificates page - cy.get('[data-cy="certificates-show-all-button"]').click(); + cy.get('[data-cy="certificates-show-all-button"]').click() cy.get('[data-cy="certificate-total-points-text"]').contains( "Der Punktestand wird zu einem späteren Zeitpunkt berechnet." - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' @@ -29,100 +29,100 @@ describe("competenceCertificate.cy.js", () => { "contain", "Der Punktestand wird zu einem späteren Zeitpunkt berechnet." ) - .and("contain", "0 von 2 Kompetenznachweis-Elementen"); + .and("contain", "0 von 2 Kompetenznachweis-Elementen") // check certificate detail page cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]' - ).click(); + ).click() cy.get( '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]' - ).should("contain", "Höchstpunktzahl"); + ).should("contain", "Höchstpunktzahl") cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' - ).should("contain", "Höchstpunktzahl"); - }); + ).should("contain", "Höchstpunktzahl") + }) it("check with finished passed edoniq test", () => { cy.manageCommand( "cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 0" - ); - login("test-student1@example.com", "test"); - cy.visit("/course/test-lehrgang/competence"); + ) + login("test-student1@example.com", "test") + cy.visit("/course/test-lehrgang/competence") cy.get('[data-cy="certificate-total-points-text"]').contains( "Erfahrungsnote üK: 5" - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .should("contain", "Note: 5") - .and("contain", "1 von 2 Kompetenznachweis-Elementen"); + .and("contain", "1 von 2 Kompetenznachweis-Elementen") // check on certificates page - cy.get('[data-cy="certificates-show-all-button"]').click(); + cy.get('[data-cy="certificates-show-all-button"]').click() cy.get('[data-cy="certificate-total-points-text"]') .should("contain", "Erfahrungsnote üK") - .and("contain", "Zwischenstand"); - cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5"); + .and("contain", "Zwischenstand") + cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' - ).should("contain", "Note: 5"); + ).should("contain", "Note: 5") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' - ).should("contain", "Ungerundete Note: 4.96"); + ).should("contain", "Ungerundete Note: 4.96") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .and("contain", "Zwischenstand") - .and("contain", "1 von 2 Kompetenznachweis-Elementen"); + .and("contain", "1 von 2 Kompetenznachweis-Elementen") // check certificate detail page cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]' - ).click(); + ).click() cy.get( '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]' ) .should("contain", "Höchstpunktzahl") - .and("contain", "Ergebnisse abgegeben"); + .and("contain", "Ergebnisse abgegeben") cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' ) .should("contain", "19") .and("contain", "Bewertung freigegeben") - .and("not.contain", "Nicht Bestanden"); + .and("not.contain", "Nicht Bestanden") // it can open learning content page directly cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]' - ).click(); + ).click() cy.get('[data-cy="test-result"]') .should("contain", "19 von 24 Punkten") - .and("contain", "79%"); - }); + .and("contain", "79%") + }) it("check with finished failed edoniq test", () => { cy.manageCommand( "cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24 0" - ); - login("test-student1@example.com", "test"); + ) + login("test-student1@example.com", "test") // go to certificate detail page cy.visit( "/course/test-lehrgang/competence/certificates/kompetenznachweis-1" - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' - ).should("contain", "Note: 3"); + ).should("contain", "Note: 3") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' - ).should("contain", "Ungerundete Note: 3.08"); + ).should("contain", "Ungerundete Note: 3.08") cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' @@ -130,90 +130,148 @@ describe("competenceCertificate.cy.js", () => { .should("contain", "10") .and("contain", "Bewertung freigegeben") .and("contain", "42%") - .and("contain", "Nicht bestanden"); + .and("contain", "Nicht bestanden") // it can open learning content page directly cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]' - ).click(); + ).click() cy.get('[data-cy="test-result"]') .should("contain", "10 von 24 Punkten") .and("contain", "42%") - .and("contain", "Nicht bestanden"); - }); + .and("contain", "Nicht bestanden") + }) it("check with finished edoniq test and finished casework", () => { cy.manageCommand( "cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24 0" - ); - login("test-student1@example.com", "test"); - cy.visit("/course/test-lehrgang/competence"); + ) + login("test-student1@example.com", "test") + cy.visit("/course/test-lehrgang/competence") cy.get('[data-cy="certificate-total-points-text"]').contains( "Erfahrungsnote üK: 5.5" - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .should("contain", "Note: 5.5") - .and("contain", "2 von 2 Kompetenznachweis-Elementen"); + .and("contain", "2 von 2 Kompetenznachweis-Elementen") // check on certificates page - cy.get('[data-cy="certificates-show-all-button"]').click(); + cy.get('[data-cy="certificates-show-all-button"]').click() cy.get('[data-cy="certificate-total-points-text"]') .should("contain", "Erfahrungsnote üK") .and("contain", "Note: 5.5") - .and("not.contain", "Zwischenstand"); + .and("not.contain", "Zwischenstand") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .and("not.contain", "Zwischenstand") - .and("contain", "2 von 2 Kompetenznachweis-Elementen"); + .and("contain", "2 von 2 Kompetenznachweis-Elementen") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' - ).should("contain", "Note: 5.5"); + ).should("contain", "Note: 5.5") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' - ).should("contain", "Ungerundete Note: 5.48"); + ).should("contain", "Ungerundete Note: 5.48") // check certificate detail page cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]' - ).click(); + ).click() cy.get( '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]' ) .should("contain", "24") .and("contain", "von 24 Punkten") - .and("contain", "Bewertung freigegeben"); + .and("contain", "Bewertung freigegeben") cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' ) .should("contain", "19") .and("contain", "von 24 Punkten") - .and("contain", "Bewertung freigegeben"); - }); + .and("contain", "Bewertung freigegeben") + }) + + it("check with finished edoniq test and finished casework in different course sessions", () => { + const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4" + const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900" + const TEST_COURSE_SESSION_ZURICH_ID = -2 + cy.manageCommand( + `cypress_reset --create-assignment-evaluation --assignment-evaluation-user-id ${TEST_TRAINER2_USER_ID} --assignment-completion-user-id ${TEST_STUDENT2_USER_ID} --edoniq-user-id ${TEST_STUDENT2_USER_ID} --edoniq-course-session-id '${TEST_COURSE_SESSION_ZURICH_ID}' --create-edoniq-test-results 19 24 0` + ) + login("test-student2@example.com", "test") + cy.visit("/course/test-lehrgang/competence") + + cy.get('[data-cy="certificate-total-points-text"]').contains( + "Erfahrungsnote üK: 5.5" + ) + + cy.get( + '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' + ) + .should("contain", "Note: 5.5") + .and("contain", "2 von 2 Kompetenznachweis-Elementen") + + // check on certificates page + cy.get('[data-cy="certificates-show-all-button"]').click() + cy.get('[data-cy="certificate-total-points-text"]') + .should("contain", "Erfahrungsnote üK") + .and("contain", "Note: 5.5") + .and("not.contain", "Zwischenstand") + + cy.get( + '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' + ) + .and("not.contain", "Zwischenstand") + .and("contain", "2 von 2 Kompetenznachweis-Elementen") + cy.get( + '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' + ).should("contain", "Note: 5.5") + cy.get( + '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' + ).should("contain", "Ungerundete Note: 5.48") + + // check certificate detail page + cy.get( + '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]' + ).click() + + cy.get( + '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]' + ) + .should("contain", "24") + .and("contain", "von 24 Punkten") + .and("contain", "Bewertung freigegeben") + cy.get( + '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' + ) + .should("contain", "19") + .and("contain", "von 24 Punkten") + .and("contain", "Bewertung freigegeben") + }) it("check with finished edoniq test with deducted points", () => { cy.manageCommand( "cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 8" - ); - login("test-student1@example.com", "test"); + ) + login("test-student1@example.com", "test") // go to certificate detail page cy.visit( "/course/test-lehrgang/competence/certificates/kompetenznachweis-1" - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' - ).should("contain", "Note: 3.5"); + ).should("contain", "Note: 3.5") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' - ).should("contain", "Ungerundete Note: 3.29"); + ).should("contain", "Ungerundete Note: 3.29") cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]' @@ -222,61 +280,61 @@ describe("competenceCertificate.cy.js", () => { .and("contain", "Bewertung freigegeben") .and("contain", "46%") .and("contain", "mit Abzug") - .and("contain", "Nicht bestanden"); + .and("contain", "Nicht bestanden") // it can open learning content page directly cy.get( '[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]' - ).click(); + ).click() cy.get('[data-cy="test-result"]') .should("contain", "11 von 24 Punkten") .and("contain", "46%") .and("contain", "Punkte aus Bewertung: 19") .and("contain", "Abgezogene Punkte: 8") .and("contain", "Grund: Edoniq Punkteabzug Test") - .and("contain", "Nicht bestanden"); - }); + .and("contain", "Nicht bestanden") + }) it("check with finished casework and points deducted", () => { cy.manageCommand( "cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 4,6,4,3,2 --assignment-points-deducted 5" - ); - login("test-student1@example.com", "test"); - cy.visit("/course/test-lehrgang/competence"); + ) + login("test-student1@example.com", "test") + cy.visit("/course/test-lehrgang/competence") cy.get('[data-cy="certificate-total-points-text"]').contains( "Erfahrungsnote üK: 4" - ); + ) cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .should("contain", "Note: 4") - .and("contain", "1 von 2 Kompetenznachweis-Elementen"); + .and("contain", "1 von 2 Kompetenznachweis-Elementen") // check on certificates page - cy.get('[data-cy="certificates-show-all-button"]').click(); + cy.get('[data-cy="certificates-show-all-button"]').click() cy.get('[data-cy="certificate-total-points-text"]') .should("contain", "Erfahrungsnote üK") .and("contain", "Note: 4") - .and("contain", "Zwischenstand"); + .and("contain", "Zwischenstand") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]' ) .and("contain", "Zwischenstand") - .and("contain", "1 von 2 Kompetenznachweis-Elementen"); + .and("contain", "1 von 2 Kompetenznachweis-Elementen") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]' - ).should("contain", "Note: 4"); + ).should("contain", "Note: 4") cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]' - ).should("contain", "Ungerundete Note: 3.92"); + ).should("contain", "Ungerundete Note: 3.92") // check certificate detail page cy.get( '[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]' - ).click(); + ).click() cy.get( '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]' @@ -285,28 +343,28 @@ describe("competenceCertificate.cy.js", () => { .and("contain", "von 24 Punkten") .and("contain", "58%") .and("contain", "mit Abzug") - .and("contain", "Bewertung freigegeben"); + .and("contain", "Bewertung freigegeben") cy.get( '[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"] [data-cy="open-learning-content"]' - ).click(); - cy.get('[data-cy="user-points"]').should("contain", "14"); + ).click() + cy.get('[data-cy="user-points"]').should("contain", "14") cy.get('[data-cy="total-points"]').should( "contain", "von 24 Punkten (58%)" - ); + ) cy.get('[data-cy="points-deducted"]') .should("contain", "Punkte aus Bewertung: 19") .and("contain", "Abgezogene Punkte: 5") - .and("contain", "Grund: Assignment Punkteabzug Test"); - }); + .and("contain", "Grund: Assignment Punkteabzug Test") + }) it("should display link to details", () => { - cy.manageCommand("cypress_reset"); - login("test-student1@example.com", "test"); - cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback"); + cy.manageCommand("cypress_reset") + login("test-student1@example.com", "test") + cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback") cy.get('[data-cy^="self-eval-"][data-cy$="-detail-url"]:first').contains( "Selbsteinschätzung anschauen" - ); - }); -}); + ) + }) +}) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 50dfdf30..9c16dcb8 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -50,20 +50,20 @@ // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -const _ = Cypress._; +const _ = Cypress._ Cypress.Commands.add("manageCommand", (command, preCommand = "") => { - const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`; - console.log(execCommand); + const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress` + console.log(execCommand) // hack to add my asdf python instance to the path // so I can run the test directly from within IntelliJ let pythonPaths = [ "/Users/daniel/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin", - "/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10.6/bin", + "/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10/bin", "/Users/christiancueni/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin", "/Users/renzo/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin", - ]; - let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`; + ] + let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}` return cy .exec(`bash -c "${bashCommand}"`, { failOnNonZeroExit: true, @@ -73,14 +73,14 @@ Cypress.Commands.add("manageCommand", (command, preCommand = "") => { throw new Error(`Execution of "${command}" failed Exit code: ${result.code} Stdout:\n${result.stdout} - Stderr:\n${result.stderr}`); + Stderr:\n${result.stderr}`) } - }); -}); + }) +}) Cypress.Commands.add("manageShellCommand", (command) => { - return cy.manageCommand(`shell -c '${command}'`); -}); + return cy.manageCommand(`shell -c '${command}'`) +}) function loadObjectJson( key, @@ -89,28 +89,28 @@ function loadObjectJson( serializerModelPath, valueAsString = false ) { - const djangoModel = _.last(djangoModelPath.split(".")); - const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join("."); - const serializerModel = _.last(serializerModelPath.split(".")); + const djangoModel = _.last(djangoModelPath.split(".")) + const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".") + const serializerModel = _.last(serializerModelPath.split(".")) const serializerModelImportPath = _.initial( serializerModelPath.split(".") - ).join("."); + ).join(".") - let filterPart = `${key}=${value}`; + let filterPart = `${key}=${value}` if (valueAsString) { - filterPart = `${key}=\\"${value}\\"`; + filterPart = `${key}=\\"${value}\\"` } if (_.isArray(key)) { filterPart = _.zip(key, value) .map(([k, v]) => { if (valueAsString) { - return `${k}=\\"${v}\\"`; + return `${k}=\\"${v}\\"` } else { - return `${k}=${v}`; + return `${k}=${v}` } }) - .join(","); + .join(",") } const command = `from ${djangoModelImportPath} import ${djangoModel}; @@ -119,13 +119,13 @@ function loadObjectJson( object = ${djangoModel}.objects.filter(${filterPart}).first(); print(create_json_from_objects(object, ${serializerModel}, many=False)); exit(); - `.replace(/(?:\r\n|\r|\n)/g, ""); + `.replace(/(?:\r\n|\r|\n)/g, "") return cy.manageShellCommand(command).then((result) => { - const objectJson = JSON.parse(result.stdout); + const objectJson = JSON.parse(result.stdout) // console.log(command); - console.log(objectJson); - return objectJson; - }); + console.log(objectJson) + return objectJson + }) } Cypress.Commands.add("loadAssignmentCompletion", (key, value) => { @@ -135,8 +135,8 @@ Cypress.Commands.add("loadAssignmentCompletion", (key, value) => { "vbv_lernwelt.assignment.models.AssignmentCompletion", "vbv_lernwelt.assignment.serializers.CypressAssignmentCompletionSerializer", true - ); -}); + ) +}) Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => { return loadObjectJson( @@ -145,8 +145,8 @@ Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => { "vbv_lernwelt.core.models.SecurityRequestResponseLog", "vbv_lernwelt.core.serializers.CypressSecurityRequestResponseLogSerializer", true - ); -}); + ) +}) Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => { return loadObjectJson( @@ -155,8 +155,8 @@ Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => { "vbv_lernwelt.core.models.ExternalApiRequestLog", "vbv_lernwelt.core.serializers.CypressExternalApiRequestLogSerializer", true - ); -}); + ) +}) Cypress.Commands.add("loadFeedbackResponse", (key, value) => { return loadObjectJson( @@ -165,8 +165,8 @@ Cypress.Commands.add("loadFeedbackResponse", (key, value) => { "vbv_lernwelt.feedback.models.FeedbackResponse", "vbv_lernwelt.feedback.serializers.CypressFeedbackResponseSerializer", true - ); -}); + ) +}) Cypress.Commands.add("loadCheckoutInformation", (key, value) => { return loadObjectJson( @@ -175,37 +175,37 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => { "vbv_lernwelt.shop.models.CheckoutInformation", "vbv_lernwelt.shop.serializers.CypressCheckoutInformationSerializer", true - ); -}); + ) +}) Cypress.Commands.add("makeSelfEvaluation", (answers) => { for (let i = 0; i < answers.length; i++) { - const answer = answers[i]; + const answer = answers[i] if (answer) { - cy.get('[data-cy="success"]').click(); + cy.get('[data-cy="success"]').click() } else { - cy.get('[data-cy="fail"]').click(); + cy.get('[data-cy="fail"]').click() } if (i < answers.length - 1) { - cy.get('[data-cy="next-step"]').click({ force: true }); + cy.get('[data-cy="next-step"]').click({ force: true }) } else { - cy.get('[data-cy="complete-and-continue"]').click({ force: true }); + cy.get('[data-cy="complete-and-continue"]').click({ force: true }) } } -}); +}) Cypress.Commands.add("learningContentMultiLayoutNextStep", () => { - return cy.get('[data-cy="next-step"]').click({ force: true }); -}); + return cy.get('[data-cy="next-step"]').click({ force: true }) +}) Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => { - return cy.get('[data-cy="previous-step"]').click({ force: true }); -}); + return cy.get('[data-cy="previous-step"]').click({ force: true }) +}) Cypress.Commands.add("testLearningContentTitle", (title) => { - return cy.get('[data-cy="lc-title"]').should("contain", title); -}); + return cy.get('[data-cy="lc-title"]').should("contain", title) +}) Cypress.Commands.add("testLearningContentSubtitle", (subtitle) => { - return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle); -}); + return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle) +}) diff --git a/package.json b/package.json index 7bb460e9..c2dd1054 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "cypress:open": "cypress open", "cypress:ci": "cypress-cloud run --parallel --record", + "cypress:install": "cypress install", "prettier": "npm run prettier --prefix client" }, "devDependencies": { diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py index abfae404..6ec9190e 100644 --- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py +++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py @@ -59,12 +59,32 @@ from vbv_lernwelt.shop.models import CheckoutInformation @click.option( "--create-assignment-completion/--no-create-assignment-completion", default=False, - help="will create assignment completion data for test-student1@example.com", + help="will create assignment completion data for test-student1@example.com by default. Other user can be specified with --assignment-completion-user", +) +@click.option( + "--assignment-completion-user-id", + default=TEST_STUDENT1_USER_ID, + help="user to create assignment completion for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.", +) +@click.option( + "--assignment-completion-course-session-id", + default=TEST_COURSE_SESSION_BERN_ID, + help="course session to create assignment completion in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.", ) @click.option( "--create-assignment-evaluation/--no-create-assignment-evaluation", default=False, - help="will create assignment evaluation data for test-student1@example.com", + help="will create assignment evaluation data for test-student1@example.com by default. Other user can be specified with --assignment-evaluation-user", +) +@click.option( + "--assignment-evaluation-user-id", + default=TEST_TRAINER1_USER_ID, + help="user to create assignment evaluation for. Defaults to test-trainer1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.", +) +@click.option( + "--assignment-evaluation-course-session-id", + default=TEST_COURSE_SESSION_BERN_ID, + help="course session to create assignment evaluation in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.", ) @click.option( "--assignment-evaluation-scores", @@ -81,7 +101,17 @@ from vbv_lernwelt.shop.models import CheckoutInformation type=(int, int, float), default=(None, None, 0.0), metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED", - help="Create edoniq result data for test-student1@example.com with user points and max points", + help="Create edoniq result data for test-student1@example.com by default with user points and max points. Use --edoniq-user-id to specify a different user.", +) +@click.option( + "--edoniq-user-id", + default=TEST_STUDENT1_USER_ID, + help="User to create edoniq test results for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-edoniq-test-results is set.", +) +@click.option( + "--edoniq-course-session-id", + default=TEST_COURSE_SESSION_BERN_ID, + help="course session to create edoniq test results in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-edoniq-test-results is set.", ) @click.option( "--create-feedback-responses/--no-create-feedback-responses", @@ -120,10 +150,16 @@ from vbv_lernwelt.shop.models import CheckoutInformation ) def command( create_assignment_completion, + assignment_completion_user_id, + assignment_completion_course_session_id, create_assignment_evaluation, + assignment_evaluation_user_id, + assignment_evaluation_course_session_id, assignment_evaluation_scores, assignment_points_deducted, create_edoniq_test_results, + edoniq_user_id, + edoniq_course_session_id, create_feedback_responses, create_course_completion_performance_criteria, create_attendance_days, @@ -170,15 +206,19 @@ def command( assignment=Assignment.objects.get( slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice" ), - course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID), - user=User.objects.get(id=TEST_STUDENT1_USER_ID), + course_session=CourseSession.objects.get( + id=assignment_completion_course_session_id + ), + user=User.objects.get(id=assignment_completion_user_id), ) create_test_assignment_submitted_data( assignment=Assignment.objects.get( slug="test-lehrgang-assignment-mein-kundenstamm" ), - course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID), - user=User.objects.get(id=TEST_STUDENT1_USER_ID), + course_session=CourseSession.objects.get( + id=assignment_completion_course_session_id + ), + user=User.objects.get(id=assignment_completion_user_id), ) if create_assignment_evaluation: if not assignment_evaluation_scores: @@ -195,9 +235,11 @@ def command( assignment=Assignment.objects.get( slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice" ), - course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID), - assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID), - evaluation_user=User.objects.get(id=TEST_TRAINER1_USER_ID), + course_session=CourseSession.objects.get( + id=assignment_evaluation_course_session_id + ), + assignment_user=User.objects.get(id=assignment_completion_user_id), + evaluation_user=User.objects.get(id=assignment_evaluation_user_id), input_scores=assignment_evaluation_scores, points_deducted=assignment_points_deducted, ) @@ -211,8 +253,8 @@ def command( assignment=Assignment.objects.get( slug="test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo" ), - course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID), - assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID), + course_session=CourseSession.objects.get(id=edoniq_course_session_id), + assignment_user=User.objects.get(id=edoniq_user_id), user_points=user_points, max_points=max_points, evaluation_points_deducted=points_deducted,