From bfd8ce9eff8f4fb88ed38156d0e8bded59e3ef63 Mon Sep 17 00:00:00 2001 From: Jake Deery Date: Mon, 8 Dec 2025 16:22:40 +0000 Subject: [PATCH] Bug 14962: On Display Cypress e2e tests This patch contains the front-end tests for the On Display module. If you'd like to verify the Perl methods, see the perl test harness patch. You can also use the test plan below to test the entire patchset works as expected. TO TEST (AUTOMATED E2E): APPLY PATCHSET a) cd $PROJECTS_DIR/koha b) DB_HOSTNAME=localhost npx yarn cypress open *) you may need to run `corepack enable` if you get "command not found" c) click E2E testing d) click start E2E testing in Electron e) under Display, click Main.ts *) verify it runs through fully with green f) return to last page, under Display, click Displays_spec.ts *) verify it runs through fully with green SIGN OFF TO TEST (MANUALLY): APPLY PATCHSET a) go to /cgi-bin/koha/display/display-home.pl *) notice module is disabled b) go to /cgi-bin/koha/admin/preferences.pl?tab=&op=search&searchfield=UseDisplayModule *) set UseDisplayModule syspref to "Use" c) repeat step a *) notice module is now enabled d) click "New display" e) Sponsored-by: ByWater Solutions Signed-of-by: Martin Renvoize --- .../integration/Display/Displays_spec.ts | 453 ++++++++++++++++++ t/cypress/integration/Display/Main.ts | 39 ++ t/cypress/support/e2e.js | 134 ++++++ 3 files changed, 626 insertions(+) create mode 100644 t/cypress/integration/Display/Displays_spec.ts create mode 100644 t/cypress/integration/Display/Main.ts diff --git a/t/cypress/integration/Display/Displays_spec.ts b/t/cypress/integration/Display/Displays_spec.ts new file mode 100644 index 00000000000..3bb0261170e --- /dev/null +++ b/t/cypress/integration/Display/Displays_spec.ts @@ -0,0 +1,453 @@ +const dayjs = require("dayjs"); /* Cannot use our calendar JS code, it's in an include file (!) + Also note that moment.js is deprecated */ + +const dates = { + today_iso: dayjs().format("YYYY-MM-DD"), + today_us: dayjs().format("MM/DD/YYYY"), + tomorrow_iso: dayjs().add(1, "day").format("YYYY-MM-DD"), + tomorrow_us: dayjs().add(1, "day").format("MM/DD/YYYY"), +}; + +describe("Displays - CRUD operations", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/api/v1/displays/config", + '{"settings":{"enabled":"1"}}' + ); + }); + + it("Add display", () => { + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, []); + cy.visit("/cgi-bin/koha/display/display-home.pl"); + + let display = cy.getDisplay(); + let displays = [display]; + + cy.contains("New display").click(); + cy.get("#display_name").type(display.display_name); + cy.get("#display_branch .vs__search").type("centerville" + "{enter}", { + force: true, + }); + cy.get("#display_holding_branch .vs__search").type( + "centerville" + "{enter}", + { + force: true, + } + ); + cy.get("#display_location .vs__search").type( + "general stacks" + "{enter}", + { + force: true, + } + ); + cy.get("#display_code .vs__search").type("reference" + "{enter}", { + force: true, + }); + cy.get("#display_itype .vs__search").type("books" + "{enter}", { + force: true, + }); + cy.get("#display_return_over .vs__search").type( + "yes, any library" + "{enter}", + { + force: true, + } + ); + cy.get("#start_date") + .invoke("attr", "value", display.start_date) + .should("have.attr", "value", display.start_date); + cy.get("#end_date") + .invoke("attr", "value", display.start_date) + .should("have.attr", "value", display.start_date); + cy.get("#display_days").type(display.display_days); + cy.get("#staff_note").type(display.staff_note); + cy.get("#public_note").type(display.public_note); + cy.get("#enabled_yes").click(); + + // Submit the form, get 500 + cy.intercept("POST", "/api/v1/displays", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#displays_add").contains("Save").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("POST", "/api/v1/displays", { + statusCode: 201, + body: display, + }); + cy.get("#displays_add").contains("Save").click(); + cy.get("main div[class='alert alert-info']").contains( + "Display created" + ); + }); + + it("List displays", () => { + // GET displays returns 500 + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 500, + }); + cy.visit("/cgi-bin/koha/display/display-home.pl"); + cy.get(".sidebar_menu a").contains("Displays").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // GET displays returns empty list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, []); + cy.visit("/cgi-bin/koha/display/displays"); + cy.get("#displays_list").contains("There are no displays defined"); + + // GET displays returns something + let display = cy.getDisplay(); + let displays = [display]; + + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }); + cy.visit("/cgi-bin/koha/display/displays"); + cy.get("#displays_list").contains("Showing 1 to 1 of 1 entries"); + }); + + it("Show display", () => { + let display = cy.getDisplay(); + let displays = [display]; + let item = cy.getItem(); + + // Click the "name" link from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }).as("get-displays"); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display).as( + "get-display" + ); + cy.intercept("GET", /\/api\/v1\/items\/\d+/, { + statusCode: 200, + body: item, + }).as("get-item"); + + cy.visit("/cgi-bin/koha/display/displays"); + cy.wait("@get-displays"); + let id_cell = cy.get("#displays_list table tbody tr:first td:first"); + id_cell.contains(display.display_id); + + let name_link = cy + .get("#displays_list table tbody tr:first td") + .eq(1) + .find("a"); + name_link.should("have.text", display.display_name); + name_link.click(); + cy.wait("@get-item"); + cy.get("#displays_show h2").contains("Display #" + display.display_id); + cy.left_menu_active_item_is("Displays"); + }); + + it("Edit display", () => { + let display = cy.getDisplay(); + let displays = [display]; + let item = cy.getItem(); + + // Click the 'Edit' button from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }).as("get-displays"); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display).as( + "get-display" + ); + cy.intercept("GET", /\/api\/v1\/items\/\d+/, { + statusCode: 200, + body: item, + }).as("get-item"); + + cy.visit("/cgi-bin/koha/display/displays"); + cy.wait("@get-displays"); + cy.get("#displays_list table tbody tr:first").contains("Edit").click(); + cy.wait("@get-item"); + cy.get("#displays_add h2").contains("Edit display"); + cy.left_menu_active_item_is("Displays"); + + // Form has been correctly filled in + cy.get("#display_name").should("have.value", display.display_name); + + cy.get("#display_holding_branch .vs__selected").contains("Centerville"); + cy.get("#display_code .vs__selected").contains("Reference"); + cy.get("#start_date").invoke("val").should("eq", "2025-01-01"); + cy.get("#end_date").invoke("val").should("eq", "2036-12-31"); + cy.get("#public_note").invoke("val").should("eq", "A public note"); + + // Test related item + cy.get("#display_items_barcode_0") + .invoke("val") + .should("eq", item.external_id); + + // Submit the form, get 500 + cy.intercept("PUT", "/api/v1/displays/*", { + statusCode: 500, + }); + cy.get("#displays_add").contains("Save").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Submit the form, success! + cy.intercept("PUT", "/api/v1/displays/*", { + statusCode: 200, + body: display, + }); + cy.get("#displays_add").contains("Save").click(); + cy.get("main div[class='alert alert-info']").contains( + "Display updated" + ); + }); + + it("Delete display", () => { + let display = cy.getDisplay(); + let displays = [display]; + let item = cy.getItem(); + + // Click the 'Delete' button from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display); + cy.visit("/cgi-bin/koha/display/displays"); + + cy.get("#displays_list table tbody tr:first") + .contains("Delete") + .click(); + cy.get(".alert-warning.confirmation h1").contains( + "remove this display" + ); + cy.contains(display.display_name); + + // Accept the confirmation dialog, get 500 + cy.intercept("DELETE", "/api/v1/displays/*", { + statusCode: 500, + }); + cy.contains("Yes, delete").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Something went wrong: Error: Internal Server Error" + ); + + // Accept the confirmation dialog, success! + cy.intercept("DELETE", "/api/v1/displays/*", { + statusCode: 204, + body: null, + }); + cy.get("#displays_list table tbody tr:first") + .contains("Delete") + .click(); + cy.get(".alert-warning.confirmation h1").contains( + "remove this display" + ); + cy.contains("Yes, delete").click(); + cy.get("main div[class='alert alert-info']") + .contains("Display") + .contains("deleted"); + + // Delete from show + // Click the "name" link from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }).as("get-displays"); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display).as( + "get-display" + ); + cy.intercept("GET", /\/api\/v1\/items\/\d+/, { + statusCode: 200, + body: item, + }).as("get-item"); + + cy.visit("/cgi-bin/koha/display/displays"); + cy.wait("@get-displays"); + let id_cell = cy.get("#displays_list table tbody tr:first td:first"); + id_cell.contains(display.display_id); + + let name_link = cy + .get("#displays_list table tbody tr:first td") + .eq(1) + .find("a"); + name_link.should("have.text", display.display_name); + name_link.click(); + cy.wait("@get-display"); + cy.get("#displays_show h2").contains("Display #" + display.display_id); + + cy.get("#displays_show #toolbar").contains("Delete").click(); + cy.get(".alert-warning.confirmation h1").contains( + "remove this display" + ); + cy.contains("Yes, delete").click(); + + //Make sure we return to list after deleting from show + cy.get("#displays_list table tbody tr:first"); + }); + + it("Batch add items", () => { + let display = cy.getDisplay(); + let displays = [display]; + let batch = { + job_id: 1, + message: "Batch add operation queued", + }; + let batches = [batch]; + let item = cy.getItem(); + + // Click the "batch add" link from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }).as("get-displays"); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display).as( + "get-display" + ); + cy.intercept("GET", /\/api\/v1\/items\/\d+/, { + statusCode: 200, + body: item, + }).as("get-item"); + + cy.visit("/cgi-bin/koha/display/display-home.pl"); + cy.contains("Batch add items from list").click(); + + cy.get("#barcodes").type("39999000002355\n39999000002331\n\n"); + cy.get("#display_id .vs__search").type( + display.display_name + "{enter}", + { + force: true, + } + ); + cy.get("#date_remove") + .invoke("attr", "value", display.end_date) + .should("have.attr", "value", display.end_date); + + // Submit the form, get 500 + cy.intercept("POST", "/api/v1/display/items/batch", { + statusCode: 500, + error: "Something went wrong", + }); + cy.get("#list").contains("Save").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Internal Server Error. Please check the browser console for diagnostic information." + ); + + cy.get("#barcodes").type("39999000002355\n39999000002331\n\n"); + cy.get("#display_id .vs__search").type( + display.display_name + "{enter}", + { + force: true, + } + ); + cy.get("#date_remove") + .invoke("attr", "value", display.end_date) + .should("have.attr", "value", display.end_date); + + // Submit the form, success! + cy.intercept("POST", "/api/v1/display/items/batch", { + statusCode: 202, + body: batch, + }); + cy.get("#list").contains("Save").click(); + cy.get("main div[class='alert alert-info']").contains( + "Batch job successfully queued" + ); + }); + + it("Batch delete items", () => { + let display = cy.getDisplay(); + let displays = [display]; + let batch = { + job_id: 2, + message: "Batch add operation queued", + }; + let batches = [batch]; + let item = cy.getItem(); + + // Click the "batch add" link from the list + cy.intercept("GET", /\/api\/v1\/displays.(?!config).*/, { + statusCode: 200, + body: displays, + headers: { + "X-Base-Total-Count": "1", + "X-Total-Count": "1", + }, + }).as("get-displays"); + cy.intercept("GET", /\/api\/v1\/displays\/\d+/, display).as( + "get-display" + ); + cy.intercept("GET", /\/api\/v1\/items\/\d+/, { + statusCode: 200, + body: item, + }).as("get-item"); + + cy.visit("/cgi-bin/koha/display/display-home.pl"); + cy.contains("Batch remove items from list").click(); + + cy.get("#barcodes").type("39999000002355\n39999000002331\n\n"); + cy.get("#display_id .vs__search").type( + display.display_name + "{enter}", + { + force: true, + } + ); + + // Submit the form, get 500 + cy.intercept("DELETE", "/api/v1/display/items/batch", { + statusCode: 500, + }); + cy.get("#list").contains("Save").click(); + cy.get("main div[class='alert alert-warning']").contains( + "Internal Server Error. Please check the browser console for diagnostic information." + ); + + cy.get("#barcodes").type("39999000002355\n39999000002331\n\n"); + cy.get("#display_id .vs__search").type( + display.display_name + "{enter}", + { + force: true, + } + ); + + // Submit the form, success! + cy.intercept("DELETE", "/api/v1/display/items/batch", { + statusCode: 202, + body: batch, + }); + cy.get("#list").contains("Save").click(); + cy.get("main div[class='alert alert-info']").contains( + "Batch job successfully queued" + ); + }); +}); diff --git a/t/cypress/integration/Display/Main.ts b/t/cypress/integration/Display/Main.ts new file mode 100644 index 00000000000..ef30b7295b0 --- /dev/null +++ b/t/cypress/integration/Display/Main.ts @@ -0,0 +1,39 @@ +describe("Main component - pref off", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/api/v1/displays/config", + '{"settings":{"enabled":"0"}}' + ); + }); + + it("Home", () => { + cy.visit("/cgi-bin/koha/display/display-home.pl"); + cy.get(".main .sidebar_menu").should("not.exist"); + cy.get(".main div[class='alert alert-warning']").contains( + "The displays module is disabled, turn on UseDisplayModule to use it" + ); + cy.get(".main div[class='alert alert-warning'] a").click(); + cy.url().should("match", /\/cgi-bin\/koha\/admin\/preferences.pl/); + }); +}); + +describe("Main component - pref on", () => { + beforeEach(() => { + cy.login(); + cy.title().should("eq", "Koha staff interface"); + cy.intercept( + "GET", + "/api/v1/displays/config", + '{"settings":{"enabled":"1"}}' + ); + }); + + it("Home", () => { + cy.visit("/cgi-bin/koha/display/display-home.pl"); + cy.get(".main .sidebar_menu").should("exist"); + cy.get(".main div[class='alert alert-warning']").should("not.exist"); + }); +}); diff --git a/t/cypress/support/e2e.js b/t/cypress/support/e2e.js index 287616198fb..b180d49c151 100644 --- a/t/cypress/support/e2e.js +++ b/t/cypress/support/e2e.js @@ -1963,6 +1963,64 @@ cy.getSIP2Institution = () => { }; }; +cy.getItem = () => { + return { + acquisition_date: "2014-09-04", + acquisition_source: null, + biblio_id: 58, + bookable: false, + call_number_sort: "_", + call_number_source: null, + callnumber: null, + checked_out_date: null, + checkouts_count: null, + coded_location_qualifier: null, + collection_code: "REF", + copy_number: null, + damaged_date: null, + damaged_status: 0, + effective_bookable: false, + effective_collection_code: "REF", + effective_holding_library_id: "CPL", + effective_home_library_id: "CPL", + effective_item_type_id: "BK", + effective_location: "GEN", + effective_not_for_loan_status: 0, + exclude_from_local_holds_priority: false, + extended_subfields: null, + external_id: "39999000002355", + holding_library_id: "CPL", + holds_count: null, + home_library_id: "CPL", + internal_notes: null, + inventory_number: null, + item_id: 125, + item_type_id: "BK", + last_checkout_date: null, + last_seen_date: "2014-09-04T00:00:00+00:00", + localuse: null, + location: "GEN", + lost_date: null, + lost_status: 0, + materials_notes: null, + new_status: null, + not_for_loan_status: 0, + permanent_location: "GEN", + public_notes: null, + purchase_price: null, + renewals_count: null, + replacement_price: null, + replacement_price_date: "2014-09-04", + restricted_status: null, + serial_issue_number: null, + shelving_control_number: null, + timestamp: "2020-01-29T13:06:19+00:00", + uri: null, + withdrawn: 0, + withdrawn_date: null, + }; +}; + cy.getItemTypes = () => { return [ { @@ -2144,6 +2202,82 @@ cy.getItemTypes = () => { ]; }; +cy.getDisplay = () => { + return { + display_branch: "CPL", + display_code: "REF", + display_days: 4382, + display_holding_branch: "CPL", + display_id: 1, + display_items: [ + { + biblionumber: 58, + date_added: null, + date_remove: "2036-12-31", + display_id: 1, + display_item_id: 1, + itemnumber: 125, + }, + ], + display_itype: "BK", + display_location: "GEN", + display_name: "Test display #1", + display_return_over: "yes - any library", + enabled: true, + end_date: "2036-12-31", + item_type: { + automatic_checkin: false, + bookable: false, + checkin_message: null, + checkin_message_type: "message", + checkprevcheckout: "inherit", + daily_rental_charge: null, + daily_rental_charge_calendar: true, + default_replacement_cost: null, + description: "Books", + hide_in_opac: false, + hourly_rental_charge: null, + hourly_rental_charge_calendar: true, + image_url: "bridge/book.png", + item_type_id: "BK", + not_for_loan_status: false, + parent_type: null, + process_fee: null, + rentalcharge: 0.0, + searchcategory: null, + sip_media_type: null, + summary: "", + }, + library: { + address1: "Jefferson Summit", + address2: null, + address3: null, + city: null, + country: null, + email: null, + fax: null, + geolocation: null, + ill_email: null, + ip: null, + library_id: "CPL", + marc_org_code: null, + name: "Centerville", + notes: null, + phone: null, + pickup_location: true, + postal_code: null, + public: true, + reply_to_email: null, + return_path_email: null, + state: null, + url: null, + }, + public_note: "A public note", + staff_note: "A staff-only note", + start_date: "2025-01-01", + }; +}; + Cypress.Commands.add("set_syspref", (variable, value) => { cy.window().then(win => { const client = win.APIClient.sysprefs; -- 2.53.0