139 lines
6.7 KiB
JavaScript
139 lines
6.7 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const path = require("node:path");
|
|
const vm = require("node:vm");
|
|
const { loadApplication, composeScript } = require("./application.cjs");
|
|
|
|
const root = path.resolve(__dirname, "../..");
|
|
const plain = (value) => JSON.parse(JSON.stringify(value));
|
|
const response = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
status, headers: { "Content-Type": "application/json" }
|
|
});
|
|
const unexpectedFetch = async () => { throw new Error("Unexpected network request in isolated test"); };
|
|
|
|
function clock() {
|
|
let now = Date.UTC(2026, 8, 16, 12);
|
|
class ClockDate extends Date {
|
|
constructor(...args) { super(...(args.length ? args : [now])); }
|
|
static now() { return now; }
|
|
}
|
|
return { Date: ClockDate, advance: (ms) => { now += ms; } };
|
|
}
|
|
|
|
function loadContent({ fetch = unexpectedFetch, storage = new Map(), xsrf = null } = {}) {
|
|
const refreshes = [];
|
|
const context = {
|
|
fetch, testXsrf: xsrf, refreshes,
|
|
document: { querySelector: () => ({ value: "Current Quarter" }) },
|
|
localStorage: {
|
|
getItem: key => storage.get(key) ?? null,
|
|
setItem: (key, value) => storage.set(key, value)
|
|
},
|
|
Date, Intl, URL
|
|
};
|
|
const app = loadApplication("content", context);
|
|
app.debugContentView.renderDebugPanel = () => {};
|
|
if (xsrf) app.authXsrfContent.getXsrfToken = xsrf;
|
|
app.platformContentMessages.refreshXsrfCookie = async () => { refreshes.push(true); return { refreshed: true }; };
|
|
return {
|
|
...app.opportunitiesFiscalPeriods, ...app.fusionOpportunities, ...app.authTokenRelay,
|
|
...app.authSalesCommon, ...app.preferencesStorage, ...app.opportunitiesFilters,
|
|
...app.opportunitiesFormatters, ...app.contentTheme, ...app.debugState,
|
|
setVersion: value => { app.opportunitiesQueryState.periodRequestVersion = value; },
|
|
setTypes: values => { app.preferencesState.selectedOpportunityTypeValues = new Set(values); },
|
|
setTable: (items, sortKey = "", sortDirection = "ascending") => {
|
|
app.opportunitiesTableState.opportunitiesTableState = { items, sortKey, sortDirection };
|
|
app.opportunitiesFilters.resetCustomerFilter(items);
|
|
app.opportunitiesFilters.resetOwnerFilter(items);
|
|
},
|
|
filters: () => {
|
|
const state = app.opportunitiesFilterState;
|
|
return {
|
|
stages: Array.from(state.selectedStages), statuses: Array.from(state.selectedStatuses),
|
|
customerKeys: Array.from(state.selectedCustomerKeys), ownerKeys: Array.from(state.selectedOwnerKeys),
|
|
customerSearch: state.customerSearch, ownerSearch: state.ownerSearch, tableSearch: state.tableSearch
|
|
};
|
|
},
|
|
storage, refreshes
|
|
};
|
|
}
|
|
|
|
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, cookies = () => [], storageError = false } = {}) {
|
|
const time = clock();
|
|
const listeners = [];
|
|
const progress = [];
|
|
const requestDebug = [];
|
|
const tabs = [];
|
|
const removedTabs = [];
|
|
const cookieQueries = [];
|
|
const api = { runtime: { onMessage: { addListener: fn => listeners.push(fn) } } };
|
|
const readCookies = details => { cookieQueries.push(plain(details)); return cookies(details); };
|
|
const getStored = key => ({ [key]: storage[key] === undefined ? undefined : plain(storage[key]) });
|
|
const store = value => Object.assign(storage, plain(value));
|
|
const openTab = details => { tabs.push(plain(details)); return { id: 91 }; };
|
|
const send = (tabId, message, options) => {
|
|
const events = message.type === "opportunitiesExtension.opptyProductsRequestDebug" ? requestDebug : progress;
|
|
events.push({ tabId, message: plain(message), options: plain(options || {}) });
|
|
};
|
|
if (family === "firefox") {
|
|
api.runtime.getBrowserInfo = async () => ({ name: "Firefox" });
|
|
api.storage = { local: {
|
|
get: async key => { if (storageError) throw new Error("Storage unavailable"); return getStored(key); },
|
|
set: async value => { if (storageError) throw new Error("Storage unavailable"); store(value); }
|
|
} };
|
|
api.tabs = { create: async details => openTab(details), remove: async id => removedTabs.push(id),
|
|
sendMessage: async (tabId, message, options) => send(tabId, message, options) };
|
|
api.cookies = {
|
|
get: async details => readCookies(details).find(cookie => cookie.name === details.name) || null,
|
|
getAll: async details => readCookies(details),
|
|
getAllCookieStores: async () => [{ id: "test-store" }]
|
|
};
|
|
} else {
|
|
const withStorageError = callback => {
|
|
api.runtime.lastError = storageError ? { message: "Storage unavailable" } : undefined;
|
|
try { callback(); } finally { delete api.runtime.lastError; }
|
|
};
|
|
api.storage = { local: {
|
|
get(key, callback) { withStorageError(() => callback(getStored(key))); },
|
|
set(value, callback) { withStorageError(() => { if (!storageError) store(value); callback(); }); }
|
|
} };
|
|
api.tabs = {
|
|
create(details, callback) { callback(openTab(details)); },
|
|
remove(id, callback) { removedTabs.push(id); callback(); },
|
|
sendMessage(tabId, message, options, callback) {
|
|
if (typeof options === "function") { callback = options; options = undefined; }
|
|
send(tabId, message, options); callback();
|
|
}
|
|
};
|
|
api.cookies = {
|
|
get(details, callback) { callback(readCookies(details).find(cookie => cookie.name === details.name) || null); },
|
|
getAll(details, callback) { callback(readCookies(details)); },
|
|
getAllCookieStores(callback) { callback([{ id: "test-store" }]); }
|
|
};
|
|
}
|
|
const context = {
|
|
[family === "firefox" ? "browser" : "chrome"]: api,
|
|
fetch, URL, Date: time.Date,
|
|
setTimeout: (callback, ms) => { time.advance(ms); return setTimeout(callback, 0); }
|
|
};
|
|
new vm.Script(composeScript("background"), { filename: "background.js" })
|
|
.runInNewContext(context);
|
|
assert.equal(listeners.length, 1, "Background must register one listener synchronously");
|
|
const sender = { id: "synthetic-extension", url: "https://eeho.fa.us2.oraclecloud.com/fscmUI/faces/FuseWelcome",
|
|
tab: { id: 7, windowId: 3, cookieStoreId: "test-store" }, frameId: 2 };
|
|
function dispatch(message) {
|
|
return new Promise((resolve, reject) => {
|
|
let replies = 0;
|
|
const timer = setTimeout(() => reject(new Error("Message did not receive a response")), 2000);
|
|
const pending = listeners[0](message, sender, result => {
|
|
clearTimeout(timer);
|
|
assert.equal(++replies, 1, "Message must receive exactly one response");
|
|
resolve(plain(result));
|
|
});
|
|
if (pending !== true) { clearTimeout(timer); reject(new Error("Expected asynchronous response contract")); }
|
|
});
|
|
}
|
|
return { dispatch, listener: listeners[0], progress, requestDebug, tabs, removedTabs, cookieQueries, storage, advance: time.advance };
|
|
}
|
|
|
|
module.exports = { root, plain, response, loadContent, loadBackground };
|