Table sorting preferences will be saved and reused based on the user’s selections
This commit is contained in:
@@ -31,11 +31,11 @@ const synthetic = (number = "TEST-1", name = "Synthetic opportunity") => ({
|
||||
EffectiveDate: "2026-10-23", LastUpdateDate: "2026-09-22T13:15:00Z", StatusCode: "OPEN", ForecastGroup_c: "FORECAST"
|
||||
});
|
||||
|
||||
function setup(items = [synthetic()]) {
|
||||
function setup(items = [synthetic()], localStorage) {
|
||||
const table = new Element("table");
|
||||
const document = { createElement: tag => new Element(tag), createElementNS: (_, tag) => new Element(tag),
|
||||
getElementById: id => id === app.contracts.OPPORTUNITIES_TABLE_ID ? table : null };
|
||||
const app = loadApplication("content", { document, Intl, Date });
|
||||
const app = loadApplication("content", { document, Intl, Date, localStorage });
|
||||
app.currentUser.allowsCrmActivities = () => true;
|
||||
const summaries = [];
|
||||
app.aiSummaryContent.requestGenerateAiSummary = number => summaries.push(number);
|
||||
@@ -46,6 +46,22 @@ function setup(items = [synthetic()]) {
|
||||
return { app, table, rows, cell, headers, summaries };
|
||||
}
|
||||
|
||||
test("Overview restores independent column and direction preferences across reloads and new data", () => {
|
||||
const saved = new Map();
|
||||
const storage = { getItem: key => saved.get(key), setItem: (key, value) => saved.set(key, value) };
|
||||
const items = [synthetic("B", "Beta"), synthetic("A", "Alpha")];
|
||||
const first = setup(items, storage);
|
||||
first.app.opportunitiesTable.sortOpportunitiesBy("name");
|
||||
first.app.opportunitiesTable.sortOpportunitiesBy("name");
|
||||
const second = setup(items.slice().reverse(), storage);
|
||||
assert.equal(second.app.opportunitiesTableState.opportunitiesTableState.sortKey, "name");
|
||||
assert.equal(second.app.opportunitiesTableState.opportunitiesTableState.sortDirection, "descending");
|
||||
assert.equal(second.cell("name").children[0].children[0].textContent, "Beta");
|
||||
second.app.opportunitiesTable.setOpportunitiesTableState({ status: "loading" });
|
||||
second.app.opportunitiesTable.setOpportunitiesTableState({ status: "ready", items });
|
||||
assert.equal(second.cell("name").children[0].children[0].textContent, "Beta");
|
||||
});
|
||||
|
||||
test("ten columns retain their links and put product/customer controls below the corresponding text", () => {
|
||||
const h = setup();
|
||||
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ ProdGroupName: "Cloud" }] });
|
||||
@@ -83,8 +99,8 @@ test("ramp months preserve zero and flag missing values beside the product count
|
||||
const badge = () => findClass(h.cell("name"), "opportunities-extension-products-ramp");
|
||||
assert.equal(badge(), undefined);
|
||||
for (const [values, expected] of [
|
||||
[[], null], [[null], "N/D"], [[null, undefined, "", " "], "N/D"], [[6], "6"],
|
||||
[[0], "0"], [[0, "3", null, " 6 ", 3], "0 | 3 | N/D | 6 | 3"]
|
||||
[[], null], [[null], "N/A"], [[null, undefined, "", " "], "N/A"], [[6], "6"],
|
||||
[[0], "0"], [[0, "3", null, " 6 ", 3], "0 | 3 | N/A | 6 | 3"]
|
||||
]) {
|
||||
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", {
|
||||
fromCache: true, items: values.map(RampMonths_c => ({ RampMonths_c }))
|
||||
@@ -93,7 +109,7 @@ test("ramp months preserve zero and flag missing values beside the product count
|
||||
if (expected === null) assert.equal(badge(), undefined);
|
||||
else {
|
||||
assert.equal(badge().textContent, expected);
|
||||
assert.equal(badge().attributes["data-missing"], String(expected.includes("N/D")));
|
||||
assert.equal(badge().attributes["data-missing"], String(expected.includes("N/A")));
|
||||
assert.equal(badge().title, `Ramp Months: ${expected.split(" | ").join(", ")}`);
|
||||
assert.equal(badge().attributes["aria-label"], badge().title);
|
||||
assert.equal(h.cell("name").children[0].children[1].children[1], badge());
|
||||
@@ -200,7 +216,7 @@ test("product table distinguishes unavailable ramp months from numeric zero", ()
|
||||
findClass(h.cell("name"), "opportunities-extension-products-count").click();
|
||||
const table = findClass(h.rows()[1], "opportunities-extension-products-table");
|
||||
const rows = table.children.find(child => child.tag === "tbody").children;
|
||||
assert.deepEqual(rows.map(row => row.children[9].textContent), ["N/D", "N/D", "N/D", "N/D", "0", "0", "6"]);
|
||||
assert.deepEqual(rows.map(row => row.children[9].textContent), ["N/A", "N/A", "N/A", "N/A", "0", "0", "6"]);
|
||||
});
|
||||
|
||||
test("customer consumption still opens immediately, uses the right customer and preserves loading state", async () => {
|
||||
@@ -239,9 +255,31 @@ test("pending, zero-count, failed and missing-data rows preserve their layout an
|
||||
assert.equal(h.cell("customer").children[0].children[0].textContent, "-");
|
||||
});
|
||||
|
||||
test("loading, empty and error messages span exactly the visible columns", () => {
|
||||
test("loading skeleton preserves columns and action slots, then disappears on success or failure", () => {
|
||||
const h = setup();
|
||||
for (const status of ["loading", "empty", "error"]) {
|
||||
for (const nextStatus of ["ready", "empty", "error"]) {
|
||||
h.app.opportunitiesTable.setOpportunitiesTableState({ status: "loading", items: [], message: "Loading opportunities..." });
|
||||
assert.equal(h.table.attributes["aria-busy"], "true");
|
||||
assert.equal(h.rows().length, 8);
|
||||
assert.equal(h.headers().length, 10);
|
||||
assert.ok(h.headers().every(header => header.children[0].disabled));
|
||||
for (const row of h.rows()) {
|
||||
assert.equal(row.attributes["aria-hidden"], "true");
|
||||
assert.deepEqual(row.children.map(cell => cell.attributes["data-column"]), h.headers().map(header => header.attributes["data-column"]));
|
||||
assert.ok(row.children.every(cell => findClass(cell, "opportunities-extension-account-plans-skeleton-bar")));
|
||||
assert.equal(walk(row).filter(node => hasClass(node, "opportunities-extension-table-skeleton-badge")).length, 7);
|
||||
assert.equal(walk(row).some(node => node.tag === "button" || node.tag === "a"), false);
|
||||
}
|
||||
h.app.opportunitiesTable.setOpportunitiesTableState({ status: nextStatus, items: nextStatus === "ready" ? [synthetic()] : [], message: "" });
|
||||
assert.equal(h.table.attributes["aria-busy"], "false");
|
||||
assert.equal(findClass(h.table, "opportunities-extension-table-skeleton-row"), undefined);
|
||||
assert.ok(h.headers().every(header => !header.children[0].disabled));
|
||||
}
|
||||
});
|
||||
|
||||
test("empty and error messages span exactly the visible columns", () => {
|
||||
const h = setup();
|
||||
for (const status of ["empty", "error"]) {
|
||||
h.app.opportunitiesTable.setOpportunitiesTableState({ status, message: "Synthetic state" });
|
||||
assert.equal(h.rows().length, 1);
|
||||
assert.equal(h.rows()[0].children[0].colSpan, 10);
|
||||
|
||||
Reference in New Issue
Block a user