const assert = require("node:assert/strict"); const { test } = require("node:test"); const { loadApplication } = require("./support/application.cjs"); test("activity indicators share Products styles, fade after 5s, retain failures and cancel stale timers", () => { const nodes = []; const timers = new Map(); let id = 0; const app = loadApplication("content", { document: { querySelector: () => ({ append: node => nodes.push(node) }), createElement: () => ({ attributes: {}, setAttribute(key, value) { this.attributes[key] = value; } }) }, setTimeout: (fn, delay) => { timers.set(++id, { fn, delay }); return id; }, clearTimeout: key => timers.delete(key) }); const update = app.activityProgress.update; update("sr", "running", 15, 58); assert.equal(nodes[0].textContent, "Service Requests 15/58"); assert.equal(nodes[0].attributes["data-state"], "running"); assert.equal(nodes[0].className, "opportunities-extension-products-progress"); update("sr", "complete", 58, 58); assert.equal([...timers.values()][0].delay, 5000); [...timers.values()][0].fn(); assert.equal(nodes[0].attributes["data-fading"], "true"); const hide = [...timers.values()].find(timer => timer.delay === 300); hide.fn(); assert.equal(nodes[0].hidden, true); update("sr", "running", 0); assert.equal(nodes[0].hidden, false); assert.equal(timers.size, 0); update("actions", "complete-with-errors", 2, 3); assert.equal(nodes[1].hidden, false); assert.equal(timers.size, 0); update("sr", "error"); assert.equal(nodes[0].textContent, "Service Requests unavailable"); update("session", "running"); assert.equal(nodes[2].textContent, "SR session loading"); assert.equal(nodes[2].attributes["aria-busy"], "true"); update("session", "complete"); assert.equal(nodes[2].textContent, "SR session ready"); assert.equal(nodes[2].attributes["aria-busy"], "false"); assert.equal([...timers.values()].at(-1).delay, 5000); }); test("CRM reports real page counts and Actions reports queued and completed requests", async () => { const app = loadApplication("content"); const progress = []; app.activityProgress.update = (...args) => progress.push(args); app.currentUser.allowsCrmActivities = () => true; app.opportunitiesQueryState.periodRequestVersion = 1; app.fusionCrmServiceRequests.requestCrmServiceRequest = async (_token, _version, _active, onProgress) => { onProgress(15, 20); onProgress(20, 20); return Array.from({ length: 20 }, () => ({})); }; await app.opportunityCrmServiceRequests.load("synthetic", 1); assert.deepEqual(progress, [["sr", "running", 0], ["sr", "running", 15, 20], ["sr", "running", 20, 20], ["sr", "complete", 20, 20]]); app.productsContentJobs.queueProductsTableRender = () => {}; app.debugContentLog.appendBackgroundRequestLog = () => {}; app.authContentSession.requestConsumerTokenServiceOnce = async () => ({}); const pending = []; app.platformContentMessages.sendRuntimeMessage = () => new Promise(resolve => pending.push(resolve)); app.opportunityWorkloadActions.loadProducts({ items: [{ RevnId: "1" }, { RevnId: "2" }] }); await new Promise(resolve => setImmediate(resolve)); assert.deepEqual(progress.at(-1), ["actions", "running", 0, 2]); pending[0]({ ok: true, actions: [], hasNext: false }); await new Promise(resolve => setImmediate(resolve)); assert.deepEqual(progress.at(-1), ["actions", "running", 1, 2]); for (let attempt = 1; attempt <= 5; attempt += 1) { pending[attempt]({ ok: false, error: "Synthetic failure" }); await new Promise(resolve => setImmediate(resolve)); } assert.deepEqual(progress.at(-1), ["actions", "complete-with-errors", 2, 2]); });