Visual enhancements for dark mode, along with search functionality for service requests and installed base
This commit is contained in:
34
tests/customerModalDarkMode.test.cjs
Normal file
34
tests/customerModalDarkMode.test.cjs
Normal file
@@ -0,0 +1,34 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { test } = require("node:test");
|
||||
|
||||
function luminance(hex) {
|
||||
const rgb = hex.slice(1).match(/../g).map(value => parseInt(value, 16) / 255)
|
||||
.map(value => value <= .04045 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4);
|
||||
return rgb[0] * .2126 + rgb[1] * .7152 + rgb[2] * .0722;
|
||||
}
|
||||
function contrast(a, b) {
|
||||
const values = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
||||
return (values[0] + .05) / (values[1] + .05);
|
||||
}
|
||||
for (const feature of ["install-base", "service-requests"]) {
|
||||
test(`${feature}: explicit dark text colors cover cells, headings, counts and errors with readable contrast`, () => {
|
||||
const css = fs.readFileSync(path.join(__dirname, `../src/features/${feature}/styles.css`), "utf8");
|
||||
const rules = [...css.matchAll(/([^{}]+)\{([^{}]+)\}/g)];
|
||||
const prefix = `.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-${feature}`;
|
||||
function color(selector) {
|
||||
const matches = rules.filter(([, selectors, body]) => selectors.split(",").map(value => value.trim()).includes(selector)
|
||||
&& /(?:^|;)\s*color\s*:/.test(body));
|
||||
assert.ok(matches.length, `Missing explicit color for ${selector}`);
|
||||
return matches.at(-1)[2].match(/(?:^|;)\s*color\s*:\s*(#[a-f0-9]{6})/i)[1];
|
||||
}
|
||||
for (const suffix of ["-content", "-table", "-table th", "-table td", "-message", '-message[role="alert"]']) {
|
||||
const foreground = color(prefix + suffix);
|
||||
const background = suffix.endsWith(" th") ? "#353331" : "#292827";
|
||||
assert.ok(contrast(foreground, background) >= 4.5, `${suffix} needs at least 4.5:1 contrast`);
|
||||
}
|
||||
assert.match(css, /color: #17693a; background: #e1f1e5/, "light status colors remain unchanged");
|
||||
assert.match(css, /color: #b8e4c6; background: #214d34/, "dark status colors remain unchanged");
|
||||
});
|
||||
}
|
||||
@@ -96,7 +96,7 @@ test("tabs support arrows, Home and End with a single selected tab and labelled
|
||||
|
||||
test("scoped debug styles are packaged after the unchanged main stylesheet", () => {
|
||||
const root = path.join(__dirname, "..");
|
||||
const css = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css"]
|
||||
const css = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css", "src/features/opportunities/customer-table-search.css"]
|
||||
.map(file => fs.readFileSync(path.join(root, file), "utf8")).join("\n");
|
||||
assert.ok(composeScript("content").includes(`const redwoodStyles = ${JSON.stringify(css)};`));
|
||||
assert.ok(!composeScript("background").includes("opportunities-extension-debug-tabs"));
|
||||
|
||||
@@ -100,6 +100,42 @@ function setup() {
|
||||
const customer = { CustomerAccount: { PartyNumber: "customer-123", PartyUniqueName: "Synthetic customer" } };
|
||||
const walk = node => [node, ...node.children.flatMap(walk)];
|
||||
|
||||
test("installed base searches every displayed column and formatted values, including text entered during loading", () => {
|
||||
const { app, overlay, document } = setup();
|
||||
const view = app.installBaseView.openInstallBaseModal(customer);
|
||||
const modal = overlay.children[0];
|
||||
const input = walk(modal).find(node => node.tag === "input");
|
||||
const row = { partyName: "S\u00e3o Customer", productGroupName: "Group Alpha", productName: "Database Beta",
|
||||
currentSupport: 1234.56, orderNumber: "ORD-321", supportStatus: "ACTIVE", quantityType: "Processor",
|
||||
quantity: 7, supportStartDate: "2026-03-12", supportEndDate: "2027-04-13", licenseValue: 9876.54, shipDate: "2020-05-14",
|
||||
hiddenField: "secretword" };
|
||||
input.value = "sao beta";
|
||||
input.listeners.input();
|
||||
assert.equal(modal.attributes["aria-busy"], "true");
|
||||
view.render([row, { productName: "Other product" }]);
|
||||
const rows = () => walk(modal).filter(node => node.tag === "td").length / 12;
|
||||
assert.equal(rows(), 1);
|
||||
for (const query of [...Object.values(row).slice(0, 12), "1,234.56", "9,876.54", "12/03/2026", "13/04/2027", "14/05/2020"]) {
|
||||
input.value = String(query); input.listeners.input();
|
||||
assert.equal(rows(), 1, String(query));
|
||||
assert.equal(walk(modal).find(node => node.tag === "input"), input, "input is not replaced while typing");
|
||||
}
|
||||
input.value = "secretword"; input.listeners.input();
|
||||
assert.equal(rows(), 0);
|
||||
assert.ok(walk(modal).some(node => node.textContent.includes("No matching records.")));
|
||||
input.value = " "; input.listeners.input();
|
||||
assert.equal(rows(), 2);
|
||||
input.focus();
|
||||
modal.listeners.keydown({ key: "Tab", shiftKey: true, preventDefault() {} });
|
||||
assert.equal(document.activeElement.tag, "button");
|
||||
view.render([], "Synthetic failure");
|
||||
input.value = "other"; input.listeners.input();
|
||||
assert.ok(walk(modal).some(node => node.textContent === "Synthetic failure"));
|
||||
app.installBaseView.closeInstallBaseModal();
|
||||
app.installBaseView.openInstallBaseModal(customer).render([row]);
|
||||
assert.equal(walk(overlay).filter(node => node.tag === "td").length, 12, "reopening starts without an invisible filter");
|
||||
});
|
||||
|
||||
test("modal shows skeleton, renders only requested fields safely and ignores closed/replaced responses", () => {
|
||||
const { app, overlay, document } = setup();
|
||||
const first = app.installBaseView.openInstallBaseModal(customer);
|
||||
@@ -122,6 +158,8 @@ test("modal shows skeleton, renders only requested fields safely and ignores clo
|
||||
assert.equal(cells[10].textContent, "0.00");
|
||||
assert.ok(cells[8].textContent.includes("2026"));
|
||||
current.listeners.keydown({ key: "Tab", preventDefault() {} });
|
||||
assert.equal(document.activeElement.type, "search");
|
||||
current.listeners.keydown({ key: "Tab", preventDefault() {} });
|
||||
assert.equal(document.activeElement.attributes.role, "region");
|
||||
assert.equal(app.installBaseView.closeInstallBaseModal(), true);
|
||||
second.render([], "Late error");
|
||||
|
||||
@@ -158,6 +158,6 @@ test("table styles reserve compact widths, prioritize Name/Customer and keep nes
|
||||
assert.ok(remaining > 0);
|
||||
assert.ok(remaining / 2 >= 12 + 84, "three customer controls fit after the left inset");
|
||||
}
|
||||
const allCss = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css"].map(read).join("\n");
|
||||
const allCss = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css", "src/features/opportunities/customer-table-search.css"].map(read).join("\n");
|
||||
assert.ok(composeScript("content").includes(`const redwoodStyles = ${JSON.stringify(allCss)};`));
|
||||
});
|
||||
|
||||
@@ -143,6 +143,35 @@ function setup() {
|
||||
}
|
||||
const customer = { CustomerAccount: { PartyNumber: "customer-test", PartyUniqueName: "Synthetic customer" } };
|
||||
|
||||
test("service request search covers nested and formatted columns and preserves errors and full results on clearing", () => {
|
||||
const { app, overlay } = setup();
|
||||
const view = app.serviceRequestsView.openServiceRequestsModal(customer);
|
||||
const modal = overlay.children[0];
|
||||
const input = walk(modal).find(node => node.tag === "input");
|
||||
const row = { customer: { name: "S\u00e3o Customer", registryId: "REG-123" }, id: "SR-456", severity: "2", status: "Open",
|
||||
subStatus: "Work In Progress", component: { productName: "Database Alpha" }, os: "Linux", csi: "CSI-789",
|
||||
created: "2026-03-12", lastUpdated: "2026-04-13", hiddenField: "secretword" };
|
||||
input.value = "SAO linux"; input.listeners.input();
|
||||
view.render([row, { id: "Other" }]);
|
||||
const rows = () => walk(modal).filter(node => node.tag === "td").length / 11;
|
||||
assert.equal(rows(), 1);
|
||||
for (const query of ["sao", "REG-123", "SR-456", "2", "open", "progress", "alpha", "linux", "CSI-789", "12/03/2026", "13/04/2026", "2026-03-12"]) {
|
||||
input.value = query; input.listeners.input();
|
||||
assert.equal(rows(), 1, query);
|
||||
}
|
||||
input.value = "secretword"; input.listeners.input();
|
||||
assert.equal(rows(), 0);
|
||||
assert.ok(walk(modal).some(node => node.textContent.includes("No matching records.")));
|
||||
input.value = ""; input.listeners.input();
|
||||
assert.equal(rows(), 2);
|
||||
view.render([], "Synthetic failure");
|
||||
input.value = "anything"; input.listeners.input();
|
||||
assert.ok(walk(modal).some(node => node.textContent === "Synthetic failure"));
|
||||
app.serviceRequestsView.closeServiceRequestsModal();
|
||||
app.serviceRequestsView.openServiceRequestsModal(customer).render([row]);
|
||||
assert.equal(walk(overlay).filter(node => node.tag === "td").length, 11);
|
||||
});
|
||||
|
||||
test("skeleton, exact field mapping, safe text, dates, badge, empty/error and focus states", () => {
|
||||
const { app, overlay, document } = setup();
|
||||
const trigger = new Element("button", document);
|
||||
@@ -169,6 +198,8 @@ test("skeleton, exact field mapping, safe text, dates, badge, empty/error and fo
|
||||
assert.equal(cells[9].textContent, app.opportunitiesFormatters.formatOracleResponseDate("2026-03-04"));
|
||||
assert.equal(cells[10].textContent, app.opportunitiesFormatters.formatOracleResponseDate("2026-09-17"));
|
||||
modal.listeners.keydown({ key: "Tab", preventDefault() {} });
|
||||
assert.equal(document.activeElement.type, "search");
|
||||
modal.listeners.keydown({ key: "Tab", preventDefault() {} });
|
||||
assert.equal(document.activeElement.attributes.role, "region");
|
||||
view.render([]);
|
||||
assert.ok(walk(modal).some(node => node.textContent === "No open service requests found for this customer."));
|
||||
|
||||
Reference in New Issue
Block a user