WIP: Adding base tempus tests

This commit is contained in:
Ivymaster
2026-05-21 13:12:09 +02:00
parent 57597dac29
commit dc05d16545
14 changed files with 2560 additions and 6 deletions
@@ -146,6 +146,7 @@ export default {
/>
<ul
v-if="contextMenu.show"
data-cy="eventContextMenu"
class="dropdown-menu show"
:style="{ position: 'fixed', top: contextMenu.y + 'px', left: contextMenu.x + 'px', zIndex: 9999 }"
>
@@ -159,4 +160,4 @@ export default {
</teleport>
</div>
`
}
}
+1
View File
@@ -247,6 +247,7 @@ export default {
<div
class="d-flex align-items-center gap-2 "
v-draggable:move.noimage="{ type: 'reservierung', id: null, orig: {} }"
data-cy="reservationDragHandle"
>
<i
class="fa-solid fa-calendar-plus text-primary"
+1 -1
View File
@@ -155,7 +155,7 @@ export default {
},
// language=HTML
template: `
<bs-modal ref="modal" class="bootstrap-prompt" dialogClass="modal-xl">
<bs-modal ref="modal" class="bootstrap-prompt" dialogClass="modal-xl" data-cy="reservationModal">
<template #title>Neue Reservierung</template>
<template #default>
<div class="row g-3">
+4 -4
View File
@@ -710,7 +710,7 @@ export default {
});
},
template: `
<div class="tempus">
<div data-cy="tempus" class="tempus">
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
<button
@@ -838,7 +838,7 @@ export default {
class="w-100"
/>
<div class="d-flex gap-1 py-1">
<div class="d-flex gap-1 py-1" data-cy="previewRoleOptionsHolder">
<button
class="btn btn-sm"
:class="previewRole === 'planer' ? 'btn-dark' : 'btn-outline-dark'"
@@ -934,7 +934,7 @@ export default {
</div>
</div>
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
<div id="verbandMenu" ref="verbandMenu" class="offcanvas offcanvas-start col-md p-md-0 h-100" tabindex="-1">
<div id="verbandMenu" ref="verbandMenu" class="offcanvas offcanvas-start col-md p-md-0 h-100" tabindex="-1" data-cy="verbandMenu">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<h5 class="offcanvas-title" id="verbandMenuLabel">
<i class="fa-solid fa-university me-2"></i>Verband
@@ -962,7 +962,7 @@ export default {
</template>
</bs-modal>
<bs-modal ref="historyModel" class="bootstrap-prompt" dialogClass="modal-lg">
<bs-modal ref="historyModel" class="bootstrap-prompt" dialogClass="modal-lg" data-cy="historyModal">
<template #title>History</template>
<template #default>
<table v-if="historyEntries.length" class="table table-bordered table-hover">
+4
View File
@@ -0,0 +1,4 @@
BASE_URL=https://demo.dev.technikum-wien.at/demoanon
USER_NAME=demoadmin
USER_PASSWORD=Demo
LOGIN_AS_USER=demoadmin
+4
View File
@@ -0,0 +1,4 @@
node_modules
.env
.env.*
!.env.example
+18
View File
@@ -0,0 +1,18 @@
const { defineConfig } = require("cypress");
require("dotenv").config();
module.exports = defineConfig({
allowCypressEnv: true,
e2e: {
baseUrl: process.env.BASE_URL || "http://localhost:8080",
defaultCommandTimeout: 40000,
pageLoadTimeout: 20000,
},
env: {
adminusername: process.env.USER_NAME || "2",
adminpassword: process.env.USER_PASSWORD || "2",
loginAsUser: process.env.LOGIN_AS_USER || process.env.USER_NAME || "demoadmin"
}
});
@@ -0,0 +1,148 @@
import { tempusPage } from "../../../support/pages/tempus.po";
context("Base tempus tests", () => {
beforeEach(() => {
cy.login();
cy.intercept({ method: "GET", url: "**/StgTree" }).as("fetchCourseTree");
cy.intercept({ method: "GET", url: /\/tempus\/Kalender\/getPlan(?:\?|$)/ }).as("fetchPlanData");
cy.intercept({ method: "GET", url: /\/tempus\/Kalender\/getPlanLecturer(?:\?|$)/ }).as("fetchLecturerPlanData");
cy.intercept({ method: "GET", url: /\/tempus\/Kalender\/getPlanStudent(?:\?|$)/ }).as("fetchStudentPlanData");
cy.intercept({ method: "GET", url: "**/tempus/Kalender/getHistory**" }).as("fetchEventHistory");
cy.intercept({ method: "GET", url: "**/tempus/coursepicker/getByStg**" }).as("fetchCoursePickerCourses");
tempusPage.visit();
cy.wait("@fetchCourseTree").its("response.statusCode").should("eq", 200);
cy.wait("@fetchPlanData").its("response.statusCode").should("eq", 200);
});
it("can access Tempus page with valid credentials", () => {
tempusPage.getTempusOverview().should("be.visible");
});
it ("shows all expected page elements", () => {
tempusPage.getTempusOverview().should("be.visible");
tempusPage.getSidebarMenu().should("exist");
tempusPage.getSlideInCoursesMenu().should("exist");
tempusPage.getCalendarSection().should("be.visible");
tempusPage.getPreviewRoleOptionsHolder().should("be.visible");
});
it("can open courses menu", () => {
tempusPage.getSlideInCoursesMenu().should("exist").should("be.hidden");
tempusPage.openCoursesMenu();
tempusPage.getCourseTreeRows().should("have.length.greaterThan", 0);
});
it("can select one course and show preview of its events", () => {
tempusPage.getSlideInCoursesMenu().should("exist");
tempusPage.getCourseTreeRows().should("have.length.greaterThan", 0);
tempusPage.getCoursePicker().should("exist");
tempusPage.getCoursePickerRows().should("have.length", 0);
tempusPage.selectFirstCourse();
cy.wait("@fetchCoursePickerCourses").its("response.statusCode").should("eq", 200);
tempusPage.getCoursePickerRows().should("have.length.greaterThan", 0);
});
it("can search for a course event in the course picker", () => {
const searchTerm = "PHY1 SO";
tempusPage.selectFirstCourse();
cy.wait("@fetchCoursePickerCourses").its("response.statusCode").should("eq", 200);
tempusPage.getCoursePickerRows().should("have.length.greaterThan", 0);
tempusPage.getCoursePickerSearchInput().type(searchTerm);
tempusPage.getCoursePickerRows().should("have.length.greaterThan", 0);
tempusPage.getCoursePickerRows().first().should("contain.text", searchTerm);
});
it("can drag and drop one course event into the calendar", () => {
tempusPage.getCalendarSection().should("exist");
tempusPage.selectFirstCourse();
cy.wait("@fetchCoursePickerCourses").its("response.statusCode").should("eq", 200);
tempusPage.getCoursePickerRows().first().drag(tempusPage.selectors.calendarSection);
tempusPage.getCalendarEvents().should("have.length", 1);
});
it("shows the same number of calendar events for all role previews", () => {
const rolePreviews = [
{ label: "Planer", fetchAlias: "@fetchPlanData" },
{ label: "Lektor", fetchAlias: "@fetchLecturerPlanData" },
{ label: "Student", fetchAlias: "@fetchStudentPlanData" },
];
const eventCounts = {};
cy.wrap(rolePreviews).each((rolePreview) => {
tempusPage.selectPreviewRole(rolePreview.label);
cy.wait(rolePreview.fetchAlias).its("response.statusCode").should("eq", 200);
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarEvents().then(($events) => {
eventCounts[rolePreview.label] = $events.length;
});
}).then(() => {
const expectedCount = eventCounts[rolePreviews[0].label];
Object.entries(eventCounts).forEach(([role, count]) => {
expect(count, `${role} calendar event count`).to.eq(expectedCount);
});
});
});
it("shows event details modal when clicking a calendar event", () => {
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarEvents().should("have.length.greaterThan", 0);
tempusPage.getCalendarEvents().first().click();
tempusPage.getCalendarEventModal().should("be.visible");
});
it("shows event context menu when right clicking a calendar event", () => {
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarEvents().should("have.length.greaterThan", 0);
tempusPage.getCalendarEvents().first().rightclick();
tempusPage.getEventContextMenu().should("be.visible");
});
it("shows history modal when selecting History from event context menu", () => {
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarEvents().should("have.length.greaterThan", 0);
tempusPage.getCalendarEvents().first().rightclick();
tempusPage.getEventContextMenuOption("History").click();
cy.wait("@fetchEventHistory").its("response.statusCode").should("eq", 200);
tempusPage.getHistoryModal().should("be.visible");
});
it("shows reservation modal when dropping reservation handle on calendar", () => {
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarSection().should("be.visible");
tempusPage.getReservationDragHandle().drag(tempusPage.selectors.calendarSection);
tempusPage.getReservationModal().should("be.visible");
});
it("can drop event from calendar into parking slot", () => {
tempusPage.getEventParkingSlot().should("exist");
tempusPage.getParkedEvents().should("have.length", 0);
tempusPage.getCalendarEvents().first().drag(tempusPage.selectors.parkingSlot);
tempusPage.getParkedEvents().should("have.length", 1);
});
});
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
+60
View File
@@ -0,0 +1,60 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
require("@4tw/cypress-drag-drop");
Cypress.Commands.add("login", () => {
const { adminusername, adminpassword, loginAsUser } = Cypress.env();
const userToImpersonate = loginAsUser || adminusername;
const auth = {
username: adminusername,
password: adminpassword,
};
cy.session(
["tempus-login", adminusername, userToImpersonate],
() => {
cy.request({
url: "/index.ci.php/tempus",
method: "POST",
auth,
body: {
searchstr: userToImpersonate,
types: ["employee"],
},
}).its("status").should("eq", 200);
},
{
cacheAcrossSpecs: true,
validate() {
cy.request({
url: "/index.ci.php/tempus",
auth,
}).its("status").should("eq", 200);
},
},
);
});
+17
View File
@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
@@ -0,0 +1,54 @@
class TempusPage {
selectors = {
calendarSection: "div[data-cy='tempus'] .fhc-calendar-base",
parkingSlot: "div[data-cy='tempus'] #parkingslot",
};
visit = () => cy.visit("/index.ci.php/tempus", {
onBeforeLoad(win) {
win.localStorage.removeItem("tempus_parking");
},
});
getTempusOverview = () => cy.get("div[data-cy='tempus']");
getSlideInCoursesMenu = () => cy.get("div[data-cy='verbandMenu']");
getSidebarMenu = () => this.getTempusOverview().find("#sidebarMenu");
getCalendarSection = () => this.getTempusOverview().find(".fhc-calendar-base");
getCoursePicker = () => this.getTempusOverview().find(".course-picker");
getPreviewRoleOptionsHolder = () => this.getTempusOverview().find("[data-cy='previewRoleOptionsHolder']");
getAllCoursesSliderBtn = () => this.getSidebarMenu().find("button[title='Verband']").first();
getCourseTreeRows = () => this.getSlideInCoursesMenu().find(".p-treetable-tbody tr");
getCoursePickerRows = () => this.getCoursePicker().find(".course-picker-row");
getCoursePickerSearchInput = () => this.getCoursePicker().find("input");
getCalendarEvents = () => this.getCalendarSection().find(".fhc-calendar-base-grid-line-event");
getCalendarLoadingEvents = () => this.getCalendarSection().find(".placeholder-glow");
getCalendarEventModal = () => this.getCalendarSection().find(".bootstrap-modal.show");
getEventContextMenu = () => cy.get("[data-cy='eventContextMenu']");
getEventContextMenuOption = (option) => this.getEventContextMenu().contains("button", option);
getHistoryModal = () => cy.get("[data-cy='historyModal']");
getReservationDragHandle = () => cy.get("[data-cy='reservationDragHandle']");
getReservationModal = () => cy.get("[data-cy='reservationModal']");
getEventParkingSlot = () => this.getSidebarMenu().find("#parkingslot");
getParkedEvents = () => this.getEventParkingSlot().find(".fhc-calendar-base-grid-line-event");
getPreviewRoleButton = (role) => this.getPreviewRoleOptionsHolder().contains("button", role);
openCoursesMenu = () => {
this.getAllCoursesSliderBtn().click();
this.getSlideInCoursesMenu().should("be.visible");
};
selectFirstCourse = () => {
this.openCoursesMenu();
this.getCourseTreeRows().first().click();
};
selectPreviewRole = (role) => {
this.getPreviewRoleButton(role).click();
};
waitForCalendarToFinishLoading = () => {
this.getCalendarLoadingEvents().should("not.exist");
};
}
export const tempusPage = new TempusPage();
+2233
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
{
"devDependencies": {
"@4tw/cypress-drag-drop": "^2.3.1",
"cypress": "^15.11.0"
},
"dependencies": {
"dotenv": "^17.4.2"
}
}