Badge indicator showing whether an opportunity has an associated Workload Workbench Action
This commit is contained in:
@@ -3,7 +3,7 @@ const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
const response = data => new Response(JSON.stringify(data), { status: 200 });
|
||||
function setup(fetch) {
|
||||
const document = { createElement: () => ({ attributes: {}, setAttribute(key, value) { this.attributes[key] = value; } }) };
|
||||
const document = { createElement: () => ({ attributes: {}, addEventListener() {}, setAttribute(key, value) { this.attributes[key] = value; } }) };
|
||||
const app = loadApplication("content", { fetch, document, Intl, Date });
|
||||
app.debugContentView.renderDebugPanel = () => {};
|
||||
app.opportunitiesQueryState.periodRequestVersion = 1;
|
||||
@@ -81,7 +81,7 @@ test("CRM query preserves payload, paginates before opportunities, uses tokenrel
|
||||
assert.equal(found.textContent, "\u2705 SR");
|
||||
assert.equal(found.attributes["data-state"], "matched");
|
||||
for (const detail of ["SR-0", "SR-1", "Reported Hours: 0", "Test Owner", "test@example.invalid", "<script>synthetic</script>"])
|
||||
assert.ok(found.title.includes(detail));
|
||||
assert.ok(found.attributes["aria-label"].includes(detail));
|
||||
assert.equal(app.opportunityCrmServiceRequests.createBadge("OTHER").textContent, "\u26d4 SR");
|
||||
assert.equal(app.opportunityCrmServiceRequests.createBadge(null), null);
|
||||
app.opportunityCrmServiceRequests.reset();
|
||||
|
||||
105
tests/crmSrPopover.test.cjs
Normal file
105
tests/crmSrPopover.test.cjs
Normal file
@@ -0,0 +1,105 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
|
||||
function setup() {
|
||||
const document = { activeElement: null, listeners: new Map() };
|
||||
const window = { innerWidth: 400, innerHeight: 600, listeners: new Map(), setTimeout, clearTimeout };
|
||||
for (const target of [document, window]) {
|
||||
target.addEventListener = (key, fn) => target.listeners.set(key, fn);
|
||||
target.removeEventListener = key => target.listeners.delete(key);
|
||||
}
|
||||
const walk = node => [node, ...node.children.flatMap(walk)];
|
||||
function element(tag) {
|
||||
return {
|
||||
tag, children: [], attributes: {}, listeners: {}, style: {}, textContent: "",
|
||||
append(...nodes) { nodes.forEach(node => { node.parent = this; this.children.push(node); }); },
|
||||
remove() { this.parent.children = this.parent.children.filter(node => node !== this); this.parent = null; },
|
||||
setAttribute(key, value) { this.attributes[key] = value; },
|
||||
addEventListener(key, fn) { this.listeners[key] = fn; },
|
||||
contains(target) { return walk(this).includes(target); },
|
||||
get isConnected() { return this === modal || !!this.parent?.isConnected; },
|
||||
getBoundingClientRect() { return { left: 350, bottom: 550, height: 250 }; },
|
||||
querySelector() { return walk(this).find(node => node.tag === "a" || node.tag === "button"); },
|
||||
focus() { document.activeElement = this; this.listeners.focus?.(); }
|
||||
};
|
||||
}
|
||||
const modal = element("main");
|
||||
Object.assign(document, {
|
||||
createElement: element, createElementNS: (_, tag) => element(tag),
|
||||
getElementById: id => id === "opportunities-extension-modal" ? modal : null
|
||||
});
|
||||
const copied = [];
|
||||
const app = loadApplication("content", { document, window, setTimeout, clearTimeout,
|
||||
navigator: { clipboard: { writeText: async value => copied.push(value) } } });
|
||||
// Keep the copy test independent of the toast's own DOM/timer implementation.
|
||||
modal.querySelector = () => ({ classList: { toggle() {} } });
|
||||
window.setTimeout = () => 0;
|
||||
app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => [
|
||||
{ SrId: "NOT-THE-URL-ID", SrNumber: "SR /?&1", Title: "<script>synthetic</script>",
|
||||
ReportedHours_c: 0, OpportunityName_Id_c: { OptyNumber: "OP1" } },
|
||||
{ SrNumber: "SR2", OpportunityName_Id_c: { OptyNumber: "OP1" } }
|
||||
];
|
||||
app.opportunitiesQueryState.periodRequestVersion = 1;
|
||||
const panel = () => modal.children.find(node => node.className === "opportunities-extension-sr-popover");
|
||||
return { app, document, window, modal, walk, copied, panel };
|
||||
}
|
||||
|
||||
test("SR popover uses encoded SrNumber, safe details and existing copy control for every record", async () => {
|
||||
const { app, modal, walk, copied, panel } = setup();
|
||||
await app.opportunityCrmServiceRequests.load("synthetic", 1);
|
||||
const badge = app.opportunityCrmServiceRequests.createBadge("OP1");
|
||||
modal.append(badge);
|
||||
badge.listeners.pointerenter();
|
||||
const nodes = walk(panel());
|
||||
const links = nodes.filter(node => node.tag === "a");
|
||||
assert.equal(links.length, 2);
|
||||
for (const link of links) {
|
||||
assert.equal(link.parent.children[0].textContent, "SR Number:");
|
||||
assert.equal(link.parent.children[2].className, "opportunities-extension-copy-button");
|
||||
}
|
||||
const details = nodes.filter(node => node.className === "opportunities-extension-sr-details");
|
||||
assert.equal(details.length, 2);
|
||||
details.forEach(node => assert.doesNotMatch(node.textContent, /SR Number:|SR \/\?&1|SR2/));
|
||||
assert.equal(links[0].href, "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=SR%20%2F%3F%261");
|
||||
assert.equal(links[0].rel, "noopener noreferrer");
|
||||
assert.equal(links[0].target, "_blank");
|
||||
assert.ok(nodes.some(node => node.textContent.includes("<script>synthetic</script>")));
|
||||
assert.equal(nodes.some(node => node.tag === "script"), false);
|
||||
const copies = nodes.filter(node => node.className === "opportunities-extension-copy-button");
|
||||
assert.equal(copies.length, 2);
|
||||
assert.equal(copies[0].title, "Copy SR number");
|
||||
copies[0].listeners.click({ preventDefault() {}, stopPropagation() {} });
|
||||
await Promise.resolve();
|
||||
assert.deepEqual(copied, ["SR /?&1"]);
|
||||
assert.equal(panel().style.width, "376px");
|
||||
app.opportunityCrmServiceRequests.reset();
|
||||
assert.equal(panel(), undefined);
|
||||
});
|
||||
|
||||
test("SR popover pins on click, closes on Escape/outside/scroll/reset and removes global listeners", async () => {
|
||||
const { app, modal, document, window, panel } = setup();
|
||||
await app.opportunityCrmServiceRequests.load("synthetic", 1);
|
||||
const badge = app.opportunityCrmServiceRequests.createBadge("OP1");
|
||||
modal.append(badge);
|
||||
badge.listeners.click({ stopPropagation() {} });
|
||||
assert.equal(badge.attributes["aria-expanded"], "true");
|
||||
assert.ok(panel().contains(document.activeElement));
|
||||
document.listeners.get("keydown")({ key: "Escape", preventDefault() {}, stopPropagation() {} });
|
||||
assert.equal(panel(), undefined);
|
||||
assert.equal(document.activeElement, badge);
|
||||
assert.equal(document.listeners.size, 0);
|
||||
assert.equal(window.listeners.size, 0);
|
||||
badge.listeners.pointerenter();
|
||||
document.listeners.get("pointerdown")({ target: modal });
|
||||
assert.equal(panel(), undefined);
|
||||
badge.listeners.pointerenter();
|
||||
document.listeners.get("scroll")({ target: panel() });
|
||||
assert.ok(panel(), "scroll inside the tooltip keeps it open");
|
||||
document.listeners.get("scroll")({ target: modal });
|
||||
assert.equal(panel(), undefined);
|
||||
badge.listeners.pointerenter();
|
||||
app.opportunityCrmServiceRequests.reset();
|
||||
assert.equal(panel(), undefined);
|
||||
assert.equal(document.listeners.size, 0);
|
||||
});
|
||||
@@ -127,7 +127,7 @@ test("CRM SR badge follows Ramp Months and remains visible while product data is
|
||||
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ RampMonths_c: 3 }] });
|
||||
h.app.opportunitiesTable.renderOpportunitiesTable();
|
||||
const actions = h.cell("name").children[0].children[1];
|
||||
assert.deepEqual(actions.children.map(child => child.textContent), ["1", "RM 3", "\u2705 SR"]);
|
||||
assert.deepEqual(actions.children.map(child => child.textContent), ["1", "RM 3", "\u2705 SR", "Act ?"]);
|
||||
findClass(h.cell("name"), "opportunities-extension-products-count").click();
|
||||
assert.ok(findClass(h.rows()[1], "opportunities-extension-products-table"));
|
||||
});
|
||||
|
||||
109
tests/productsProgressFade.test.cjs
Normal file
109
tests/productsProgressFade.test.cjs
Normal file
@@ -0,0 +1,109 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
|
||||
function fixture(reduced = false) {
|
||||
let now = 0, nextId = 0;
|
||||
const timers = new Map();
|
||||
const badge = { hidden: false, attributes: {}, setAttribute(key, value) { this.attributes[key] = value; } };
|
||||
const timestamp = {};
|
||||
const app = loadApplication("content", {
|
||||
document: { getElementById: id => id === app.contracts.PRODUCTS_PROGRESS_ID ? badge : timestamp, querySelector: () => badge },
|
||||
setTimeout: (fn, delay) => { const id = ++nextId; timers.set(id, { fn, at: now + delay }); return id; },
|
||||
clearTimeout: id => timers.delete(id), matchMedia: () => ({ matches: reduced })
|
||||
});
|
||||
const render = state => {
|
||||
app.productsContentState.productsProgress = { state, completed: 2, total: 2, failed: 0, cacheHits: 0 };
|
||||
app.productsContentJobs.renderProductsProgress();
|
||||
};
|
||||
function advance(ms) {
|
||||
const end = now + ms;
|
||||
while (true) {
|
||||
const entry = [...timers].filter(([, timer]) => timer.at <= end).sort((a, b) => a[1].at - b[1].at)[0];
|
||||
if (!entry) break;
|
||||
now = entry[1].at;
|
||||
timers.delete(entry[0]);
|
||||
entry[1].fn();
|
||||
}
|
||||
now = end;
|
||||
}
|
||||
app.productsContentState.currentProductsRequestId = "first";
|
||||
app.productsContentState.productsLastCachedAt = 1;
|
||||
return { app, badge, timestamp, render, advance, timers };
|
||||
}
|
||||
|
||||
test("Products ready stays for five seconds then fades for 300ms before hidden", () => {
|
||||
const h = fixture();
|
||||
h.render("complete");
|
||||
h.advance(4999);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
assert.equal(h.badge.attributes["data-fading"], "false");
|
||||
h.render("complete");
|
||||
h.advance(1);
|
||||
assert.equal(h.badge.attributes["data-fading"], "true");
|
||||
assert.equal(h.badge.hidden, false);
|
||||
h.advance(300);
|
||||
assert.equal(h.badge.hidden, true);
|
||||
h.render("complete");
|
||||
assert.equal(h.badge.hidden, true, "rerenders do not restart the completion timer");
|
||||
assert.equal(h.timestamp.hidden, false, "Last update remains visible");
|
||||
});
|
||||
|
||||
test("new product jobs cancel a pending fade and errors remain visible", () => {
|
||||
const h = fixture();
|
||||
h.render("complete");
|
||||
h.advance(5000);
|
||||
h.app.productsContentState.currentProductsRequestId = "second";
|
||||
h.render("running");
|
||||
h.advance(10000);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
assert.equal(h.badge.attributes["data-fading"], "false");
|
||||
assert.equal(h.timers.size, 0);
|
||||
for (const state of ["complete-with-errors", "error"]) {
|
||||
h.render(state);
|
||||
h.advance(10000);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
}
|
||||
h.render("complete");
|
||||
h.advance(5300);
|
||||
assert.equal(h.badge.hidden, true);
|
||||
});
|
||||
|
||||
test("reduced motion hides at five seconds without an animated delay", () => {
|
||||
const h = fixture(true);
|
||||
h.render("complete");
|
||||
h.advance(5000);
|
||||
assert.equal(h.badge.hidden, true);
|
||||
});
|
||||
|
||||
test("Authenticated fades after five seconds without restarting on repeated status updates", () => {
|
||||
const h = fixture();
|
||||
const update = () => h.app.contentModal.updateAuthBadge(h.app.contracts.AUTH_STATUS.authenticated);
|
||||
update();
|
||||
h.advance(4999);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
update();
|
||||
h.advance(1);
|
||||
assert.equal(h.badge.attributes["data-fading"], "true");
|
||||
h.advance(300);
|
||||
assert.equal(h.badge.hidden, true);
|
||||
update();
|
||||
assert.equal(h.badge.hidden, true);
|
||||
h.app.contentModal.updateAuthBadge(h.app.contracts.AUTH_STATUS.unauthenticated);
|
||||
h.advance(10000);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
assert.equal(h.badge.attributes["data-fading"], "false");
|
||||
update();
|
||||
h.advance(5000);
|
||||
h.app.contentModal.updateAuthBadge(h.app.contracts.AUTH_STATUS.pending);
|
||||
h.advance(300);
|
||||
assert.equal(h.badge.hidden, false);
|
||||
assert.equal(h.badge.textContent, "Authenticating");
|
||||
});
|
||||
|
||||
test("Authenticated respects reduced motion", () => {
|
||||
const h = fixture(true);
|
||||
h.app.contentModal.updateAuthBadge(h.app.contracts.AUTH_STATUS.authenticated);
|
||||
h.advance(5000);
|
||||
assert.equal(h.badge.hidden, true);
|
||||
});
|
||||
185
tests/workloadActions.test.cjs
Normal file
185
tests/workloadActions.test.cjs
Normal file
@@ -0,0 +1,185 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadBackground, response } = require("./support/characterization.cjs");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
const flush = () => new Promise(resolve => setImmediate(resolve));
|
||||
|
||||
for (const family of ["chrome", "firefox"]) {
|
||||
test(`${family}: WWB-TS selection, fixed workload header, first-page GET and masked debug`, async () => {
|
||||
const calls = [];
|
||||
const h = loadBackground({ family, fetch: async (url, options) => {
|
||||
calls.push({ url, options });
|
||||
if (url.endsWith("consumerTokenService")) return response([
|
||||
{ tokenService: "ACCP-TS", uiaasHeader: { value: "summary" } },
|
||||
{ tokenService: "OCICONS-TS", uiaasHeader: { value: "consumption" } },
|
||||
{ tokenService: "WWB-TS", uiaasHeader: { label: "ignored-label", value: "synthetic-wwb" } }
|
||||
]);
|
||||
return response({ actions: [{ id: 1, name: "TEST" }], totalCount: 20, hasNext: true });
|
||||
} });
|
||||
const auth = await h.dispatch({ type: "opportunitiesExtension.requestConsumerTokenService" });
|
||||
assert.equal(auth.authorizations.workloadActions.value, "synthetic-wwb");
|
||||
const result = await h.dispatch({ type: "opportunitiesExtension.requestWlActions", revnId: "12345", authorization: auth.authorizations.workloadActions });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.hasNext, true);
|
||||
assert.equal(result.actions.length, 1);
|
||||
assert.equal(calls.length, 2, "no pagination");
|
||||
assert.equal(calls[1].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/actions?parentType=WORKLOAD&parentId=12345");
|
||||
assert.equal(calls[1].options.method, "GET");
|
||||
assert.equal(calls[1].options.headers["Wwb-Provider-Authorization"], "synthetic-wwb");
|
||||
assert.equal(calls[1].options.headers.Authorization, undefined);
|
||||
assert.equal(result.debugRequests[0].name, "requestWlActions");
|
||||
assert.equal(result.debugRequests[0].requestHeaders["Wwb-Provider-Authorization"], "(present)");
|
||||
});
|
||||
}
|
||||
|
||||
test("workload actions reject missing credentials, invalid ids, HTTP/network/JSON failures", async () => {
|
||||
for (const fetch of [
|
||||
async () => { throw new Error("Synthetic network failure"); },
|
||||
async () => new Response("Denied", { status: 401 }),
|
||||
async () => new Response("Denied", { status: 403 }),
|
||||
async () => new Response("<html>Login</html>"),
|
||||
async () => response({ actions: null, hasNext: false })
|
||||
]) {
|
||||
const h = loadBackground({ fetch });
|
||||
const result = await h.dispatch({ type: "opportunitiesExtension.requestWlActions", revnId: "123", authorization: { value: "synthetic" } });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.debugRequests.length, 1);
|
||||
}
|
||||
const h = loadBackground({ fetch: () => assert.fail("Unexpected network") });
|
||||
for (const fields of [{ revnId: "123" }, { revnId: "bad&id=2", authorization: { value: "synthetic" } }]) {
|
||||
assert.equal((await h.dispatch({ type: "opportunitiesExtension.requestWlActions", ...fields })).ok, false);
|
||||
}
|
||||
});
|
||||
|
||||
function contentFixture(send) {
|
||||
const app = loadApplication("content", { document: { createElement: () => ({ attributes: {}, setAttribute(k, v) { this.attributes[k] = v; } }) } });
|
||||
app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic-wwb" } });
|
||||
app.authSessionState.principal = "test@example.invalid";
|
||||
app.platformContentMessages.sendRuntimeMessage = send;
|
||||
app.debugContentView.renderDebugPanel = () => {};
|
||||
app.productsContentJobs.queueProductsTableRender = () => {};
|
||||
return app;
|
||||
}
|
||||
|
||||
test("actions aggregate all products, share RevnId queries across Reference changes and restart on reopen", async () => {
|
||||
const calls = [];
|
||||
const app = contentFixture(async message => {
|
||||
calls.push(message);
|
||||
return { ok: true, hasNext: false, actions: message.revnId === "1" ? [{ id: 1, owner: "test@example.invalid", name: "<script>safe</script>" }] : [] };
|
||||
});
|
||||
const products = { items: [{ RevnId: "1" }, { RevnId: 2 }, { RevnId: "1" }] };
|
||||
app.productsContentState.opptyProductsByNumber.set("A", products);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("A").textContent, "Act ...");
|
||||
await flush();
|
||||
assert.equal(calls.length, 2);
|
||||
assert.equal(calls[0].authorization.value, "synthetic-wwb");
|
||||
const badge = app.opportunityWorkloadActions.createBadge("A");
|
||||
assert.equal(badge.textContent, "\u2705 Act");
|
||||
assert.match(badge.title, /<script>safe<\/script>/);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
await flush();
|
||||
assert.equal(calls.length, 2);
|
||||
app.productsContentState.opptyProductsByNumber.set("B", { items: [{ RevnId: 2 }] });
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("B").textContent, "\u26d4 Act");
|
||||
app.opportunityWorkloadActions.reset(false);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
await flush();
|
||||
assert.equal(calls.length, 2);
|
||||
app.opportunityWorkloadActions.reopen();
|
||||
await flush();
|
||||
assert.equal(calls.length, 4);
|
||||
assert.equal(app.debugContentView.getRequestCategory({ name: "requestWlActions" }), "actions");
|
||||
});
|
||||
|
||||
test("errors, missing ids and partial empty pages never appear as confirmed absence", async () => {
|
||||
for (const result of [{ ok: false, error: "Synthetic" }, { ok: true, actions: [], hasNext: true }]) {
|
||||
const app = contentFixture(async () => result);
|
||||
const products = { items: [{ RevnId: "1" }] };
|
||||
app.productsContentState.opptyProductsByNumber.set("A", products);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
await flush();
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("A").textContent, "Act ?");
|
||||
app.productsContentState.opptyProductsByNumber.set("A", { items: [{}] });
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("A").textContent, "Act ?");
|
||||
}
|
||||
});
|
||||
|
||||
test("Act requires an owner matching tokenrelay principal while tooltip retains every action", async () => {
|
||||
const actions = [
|
||||
{ id: 1, name: "Other action", owner: "other@example.invalid", ownerEmail: "test@example.invalid" },
|
||||
{ id: 2, name: "My action", owner: "test@example.invalid", ownerEmail: "different@example.invalid" }
|
||||
];
|
||||
const app = contentFixture(async () => ({ ok: true, actions, hasNext: false }));
|
||||
const products = { items: [{ RevnId: "1" }] };
|
||||
app.productsContentState.opptyProductsByNumber.set("A", products);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
await flush();
|
||||
const badge = () => app.opportunityWorkloadActions.createBadge("A");
|
||||
assert.equal(badge().textContent, "\u2705 Act");
|
||||
assert.match(badge().title, /Other action/);
|
||||
assert.match(badge().title, /My action/);
|
||||
app.authSessionState.principal = "different@example.invalid";
|
||||
assert.equal(badge().textContent, "\u26d4 Act", "ownerEmail must not determine ownership");
|
||||
assert.match(badge().title, /Other action/);
|
||||
assert.match(badge().title, /My action/);
|
||||
app.authSessionState.principal = "";
|
||||
assert.equal(badge().textContent, "Act ?");
|
||||
});
|
||||
|
||||
test("general tokenrelay retains principal without changing access-token return or accepting sales-common identity", async () => {
|
||||
let payload = { access_token: "synthetic-token", principal: "test@example.invalid" };
|
||||
const app = loadApplication("content", { fetch: async () => response(payload) });
|
||||
app.authXsrfContent.getXsrfToken = async () => ({ token: "synthetic-xsrf" });
|
||||
app.debugContentView.renderDebugPanel = () => {};
|
||||
assert.equal(await app.authTokenRelay.requestTokenRelay(), "synthetic-token");
|
||||
assert.equal(app.authSessionState.principal, "test@example.invalid");
|
||||
payload = { access_token: "scoped-token", principal: "other@example.invalid", expires_in: 3600 };
|
||||
await app.authTokenRelay.requestTokenRelay({ url: `${app.contracts.TOKEN_RELAY_URL}?scopesfq=synthetic`, includeExpiry: true });
|
||||
assert.equal(app.authSessionState.principal, "test@example.invalid");
|
||||
payload = { access_token: "synthetic-token" };
|
||||
await app.authTokenRelay.requestTokenRelay();
|
||||
assert.equal(app.authSessionState.principal, "");
|
||||
});
|
||||
|
||||
test("actions queue caps concurrency and discards results/queued jobs from closed modals", async () => {
|
||||
const pending = [];
|
||||
const app = contentFixture(() => new Promise(resolve => pending.push(resolve)));
|
||||
const products = { items: Array.from({ length: 8 }, (_, i) => ({ RevnId: String(i + 1) })) };
|
||||
app.productsContentState.opptyProductsByNumber.set("A", products);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
await flush();
|
||||
assert.equal(pending.length, 4);
|
||||
app.opportunityWorkloadActions.reset(false);
|
||||
pending.forEach(resolve => resolve({ ok: true, hasNext: false, actions: [{ id: 1 }] }));
|
||||
await flush();
|
||||
assert.equal(pending.length, 4);
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("A"), null);
|
||||
});
|
||||
|
||||
test("product progress triggers Actions for cached products and ignores stale product jobs", async () => {
|
||||
let listener;
|
||||
const calls = [];
|
||||
const app = loadApplication("content", {
|
||||
document: { getElementById: () => null },
|
||||
window: { requestAnimationFrame() {} },
|
||||
chrome: { runtime: { onMessage: { addListener(fn) { listener = fn; } } } }
|
||||
});
|
||||
app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic" } });
|
||||
app.platformContentMessages.sendRuntimeMessage = async message => {
|
||||
calls.push(message);
|
||||
return { ok: true, actions: [], hasNext: false };
|
||||
};
|
||||
app.productsContentState.currentProductsRequestId = "current";
|
||||
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
|
||||
const message = { type: app.messageTypes?.opptyProductsProgress || "opportunitiesExtension.opptyProductsProgress",
|
||||
requestId: "current", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: true };
|
||||
listener({ ...message, requestId: "old" });
|
||||
await flush();
|
||||
assert.equal(calls.length, 0);
|
||||
listener(message);
|
||||
await flush();
|
||||
assert.equal(calls.length, 1);
|
||||
assert.equal(calls[0].type, "opportunitiesExtension.requestWlActions");
|
||||
assert.equal(calls[0].revnId, "123");
|
||||
});
|
||||
Reference in New Issue
Block a user