Bugzilla – Attachment 193905 Details for
Bug 10190
Overdue notice triggers based on item type
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 10190: fix failing Permissions.t and overhaul CirculationTriggers Cypress spec
6286ac7.patch (text/plain), 45.99 KB, created by
Martin Renvoize (ashimema)
on 2026-02-25 15:33:31 UTC
(
hide
)
Description:
Bug 10190: fix failing Permissions.t and overhaul CirculationTriggers Cypress spec
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-02-25 15:33:31 UTC
Size:
45.99 KB
patch
obsolete
>From 6286ac738ff496df97ad2e6674c47c62f40845ce Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Wed, 25 Feb 2026 14:45:24 +0000 >Subject: [PATCH] Bug 10190: fix failing Permissions.t and overhaul > CirculationTriggers Cypress spec > >Permissions.t: >- Remove edit_notice_status_triggers from userpermissions.sql so fresh installs > no longer seed this deprecated permission (existing installs are handled by > the atomic update) >- Add a defensive DELETE in the test to cover DBs that pre-date the atomic update > >CirculationTriggers_spec.ts: >- Fix getMockCirculationRule: the helper was returning the old {rule_name, > rule_value, branchcode, ...} format; update to the current API format > {context: {...}, overdue_N_delay: ..., ...} >- Remove assertions for UI elements that do not exist (success/error flash > messages, "Existing triggers" heading, "rule set will be removed" text) >- Fix reset confirmation text to match actual component output >- Fix duplicate patron_category_id "ST" in mock patron categories >- Remove aspirational tests that checked non-implemented behaviour >- Add describe block for the no-default-triggers warning (no rules anywhere, > rules only at specific libraries, library link switches the filter) >- Add regression test for NaN trigger number when adding first rule for a library >--- > .../data/mysql/mandatory/userpermissions.sql | 1 - > t/Koha/Auth/Permissions.t | 6 + > .../Admin/CirculationTriggers_spec.ts | 871 +++++------------- > 3 files changed, 224 insertions(+), 654 deletions(-) > >diff --git a/installer/data/mysql/mandatory/userpermissions.sql b/installer/data/mysql/mandatory/userpermissions.sql >index 10b8607c47d..d10cd4cbea0 100644 >--- a/installer/data/mysql/mandatory/userpermissions.sql >+++ b/installer/data/mysql/mandatory/userpermissions.sql >@@ -107,7 +107,6 @@ INSERT INTO permissions (module_bit, code, description) VALUES > (13, 'edit_calendar', 'Define days when the library is closed'), > (13, 'moderate_comments', 'Moderate patron comments'), > (13, 'edit_notices', 'Define notices'), >- (13, 'edit_notice_status_triggers', 'Set notice/status triggers for overdue items'), > (13, 'edit_quotes', 'Edit quotes for quote-of-the-day feature'), > (13, 'view_system_logs', 'Browse the system logs'), > (13, 'inventory', 'Perform inventory (stocktaking) of your catalog'), >diff --git a/t/Koha/Auth/Permissions.t b/t/Koha/Auth/Permissions.t >index 5b740193d95..6619625d392 100755 >--- a/t/Koha/Auth/Permissions.t >+++ b/t/Koha/Auth/Permissions.t >@@ -20,8 +20,14 @@ use Modern::Perl; > use Test::NoWarnings; > use Test::More tests => 4; > >+use C4::Context; >+ > use_ok('Koha::Auth::Permissions'); > >+# Remove the deprecated edit_notice_status_triggers permission (bug 10190). >+# It may still be present in DBs that pre-date the atomic update. >+C4::Context->dbh->do(q|DELETE FROM permissions WHERE code = 'edit_notice_status_triggers'|); >+ > subtest 'normal staff user test' => sub { > > plan tests => 1; >diff --git a/t/cypress/integration/Admin/CirculationTriggers_spec.ts b/t/cypress/integration/Admin/CirculationTriggers_spec.ts >index 77e30485edb..ae78f7f89c8 100644 >--- a/t/cypress/integration/Admin/CirculationTriggers_spec.ts >+++ b/t/cypress/integration/Admin/CirculationTriggers_spec.ts >@@ -8,20 +8,21 @@ const dates = { > tomorrow_us: dayjs().add(1, "day").format("MM/DD/YYYY"), > }; > >-// Helper function to generate mock circulation rule >-const getMockCirculationRule = ( >+// Generates a rule set in the format the API actually returns: >+// { context: { library_id, patron_category_id, item_type_id }, overdue_N_delay, ... } >+const getMockRuleSet = ( > library_id: string = "*", > patron_category_id: string = "*", > item_type_id: string = "*", >- triggerNum: number = 1 >+ triggerNum: number = 1, >+ delay: string = "7" > ) => { > return { >- id: Math.floor(Math.random() * 1000), >- rule_name: `overdue_${triggerNum}_delay`, >- rule_value: "7", >- branchcode: library_id === "*" ? null : library_id, >- categorycode: patron_category_id === "*" ? null : patron_category_id, >- itemtype: item_type_id === "*" ? null : item_type_id, >+ context: { library_id, patron_category_id, item_type_id }, >+ [`overdue_${triggerNum}_delay`]: delay, >+ [`overdue_${triggerNum}_notice`]: null, >+ [`overdue_${triggerNum}_restrict`]: null, >+ [`overdue_${triggerNum}_mtt`]: null, > }; > }; > >@@ -61,7 +62,7 @@ const getMockPatronCategories = () => { > description: "Student patron", > }, > { >- patron_category_id: "ST", >+ patron_category_id: "SF", > name: "Staff", > description: "Staff member", > }, >@@ -127,13 +128,11 @@ describe("Circulation Triggers - Breadcrumbs", () => { > cy.get("#breadcrumbs > ol > li:nth-child(3)").contains( > "Circulation triggers" > ); >- cy.get(".current").contains("Home"); > }); > > it("Should have breadcrumb link from add form", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.contains("Add new trigger").click(); >- cy.get(".current").contains("Add new trigger"); > cy.get("#breadcrumbs") > .contains("Circulation triggers") > .should("have.attr", "href") >@@ -146,7 +145,6 @@ describe("Circulation Triggers - Initial Load", () => { > cy.login(); > cy.title().should("eq", "Koha staff interface"); > >- // Intercept API calls for initial data > cy.intercept("GET", "/api/v1/libraries*", { > statusCode: 200, > body: getMockLibraries(), >@@ -165,9 +163,9 @@ describe("Circulation Triggers - Initial Load", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("*", "*", "*", 1), >- getMockCirculationRule("*", "*", "*", 2), >- getMockCirculationRule("CPL", "PT", "BK", 1), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("*", "*", "*", 2), >+ getMockRuleSet("CPL", "PT", "BK", 1), > ], > headers: { > "X-Base-Total-Count": "3", >@@ -179,29 +177,21 @@ describe("Circulation Triggers - Initial Load", () => { > it("Should successfully load the component and display initial elements", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > >- // Check main heading > cy.get("h1").should("contain", "Circulation triggers"); >- >- // Check for the rules precedence information > cy.get(".page-section").should( > "contain", > "Rules are applied from most specific to less specific" > ); >- >- // Check that filters are displayed > cy.get("#library_select").should("exist"); > cy.get("#patron_category_select").should("exist"); > cy.get("#item_type_select").should("exist"); >- >- // Check toolbar button exists > cy.contains("Add new trigger").should("exist"); > }); > >- it("Should display trigger tabs", () => { >+ it("Should display trigger tabs when rules exist", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Check that tabs are displayed > cy.get("#circ_triggers_tabs").should("exist"); > cy.get(".nav-link").contains("Trigger 1").should("exist"); > cy.get(".nav-link").contains("Trigger 2").should("exist"); >@@ -215,8 +205,140 @@ describe("Circulation Triggers - Initial Load", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules-error"); > >- // Component should handle error gracefully >- // (specific error handling depends on implementation) >+ // Component should still render without crashing >+ cy.get("h1").should("contain", "Circulation triggers"); >+ }); >+}); >+ >+describe("Circulation Triggers - No default rules warning", () => { >+ beforeEach(() => { >+ cy.login(); >+ cy.title().should("eq", "Koha staff interface"); >+ >+ cy.intercept("GET", "/api/v1/libraries*", { >+ statusCode: 200, >+ body: getMockLibraries(), >+ }).as("get-libraries"); >+ >+ cy.intercept("GET", "/api/v1/patron_categories*", { >+ statusCode: 200, >+ body: getMockPatronCategories(), >+ }).as("get-patron-categories"); >+ >+ cy.intercept("GET", "/api/v1/item_types*", { >+ statusCode: 200, >+ body: getMockItemTypes(), >+ }).as("get-item-types"); >+ }); >+ >+ it("Should show warning when no default rules exist but library-specific rules do", () => { >+ // First intercept: default-library fetch returns empty >+ // Second intercept: all-rules fetch (for getLibrariesWithRules) returns CPL rules >+ cy.intercept("GET", "/api/v1/circulation_rules*", req => { >+ const url = new URL(req.url); >+ if (url.searchParams.get("library_id") === "*") { >+ req.reply({ >+ statusCode: 200, >+ body: [], >+ headers: { >+ "X-Base-Total-Count": "0", >+ "X-Total-Count": "0", >+ }, >+ }); >+ } else { >+ req.reply({ >+ statusCode: 200, >+ body: [getMockRuleSet("CPL", "*", "*", 1)], >+ headers: { >+ "X-Base-Total-Count": "1", >+ "X-Total-Count": "1", >+ }, >+ }); >+ } >+ }).as("get-rules"); >+ >+ cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >+ cy.wait("@get-rules"); >+ >+ cy.get(".alert-warning").should( >+ "contain", >+ "No default overdue triggers are defined" >+ ); >+ cy.get(".alert-warning").should( >+ "contain", >+ "The following libraries have library-specific triggers defined" >+ ); >+ cy.get(".alert-warning").contains("Centerville").should("exist"); >+ }); >+ >+ it("Should show get-started message when no rules exist anywhere", () => { >+ cy.intercept("GET", "/api/v1/circulation_rules*", { >+ statusCode: 200, >+ body: [], >+ headers: { "X-Base-Total-Count": "0", "X-Total-Count": "0" }, >+ }).as("get-rules"); >+ >+ cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >+ cy.wait("@get-rules"); >+ >+ cy.get(".alert-warning").should( >+ "contain", >+ "No default overdue triggers are defined" >+ ); >+ cy.get(".alert-warning").should( >+ "contain", >+ "Select add new trigger above to get started" >+ ); >+ }); >+ >+ it("Should not show warning when default rules exist", () => { >+ cy.intercept("GET", "/api/v1/circulation_rules*", { >+ statusCode: 200, >+ body: [getMockRuleSet("*", "*", "*", 1)], >+ headers: { "X-Base-Total-Count": "1", "X-Total-Count": "1" }, >+ }).as("get-rules"); >+ >+ cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >+ cy.wait("@get-rules"); >+ >+ cy.get(".alert-warning").should("not.exist"); >+ }); >+ >+ it("Should switch to library view when clicking a library link in the warning", () => { >+ cy.intercept("GET", "/api/v1/circulation_rules*", req => { >+ const url = new URL(req.url); >+ if (url.searchParams.get("library_id") === "*") { >+ req.reply({ >+ statusCode: 200, >+ body: [], >+ headers: { >+ "X-Base-Total-Count": "0", >+ "X-Total-Count": "0", >+ }, >+ }); >+ } else { >+ req.reply({ >+ statusCode: 200, >+ body: [getMockRuleSet("CPL", "*", "*", 1)], >+ headers: { >+ "X-Base-Total-Count": "1", >+ "X-Total-Count": "1", >+ }, >+ }); >+ } >+ }).as("get-rules"); >+ >+ cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >+ cy.wait("@get-rules"); >+ >+ cy.get(".alert-warning").contains("Centerville").click(); >+ >+ // The library filter should now show Centerville and the warning should be gone >+ cy.get("#library_select .vs__selected").should( >+ "contain", >+ "Centerville" >+ ); >+ cy.get(".alert-warning").should("not.exist"); > }); > }); > >@@ -243,11 +365,11 @@ describe("Circulation Triggers - Filtering", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("*", "*", "*", 1), >- getMockCirculationRule("CPL", "*", "*", 1), >- getMockCirculationRule("CPL", "PT", "*", 1), >- getMockCirculationRule("CPL", "PT", "BK", 1), >- getMockCirculationRule("MPL", "ST", "DVD", 1), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("CPL", "*", "*", 1), >+ getMockRuleSet("CPL", "PT", "*", 1), >+ getMockRuleSet("CPL", "PT", "BK", 1), >+ getMockRuleSet("MPL", "ST", "DVD", 1), > ], > headers: { > "X-Base-Total-Count": "5", >@@ -260,77 +382,48 @@ describe("Circulation Triggers - Filtering", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Select a specific library > cy.get("#library_select .vs__search").type("Centerville{enter}", { > force: true, > }); >- cy.get("#library_select .vs__selected").contains("Centerville"); >- >- // Rules should be filtered to show only Centerville rules >+ cy.get("#library_select .vs__selected").should( >+ "contain", >+ "Centerville" >+ ); > }); > > it("Should filter by patron category", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Select a specific patron category > cy.get("#patron_category_select .vs__search").type("Patron{enter}", { > force: true, > }); >- cy.get("#patron_category_select .vs__selected").contains("Patron"); >- >- // Rules should be filtered >+ cy.get("#patron_category_select .vs__selected").should( >+ "contain", >+ "Patron" >+ ); > }); > > it("Should filter by item type", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Select a specific item type > cy.get("#item_type_select .vs__search").type("Books{enter}", { > force: true, > }); >- cy.get("#item_type_select .vs__selected").contains("Books"); >- >- // Rules should be filtered >- }); >- >- it("Should filter by multiple criteria", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- // Select library >- cy.get("#library_select .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- >- // Select patron category >- cy.get("#patron_category_select .vs__search").type("Patron{enter}", { >- force: true, >- }); >- >- // Select item type >- cy.get("#item_type_select .vs__search").type("Books{enter}", { >- force: true, >- }); >- >- // Should show only rules matching all three criteria >+ cy.get("#item_type_select .vs__selected").should("contain", "Books"); > }); > > it("Should toggle between explicit and all applicable rules", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Initially should show "all applied rules" > cy.get("#filter-rules").should("exist"); > >- // Change to "explicitly set rules" > cy.get("#filter-rules .vs__search").click(); > cy.get("#filter-rules .vs__dropdown-menu") > .contains("explicitly set rules") > .click({ force: true }); >- >- // Should show fewer rules (only explicitly set ones) > }); > }); > >@@ -347,9 +440,9 @@ describe("Circulation Triggers - Tab Navigation", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("*", "*", "*", 1), >- getMockCirculationRule("*", "*", "*", 2), >- getMockCirculationRule("*", "*", "*", 3), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("*", "*", "*", 2), >+ getMockRuleSet("*", "*", "*", 3), > ], > headers: { > "X-Base-Total-Count": "3", >@@ -362,21 +455,17 @@ describe("Circulation Triggers - Tab Navigation", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Check Trigger 1 is initially active > cy.get(".nav-link") > .contains("Trigger 1") > .should("have.class", "active"); > >- // Click on Trigger 2 > cy.get(".nav-link").contains("Trigger 2").click(); > cy.get(".nav-link") > .contains("Trigger 2") > .should("have.class", "active"); > >- // Check that Trigger 2 content is displayed > cy.get(".tab-pane.active").should("exist"); > >- // Click on Trigger 3 > cy.get(".nav-link").contains("Trigger 3").click(); > cy.get(".nav-link") > .contains("Trigger 3") >@@ -406,7 +495,7 @@ describe("Circulation Triggers - Add New Trigger", () => { > > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, >- body: [getMockCirculationRule("CPL", "PT", "BK", 1)], >+ body: [getMockRuleSet("*", "*", "*", 1, "7")], > headers: { > "X-Base-Total-Count": "1", > "X-Total-Count": "1", >@@ -420,7 +509,6 @@ describe("Circulation Triggers - Add New Trigger", () => { > cy.get(".modal-dialog").should("not.exist"); > cy.contains("Add new trigger").click(); > >- // Modal should open > cy.get(".modal-dialog").should("be.visible"); > cy.get(".modal-title").should( > "contain", >@@ -428,117 +516,27 @@ describe("Circulation Triggers - Add New Trigger", () => { > ); > }); > >- it("Should require context selection", () => { >+ it("Should require context selection fields", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.contains("Add new trigger").click(); > >- // Should show context selection fields > cy.get("#library_id").should("exist"); > cy.get("#patron_category_id").should("exist"); > cy.get("#item_type_id").should("exist"); >- >- // All fields should be required >- cy.get("input[required]").should("have.length.at.least", 3); >- }); >- >- it("Should proceed through add trigger workflow", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- // Step 1: Select context >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >- }); >- >- // Confirm context >- cy.contains("Confirm context").click(); >- >- // Step 2: Should show existing triggers for context >- cy.get(".modal-dialog").should("contain", "Existing triggers"); >- >- // Step 3: Fill in trigger details >- cy.get("#overdue_delay").should("exist"); >- cy.get("#overdue_delay").type("14"); >- >- // Set restrict checkouts >- cy.get("#restricts-yes").check(); >- >- // Select letter template >- cy.get("#letter_code").should("exist"); >- }); >- >- it("Should validate delay constraints", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- // Complete context selection >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >- }); >- cy.contains("Confirm context").click(); >- >- // Try to enter invalid delay (should have min/max constraints) >- cy.get("#overdue_delay").should("have.attr", "min"); >- cy.get("#overdue_delay").should("have.attr", "max"); > }); > >- it("Should successfully submit new trigger", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >+ it("Should show trigger number 1 when adding first trigger for a library with no existing rules", () => { >+ // Regression test for bug where triggerNumber was NaN when no rules >+ // existed for a library (triggerCounts[library_id] was undefined). >+ cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, >- body: getMockCirculationRule("CPL", "PT", "BK", 2), >- }).as("create-rule"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- // Complete form >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >+ body: [], >+ headers: { "X-Base-Total-Count": "0", "X-Total-Count": "0" }, > }); >- cy.contains("Confirm context").click(); >- >- cy.get("#overdue_delay").type("14"); >- cy.get("#restricts-yes").check(); >- >- // Submit form >- cy.get("form").submit(); >- >- cy.wait("@create-rule"); >- >- // Should return to list view >- cy.get(".modal-dialog").should("not.exist"); >- >- // Should show success message >- cy.get(".alert-info").should("contain", "updated"); >- }); >- >- it("Should handle submission errors", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 500, >- }).as("create-rule-error"); > > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.contains("Add new trigger").click(); > >- // Complete and submit form > cy.get("#library_id .vs__search").type("Centerville{enter}", { > force: true, > }); >@@ -550,13 +548,10 @@ describe("Circulation Triggers - Add New Trigger", () => { > }); > cy.contains("Confirm context").click(); > >- cy.get("#overdue_delay").type("14"); >- cy.get("form").submit(); >- >- cy.wait("@create-rule-error"); >- >- // Should show error message >- cy.get(".alert-warning").should("contain", "went wrong"); >+ // trigger number should be 1, not NaN >+ cy.get("legend").should("contain", "Add new trigger 1"); >+ cy.get("#overdue_delay").should("exist"); >+ cy.get("#overdue_delay").should("not.have.attr", "name", /NaN/); > }); > > it("Should allow canceling add operation", () => { >@@ -565,221 +560,11 @@ describe("Circulation Triggers - Add New Trigger", () => { > > cy.get(".modal-dialog").should("be.visible"); > >- // Click cancel button > cy.contains("Cancel").click(); > >- // Modal should close > cy.get(".modal-dialog").should("not.exist"); >- >- // Should return to list view > cy.get("h1").should("contain", "Circulation triggers"); > }); >- >- it("Should show placeholder values for fallback rules", () => { >- cy.intercept("GET", "/api/v1/circulation_rules*", { >- statusCode: 200, >- body: [ >- { >- ...getMockCirculationRule("*", "*", "*", 1), >- rule_value: "7", // Default delay >- }, >- { >- ...getMockCirculationRule("*", "*", "*", 1), >- rule_name: "overdue_1_restrict", >- rule_value: "1", // Default restrict >- }, >- ], >- headers: { >- "X-Base-Total-Count": "2", >- "X-Total-Count": "2", >- }, >- }); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- // Complete context >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >- }); >- cy.contains("Confirm context").click(); >- >- // Delay field should show fallback value as placeholder >- cy.get("#overdue_delay").should("have.attr", "placeholder", "7"); >- >- // Restrict checkouts should show fallback value >- cy.contains("Fallback to default").should("exist"); >- cy.contains("(Yes)").should("exist"); // Showing the fallback value >- }); >-}); >- >-describe("Circulation Triggers - Edit Existing Trigger", () => { >- beforeEach(() => { >- cy.login(); >- cy.title().should("eq", "Koha staff interface"); >- >- cy.intercept("GET", "/api/v1/**", { >- statusCode: 200, >- body: [], >- }); >- >- cy.intercept("GET", "/api/v1/circulation_rules*", { >- statusCode: 200, >- body: [ >- { >- ...getMockCirculationRule("CPL", "PT", "BK", 1), >- rule_value: "7", >- }, >- { >- ...getMockCirculationRule("CPL", "PT", "BK", 1), >- rule_name: "overdue_1_restrict", >- rule_value: "1", >- }, >- { >- ...getMockCirculationRule("CPL", "PT", "BK", 1), >- rule_name: "overdue_1_letter", >- rule_value: "ODUE1", >- }, >- ], >- headers: { >- "X-Base-Total-Count": "3", >- "X-Total-Count": "3", >- }, >- }).as("get-rules"); >- }); >- >- it("Should open edit modal from table", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- // Click edit button/icon in table >- cy.get("table").contains("Edit").click(); >- >- // Modal should open >- cy.get(".modal-dialog").should("be.visible"); >- }); >- >- it("Should pre-populate form with existing values", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- >- // Context should be pre-selected and disabled >- cy.get("#library_id").should("be.disabled"); >- cy.get("#patron_category_id").should("be.disabled"); >- cy.get("#item_type_id").should("be.disabled"); >- >- // Delay should be pre-filled >- cy.get("#overdue_delay").should("have.value", "7"); >- >- // Restrict should be pre-selected >- cy.get("#restricts-yes").should("be.checked"); >- >- // Letter should be pre-selected >- cy.get("#letter_code .vs__selected").should("contain", "ODUE1"); >- }); >- >- it("Should allow modifying trigger values within constraints", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- >- // Change delay >- cy.get("#overdue_delay").clear().type("10"); >- >- // Change restrict >- cy.get("#restricts-no").check(); >- >- // Values should update >- cy.get("#overdue_delay").should("have.value", "10"); >- cy.get("#restricts-no").should("be.checked"); >- }); >- >- it("Should use increment/decrement buttons for delay", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- >- const initialDelay = 7; >- >- // Click increment >- cy.get(".increment-btn").click(); >- cy.get("#overdue_delay").should("have.value", String(initialDelay + 1)); >- >- // Click decrement >- cy.get(".decrement-btn").click(); >- cy.get("#overdue_delay").should("have.value", String(initialDelay)); >- }); >- >- it("Should allow clearing values to use fallback", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- >- // Clear delay using clear button >- cy.get(".clear-btn").click(); >- cy.get("#overdue_delay").should("have.value", ""); >- >- // Select fallback for restrict >- cy.get("#restricts-fallback").check(); >- >- // Should show what fallback value will be used >- cy.get("#overdue_delay").should("have.attr", "placeholder"); >- }); >- >- it("Should successfully update trigger", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 200, >- body: { >- ...getMockCirculationRule("CPL", "PT", "BK", 1), >- rule_value: "10", >- }, >- }).as("update-rule"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- >- cy.get("#overdue_delay").clear().type("10"); >- cy.get("form").submit(); >- >- cy.wait("@update-rule"); >- >- // Modal should close >- cy.get(".modal-dialog").should("not.exist"); >- >- // Should show success message >- cy.get(".alert-info").should("exist"); >- }); >- >- it("Should handle update errors", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 500, >- }).as("update-rule-error"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get("table").contains("Edit").click(); >- cy.get("#overdue_delay").clear().type("10"); >- cy.get("form").submit(); >- >- cy.wait("@update-rule-error"); >- >- // Should show error message >- cy.get(".alert-warning").should("contain", "went wrong"); >- }); > }); > > describe("Circulation Triggers - Delete Trigger", () => { >@@ -795,9 +580,9 @@ describe("Circulation Triggers - Delete Trigger", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("CPL", "PT", "BK", 1), >- getMockCirculationRule("CPL", "PT", "BK", 2), >- getMockCirculationRule("CPL", "PT", "BK", 3), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("*", "*", "*", 2), >+ getMockRuleSet("*", "*", "*", 3), > ], > headers: { > "X-Base-Total-Count": "3", >@@ -806,20 +591,14 @@ describe("Circulation Triggers - Delete Trigger", () => { > }).as("get-rules"); > }); > >- it("Should only show delete button for last trigger", () => { >+ it("Should only show delete button for last trigger tab", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Navigate to Trigger 3 tab (last one) > cy.get(".nav-link").contains("Trigger 3").click(); >- >- // Delete button should exist > cy.contains("Delete").should("exist"); > >- // Navigate to Trigger 1 tab > cy.get(".nav-link").contains("Trigger 1").click(); >- >- // Delete button should not exist > cy.contains("Delete").should("not.exist"); > }); > >@@ -830,54 +609,9 @@ describe("Circulation Triggers - Delete Trigger", () => { > cy.get(".nav-link").contains("Trigger 3").click(); > cy.contains("Delete").click(); > >- // Confirmation modal should open > cy.get(".modal-dialog").should("be.visible"); > cy.get(".modal-title").should("contain", "Delete"); >- >- // Should show which trigger will be deleted >- cy.get(".modal-body").should("contain", "Trigger 3"); >- }); >- >- it("Should successfully delete trigger", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 200, >- body: null, >- }).as("delete-rule"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get(".nav-link").contains("Trigger 3").click(); >- cy.contains("Delete").click(); >- >- // Confirm deletion >- cy.contains("Confirm").click(); >- >- cy.wait("@delete-rule"); >- >- // Should return to list >- cy.get(".modal-dialog").should("not.exist"); >- >- // Success message should appear >- cy.get(".alert-info").should("exist"); >- }); >- >- it("Should handle delete errors", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 500, >- }).as("delete-rule-error"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.get(".nav-link").contains("Trigger 3").click(); >- cy.contains("Delete").click(); >- cy.contains("Confirm").click(); >- >- cy.wait("@delete-rule-error"); >- >- // Error message should appear >- cy.get(".alert-warning").should("contain", "went wrong"); >+ cy.get(".modal-body").should("contain", "3"); > }); > > it("Should allow canceling delete operation", () => { >@@ -889,13 +623,9 @@ describe("Circulation Triggers - Delete Trigger", () => { > > cy.get(".modal-dialog").should("be.visible"); > >- // Click cancel > cy.contains("Cancel").click(); > >- // Modal should close > cy.get(".modal-dialog").should("not.exist"); >- >- // Should still be on Trigger 3 tab > cy.get(".nav-link") > .contains("Trigger 3") > .should("have.class", "active"); >@@ -915,9 +645,9 @@ describe("Circulation Triggers - Reset Rule Set", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("*", "*", "*", 1), >- getMockCirculationRule("CPL", "PT", "BK", 1), >- getMockCirculationRule("MPL", "PT", "BK", 1), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("CPL", "PT", "BK", 1), >+ getMockRuleSet("MPL", "PT", "BK", 1), > ], > headers: { > "X-Base-Total-Count": "3", >@@ -926,11 +656,10 @@ describe("Circulation Triggers - Reset Rule Set", () => { > }).as("get-rules"); > }); > >- it("Should show reset button for explicit rules", () => { >+ it("Should show Reset button for explicit rule sets in the table", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Look for reset button in rules table > cy.get("table").contains("Reset").should("exist"); > }); > >@@ -938,158 +667,28 @@ describe("Circulation Triggers - Reset Rule Set", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- cy.contains("Reset").click(); >+ cy.get("table").contains("Reset").click(); > >- // Confirmation modal should open > cy.get(".modal-dialog").should("be.visible"); >- cy.get(".modal-title").should("contain", "Reset"); >- >- // Should explain what will happen >- cy.get(".modal-body").should("contain", "rule set will be removed"); >- }); >- >- it("Should successfully reset rule set", () => { >- cy.intercept("PUT", "/api/v1/circulation_rules", { >- statusCode: 200, >- body: null, >- }).as("reset-rules"); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- cy.contains("Reset").click(); >- cy.contains("Confirm").click(); >- >- cy.wait("@reset-rules"); >- >- // Should return to list >- cy.get(".modal-dialog").should("not.exist"); >- >- // Success message >- cy.get(".alert-info").should("exist"); >- }); >- >- it("Should not allow reset if it's the only rule set", () => { >- cy.intercept("GET", "/api/v1/circulation_rules*", { >- statusCode: 200, >- body: [getMockCirculationRule("CPL", "PT", "BK", 1)], >- headers: { >- "X-Base-Total-Count": "1", >- "X-Total-Count": "1", >- }, >- }); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- >- // Reset button should not be available >- cy.contains("Reset").should("not.exist"); >- }); >-}); >- >-describe("Circulation Triggers - Letter Template Filtering", () => { >- beforeEach(() => { >- cy.login(); >- cy.title().should("eq", "Koha staff interface"); >- >- cy.intercept("GET", "/api/v1/**", { >- statusCode: 200, >- body: [], >- }); >- }); >- >- it("Should filter letter templates by library", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- // Select Centerville library >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >- }); >- cy.contains("Confirm context").click(); >- >- // Letter dropdown should include Centerville-specific and default templates >- cy.get("#letter_code .vs__search").click(); >- cy.get("#letter_code .vs__dropdown-menu").should("contain", "ODUE1"); >- cy.get("#letter_code .vs__dropdown-menu").should("contain", "CPL_ODUE"); >- >- // Should NOT include templates for other libraries >- cy.get("#letter_code .vs__dropdown-menu").should( >- "not.contain", >- "MPL_ODUE" >+ cy.get(".modal-title").should( >+ "contain", >+ "Confirm circulation rule set reset" > ); >- }); >- >- it("Should include No letter option", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >- >- cy.get("#library_id .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_id .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_id .vs__search").type("Books{enter}", { >- force: true, >- }); >- cy.contains("Confirm context").click(); >- >- // Should have "No letter" option >- cy.get("#letter_code .vs__search").click(); >- cy.get("#letter_code .vs__dropdown-menu").should( >+ cy.get(".modal-body").should( > "contain", >- "No letter" >+ "Resetting this rule set for the chosen context" > ); > }); >-}); >- >-describe("Circulation Triggers - Loading States", () => { >- beforeEach(() => { >- cy.login(); >- cy.title().should("eq", "Koha staff interface"); >- }); >- >- it("Should show loading messages during initialization", () => { >- cy.intercept("GET", "/api/v1/circulation_rules*", req => { >- // Delay response to see loading state >- req.reply({ >- delay: 1000, >- statusCode: 200, >- body: [], >- }); >- }); > >+ it("Should allow canceling reset operation", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >+ cy.wait("@get-rules"); > >- // Should show loading indicator or message >- cy.contains("Loading").should("exist"); >- }); >- >- it("Should show specific loading messages in modals", () => { >- cy.intercept("GET", "/api/v1/**", { >- statusCode: 200, >- body: [], >- }); >- >- cy.intercept("GET", "/api/v1/circulation_rules*", req => { >- req.reply({ >- delay: 500, >- statusCode: 200, >- body: [], >- }); >- }); >- >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.contains("Add new trigger").click(); >+ cy.get("table").contains("Reset").click(); >+ cy.get(".modal-dialog").should("be.visible"); > >- // Should show context-specific loading message >- cy.contains("Loading").should("exist"); >+ cy.contains("Cancel").click(); >+ cy.get(".modal-dialog").should("not.exist"); > }); > }); > >@@ -1113,11 +712,14 @@ describe("Circulation Triggers - Empty States", () => { > }); > }); > >- it("Should handle no rules gracefully", () => { >+ it("Should render without crashing when no rules exist", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > >- // Should show message about no rules or empty state >- cy.get("body").should("exist"); // Component should still render >+ cy.get("h1").should("contain", "Circulation triggers"); >+ cy.get(".alert-warning").should( >+ "contain", >+ "No default overdue triggers" >+ ); > }); > }); > >@@ -1144,12 +746,12 @@ describe("Circulation Triggers - Complex Scenarios", () => { > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, > body: [ >- getMockCirculationRule("*", "*", "*", 1), >- getMockCirculationRule("*", "*", "*", 2), >- getMockCirculationRule("*", "*", "*", 3), >- getMockCirculationRule("CPL", "*", "*", 1), >- getMockCirculationRule("CPL", "PT", "*", 1), >- getMockCirculationRule("CPL", "PT", "BK", 1), >+ getMockRuleSet("*", "*", "*", 1), >+ getMockRuleSet("*", "*", "*", 2), >+ getMockRuleSet("*", "*", "*", 3), >+ getMockRuleSet("CPL", "*", "*", 1), >+ getMockRuleSet("CPL", "PT", "*", 1), >+ getMockRuleSet("CPL", "PT", "BK", 1), > ], > headers: { > "X-Base-Total-Count": "6", >@@ -1158,58 +760,24 @@ describe("Circulation Triggers - Complex Scenarios", () => { > }).as("get-rules"); > }); > >- it("Should handle multiple triggers for same context", () => { >+ it("Should show all three trigger tabs for default library", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Should show all three triggers > cy.get(".nav-link").contains("Trigger 1").should("exist"); > cy.get(".nav-link").contains("Trigger 2").should("exist"); > cy.get(".nav-link").contains("Trigger 3").should("exist"); > }); > >- it("Should show inheritance hierarchy correctly", () => { >+ it("Should handle rapid tab switching without errors", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.wait("@get-rules"); > >- // Select specific context >- cy.get("#library_select .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_select .vs__search").type("Patron{enter}", { >- force: true, >- }); >- cy.get("#item_type_select .vs__search").type("Books{enter}", { >- force: true, >- }); >- >- // Should show most specific rule >- // And indicate inherited values in italic/bold >- }); >- >- it("Should allow rapid successive operations", () => { >- cy.visit("/cgi-bin/koha/admin/circulation_triggers"); >- cy.wait("@get-rules"); >- >- // Quickly change filters >- cy.get("#library_select .vs__search").type("Centerville{enter}", { >- force: true, >- }); >- cy.get("#patron_category_select .vs__search").type("Patron{enter}", { >- force: true, >- }); >- >- // Switch tabs > cy.get(".nav-link").contains("Trigger 2").click(); > cy.get(".nav-link").contains("Trigger 1").click(); >+ cy.get(".nav-link").contains("Trigger 3").click(); > >- // Toggle filter >- cy.get("#filter-rules .vs__search").click(); >- cy.get("#filter-rules .vs__dropdown-menu") >- .contains("explicitly set rules") >- .click({ force: true }); >- >- // Should handle all operations without errors >+ cy.get(".tab-pane.active").should("exist"); > cy.get("body").should("exist"); > }); > }); >@@ -1226,7 +794,7 @@ describe("Circulation Triggers - Browser Navigation", () => { > > cy.intercept("GET", "/api/v1/circulation_rules*", { > statusCode: 200, >- body: [getMockCirculationRule("CPL", "PT", "BK", 1)], >+ body: [getMockRuleSet("*", "*", "*", 1)], > headers: { > "X-Base-Total-Count": "1", > "X-Total-Count": "1", >@@ -1234,26 +802,23 @@ describe("Circulation Triggers - Browser Navigation", () => { > }); > }); > >- it("Should handle browser back button", () => { >+ it("Should close modal on browser back button", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.contains("Add new trigger").click(); > > cy.get(".modal-dialog").should("be.visible"); > >- // Use browser back > cy.go("back"); > >- // Modal should close > cy.get(".modal-dialog").should("not.exist"); > }); > >- it("Should handle browser forward button", () => { >+ it("Should reopen modal on browser forward button", () => { > cy.visit("/cgi-bin/koha/admin/circulation_triggers"); > cy.contains("Add new trigger").click(); > cy.go("back"); > cy.go("forward"); > >- // Modal should reopen > cy.get(".modal-dialog").should("be.visible"); > }); > }); >-- >2.53.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 10190
:
170357
|
170358
|
170359
|
170360
|
170361
|
170362
|
170363
|
170364
|
170365
|
170366
|
170367
|
170368
|
170369
|
170370
|
170371
|
170372
|
170373
|
170374
|
170375
|
170376
|
170380
|
170449
|
171727
|
171728
|
171729
|
171730
|
171731
|
171732
|
171733
|
171734
|
171735
|
171736
|
171737
|
171738
|
171739
|
171740
|
171741
|
171742
|
171743
|
171744
|
171745
|
171746
|
171747
|
171748
|
171749
|
171750
|
171751
|
171752
|
171753
|
171754
|
171755
|
172588
|
172589
|
172590
|
172591
|
172592
|
172593
|
172594
|
172595
|
172596
|
172597
|
172598
|
172599
|
172600
|
172601
|
172602
|
172603
|
172604
|
172605
|
172606
|
172607
|
172608
|
172609
|
172612
|
172614
|
172617
|
172619
|
172621
|
172622
|
172623
|
172624
|
172625
|
172626
|
172630
|
172631
|
172632
|
172633
|
172634
|
172635
|
172636
|
172637
|
172638
|
172639
|
172640
|
172641
|
172642
|
172643
|
172644
|
172645
|
172646
|
172647
|
172648
|
172649
|
172650
|
172651
|
172652
|
172653
|
172654
|
172655
|
172656
|
172657
|
172658
|
172659
|
172660
|
172661
|
172662
|
172663
|
178643
|
178644
|
178645
|
178646
|
178647
|
178648
|
178649
|
178650
|
178651
|
178652
|
178653
|
178654
|
178655
|
178656
|
178657
|
178658
|
178659
|
178660
|
178661
|
178662
|
178663
|
178664
|
178665
|
178666
|
178667
|
178668
|
178669
|
178670
|
178671
|
178672
|
178673
|
178674
|
178675
|
188075
|
188076
|
188077
|
188078
|
188079
|
188080
|
188081
|
188082
|
188083
|
188084
|
188085
|
188086
|
188087
|
188088
|
188476
|
189345
|
189863
|
189864
|
189865
|
189866
|
189867
|
189868
|
191749
|
191750
|
191751
|
191752
|
191753
|
191754
|
191755
|
191756
|
191757
|
191758
|
191759
|
191760
|
191761
|
191762
|
191763
|
191795
|
191796
|
191805
|
191854
|
191873
|
192871
|
192872
|
192873
|
192874
|
192875
|
192876
|
192877
|
192878
|
192879
|
192880
|
192881
|
192882
|
192883
|
192884
|
192885
|
192886
|
192887
|
192888
|
192889
|
192890
|
192891
|
192892
|
192893
|
192894
|
192895
|
192896
|
192897
|
192898
|
192899
|
193864
|
193866
|
193867
|
193868
|
193869
|
193870
|
193871
|
193872
|
193873
|
193874
|
193875
|
193876
|
193877
|
193878
|
193879
|
193880
|
193881
|
193882
|
193883
|
193884
|
193885
|
193886
|
193887
|
193888
|
193889
|
193890
|
193891
|
193892
|
193893
|
193894
|
193895
|
193896
|
193897
|
193898
|
193899
|
193900
|
193901
|
193902
|
193903
|
193904
|
193905
|
193979
|
194045
|
194046
|
194047
|
194048
|
194049
|
194050
|
194051
|
194052
|
194053
|
194054
|
194055
|
194056
|
194057
|
194058
|
194059
|
194060
|
194061
|
194062
|
194063
|
194064
|
194065
|
194066
|
194067
|
194068
|
194069
|
194070
|
194071
|
194072
|
194073
|
194074
|
194075
|
194076
|
194077
|
194078
|
194079
|
194080
|
194081
|
194082
|
194083
|
194084
|
194085
|
194086
|
194087
|
194088
|
194090
|
194094