Repositioned the opportunity and customer control buttons
This commit is contained in:
13
README.md
13
README.md
@@ -31,11 +31,22 @@ Do not edit files under `dist` directly. Both targets use Manifest V3, with a
|
||||
Chromium service worker and Firefox background scripts. The build composes classic
|
||||
factory modules from `scripts/sources.cjs` into one isolated IIFE per execution
|
||||
context. It serializes `src/content/styles/redwood.css` followed by the scoped
|
||||
`src/features/debug/styles.css` at build time. There is no
|
||||
`src/features/debug/styles.css` and `src/features/opportunities/styles.css` at
|
||||
build time. There is no
|
||||
runtime module loader, browser-side eval, ES-module requirement or new dependency.
|
||||
|
||||
## Tests and Maintenance
|
||||
|
||||
The opportunities table places product counts below Name and the customer
|
||||
consumption action below Customer, without a separate Actions column. Compact
|
||||
columns retain their values; Name and Customer share the remaining width while
|
||||
Owner is fixed at 132px. Both text cells reserve at least two text lines, and
|
||||
their controls stay anchored 10px above the row bottom, including while products
|
||||
load. Longer text can grow the row without overlapping controls. Horizontal
|
||||
scrolling is reserved for table containers below
|
||||
1120px. The scoped table stylesheet does not change product-table column widths
|
||||
or the existing light/dark colors.
|
||||
|
||||
Product HTTP requests appear in Debug Requests (Ctrl+D) as soon as they start.
|
||||
Each entry identifies the opportunity and page offset, stays `running` while
|
||||
waiting for headers or the response body, and updates with HTTP status, response,
|
||||
|
||||
45
dist/chromium/content.js
vendored
45
dist/chromium/content.js
vendored
File diff suppressed because one or more lines are too long
45
dist/firefox/content.js
vendored
45
dist/firefox/content.js
vendored
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ function composeScript(target, { bootstrap = true } = {}) {
|
||||
return `// Source: ${file}\n${source}`;
|
||||
});
|
||||
if (target === "content") {
|
||||
const css = ["src/content/styles/redwood.css", "src/features/debug/styles.css"]
|
||||
const css = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css"]
|
||||
.map(file => fs.readFileSync(path.join(root, file), "utf8")).join("\n");
|
||||
parts.unshift(`const redwoodStyles = ${JSON.stringify(css)};`);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ function createOpportunitiesColumns({ formatOracleResponseDate, formatRevenue, f
|
||||
{ key: "status", label: "Status", value: (item) => item.StatusCode, display: (item) => item.StatusCode },
|
||||
{ key: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability },
|
||||
{ key: "stage", label: "Stage", value: (item) => item.ForecastGroup_c, display: (item) => item.ForecastGroup_c },
|
||||
{ key: "lastUpdateDate", label: "Last Update Date", value: (item) => item.LastUpdateDate, display: (item) => formatOracleResponseDate(item.LastUpdateDate, true) },
|
||||
{ key: "actions", label: "Actions", value: () => "", display: () => "", sortable: false }
|
||||
{ key: "lastUpdateDate", label: "Last Update Date", value: (item) => item.LastUpdateDate, display: (item) => formatOracleResponseDate(item.LastUpdateDate, true) }
|
||||
];
|
||||
}
|
||||
|
||||
113
src/features/opportunities/styles.css
Normal file
113
src/features/opportunities/styles.css
Normal file
@@ -0,0 +1,113 @@
|
||||
/* Keep layout rules on the outer table, never on expanded product tables. */
|
||||
.opportunities-extension-table[data-opportunities-layout="inline-actions"] {
|
||||
width: 100%;
|
||||
min-width: 1120px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* Fixed table layout shares leftover width only between the two auto columns. */
|
||||
.opportunities-extension-table > thead > tr > th[data-column="name"] { width: auto; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="customer"] { width: auto; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="owner"] { width: 132px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="optyNumber"] { width: 106px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="revenue"] { width: 124px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="closeDate"] { width: 100px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="status"] { width: 114px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="winProbability"] { width: 110px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="stage"] { width: 94px; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="lastUpdateDate"] { width: 136px; }
|
||||
|
||||
.opportunities-extension-table > tbody > tr > td[data-column] {
|
||||
padding: 10px 8px;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: normal;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="name"],
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="customer"] {
|
||||
position: relative;
|
||||
padding-right: 16px;
|
||||
padding-left: 12px;
|
||||
padding-bottom: 38px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="owner"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="closeDate"],
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="lastUpdateDate"],
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="status"],
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="stage"],
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="winProbability"] {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-content {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
min-height: 2.7em;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-content > :first-child {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-actions {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 16px;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-actions .opportunities-extension-action-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-actions .opportunities-extension-action-button svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-actions button:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > thead > tr > th[data-column] .opportunities-extension-sort-button {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 12px;
|
||||
gap: 6px;
|
||||
padding: 10px 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > thead > tr > th[data-column="name"] .opportunities-extension-sort-button,
|
||||
.opportunities-extension-table > thead > tr > th[data-column="customer"] .opportunities-extension-sort-button {
|
||||
padding-left: 12px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.opportunities-extension-sort-label {
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.opportunities-extension-table > thead > tr > th[data-column] .opportunities-extension-sort-button::after {
|
||||
position: static;
|
||||
justify-self: end;
|
||||
}
|
||||
@@ -48,6 +48,7 @@ function createOpportunitiesTable({
|
||||
}
|
||||
|
||||
table.textContent = "";
|
||||
table.setAttribute("data-opportunities-layout", "inline-actions");
|
||||
table.setAttribute("aria-busy", opportunitiesTableState.opportunitiesTableState.status === "loading" ? "true" : "false");
|
||||
|
||||
const caption = document.createElement("caption");
|
||||
@@ -61,6 +62,7 @@ function createOpportunitiesTable({
|
||||
const header = document.createElement("th");
|
||||
const isSorted = opportunitiesTableState.opportunitiesTableState.sortKey === column.key;
|
||||
header.scope = "col";
|
||||
header.setAttribute("data-column", column.key);
|
||||
|
||||
if (column.sortable === false) {
|
||||
const label = document.createElement("span");
|
||||
@@ -75,7 +77,10 @@ function createOpportunitiesTable({
|
||||
sortButton.className = "opportunities-extension-sort-button";
|
||||
sortButton.setAttribute("data-sort-direction", isSorted ? opportunitiesTableState.opportunitiesTableState.sortDirection : "none");
|
||||
sortButton.setAttribute("aria-label", `Sort by ${column.label}${isSorted ? `, ${opportunitiesTableState.opportunitiesTableState.sortDirection}` : ""}`);
|
||||
sortButton.textContent = column.label;
|
||||
const sortLabel = document.createElement("span");
|
||||
sortLabel.className = "opportunities-extension-sort-label";
|
||||
sortLabel.textContent = column.label;
|
||||
sortButton.append(sortLabel);
|
||||
sortButton.addEventListener("click", () => sortOpportunitiesBy(column.key));
|
||||
header.append(sortButton);
|
||||
}
|
||||
@@ -103,32 +108,26 @@ function createOpportunitiesTable({
|
||||
|
||||
OPPORTUNITIES_COLUMNS.forEach((column) => {
|
||||
const cell = document.createElement("td");
|
||||
cell.setAttribute("data-column", column.key);
|
||||
|
||||
if (column.key === "stage") {
|
||||
cell.append(createStageBadge(column.display(item)));
|
||||
} else if (column.key === "status") {
|
||||
cell.append(createStatusBadge(column.display(item)));
|
||||
} else if (column.key === "actions") {
|
||||
const actionsContent = document.createElement("span");
|
||||
actionsContent.className = "opportunities-extension-actions-content";
|
||||
const productsResult = productsContentState.opptyProductsByNumber.get(String(item.OptyNumber));
|
||||
|
||||
if (productsResult) {
|
||||
actionsContent.append(createProductsCountButton(item.OptyNumber, productsResult));
|
||||
}
|
||||
|
||||
actionsContent.append(createCloudConsumptionButton(item));
|
||||
cell.append(actionsContent);
|
||||
} else if (column.key === "name" || column.key === "optyNumber") {
|
||||
const opportunityLink = createDetailLink(
|
||||
column.display(item),
|
||||
item.OptyNumber ? `${OPPORTUNITY_DETAIL_URL}${encodeURIComponent(item.OptyNumber)}` : ""
|
||||
);
|
||||
|
||||
if (column.key === "name" && item.OptyNumber) {
|
||||
if (column.key === "name") {
|
||||
const nameCellContent = document.createElement("span");
|
||||
nameCellContent.className = "opportunities-extension-name-content";
|
||||
nameCellContent.append(opportunityLink);
|
||||
nameCellContent.className = "opportunities-extension-cell-content";
|
||||
const productActions = document.createElement("span");
|
||||
productActions.className = "opportunities-extension-cell-actions";
|
||||
const productsResult = item.OptyNumber && productsContentState.opptyProductsByNumber.get(String(item.OptyNumber));
|
||||
if (productsResult) productActions.append(createProductsCountButton(item.OptyNumber, productsResult));
|
||||
nameCellContent.append(opportunityLink, productActions);
|
||||
cell.append(nameCellContent);
|
||||
} else if (column.key === "optyNumber" && item.OptyNumber) {
|
||||
const opportunityCellContent = document.createElement("span");
|
||||
@@ -139,12 +138,19 @@ function createOpportunitiesTable({
|
||||
cell.append(opportunityLink);
|
||||
}
|
||||
} else if (column.key === "customer") {
|
||||
cell.append(createDetailLink(
|
||||
const customerCellContent = document.createElement("span");
|
||||
customerCellContent.className = "opportunities-extension-cell-content";
|
||||
const customerLink = createDetailLink(
|
||||
column.display(item),
|
||||
getNestedValue(item, ["CustomerAccount", "PartyId"])
|
||||
? `${ACCOUNT_DETAIL_URL}${encodeURIComponent(getNestedValue(item, ["CustomerAccount", "PartyId"]))}`
|
||||
: ""
|
||||
));
|
||||
);
|
||||
const customerActions = document.createElement("span");
|
||||
customerActions.className = "opportunities-extension-cell-actions";
|
||||
customerActions.append(createCloudConsumptionButton(item));
|
||||
customerCellContent.append(customerLink, customerActions);
|
||||
cell.append(customerCellContent);
|
||||
} else {
|
||||
cell.textContent = displayOpportunityValue(column, item);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,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"]
|
||||
const css = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.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"));
|
||||
|
||||
158
tests/opportunityTable.test.cjs
Normal file
158
tests/opportunityTable.test.cjs
Normal file
@@ -0,0 +1,158 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { loadApplication, composeScript } = require("./support/application.cjs");
|
||||
|
||||
class Element {
|
||||
constructor(tag) {
|
||||
this.tag = tag;
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.attributes = {};
|
||||
this.listeners = {};
|
||||
this.classes = new Set();
|
||||
this.classList = { toggle: (name, on) => on ? this.classes.add(name) : this.classes.delete(name) };
|
||||
this.text = "";
|
||||
}
|
||||
set textContent(value) { this.text = value; this.children = []; }
|
||||
get textContent() { return this.text; }
|
||||
append(...nodes) { this.children.push(...nodes); }
|
||||
setAttribute(key, value) { this.attributes[key] = value; }
|
||||
addEventListener(key, handler) { this.listeners[key] = handler; }
|
||||
click() { if (!this.disabled) this.listeners.click({ preventDefault() {}, stopPropagation() {} }); }
|
||||
}
|
||||
const walk = node => [node, ...node.children.flatMap(walk)];
|
||||
const hasClass = (node, name) => (node.className || "").split(" ").includes(name);
|
||||
const findClass = (node, name) => walk(node).find(child => hasClass(child, name));
|
||||
const synthetic = (number = "TEST-1", name = "Synthetic opportunity") => ({
|
||||
Name: name, OptyNumber: number, CustomerAccount: { PartyUniqueName: "Synthetic customer", PartyId: "party/id", PartyNumber: "reg-123" },
|
||||
Owner: { PersonFirstName: "Test", PersonLastName: "Owner" }, PrimaryRevenue: { RevnAmount: "123456.78", RevnAmountCurcyCode: "USD", WinProb: 60 },
|
||||
EffectiveDate: "2026-10-23", LastUpdateDate: "2026-09-22T13:15:00Z", StatusCode: "OPEN", ForecastGroup_c: "FORECAST"
|
||||
});
|
||||
|
||||
function setup(items = [synthetic()]) {
|
||||
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 summaries = [];
|
||||
app.aiSummaryContent.requestGenerateAiSummary = number => summaries.push(number);
|
||||
app.opportunitiesTable.setOpportunitiesTableState({ status: "ready", items });
|
||||
const rows = () => table.children.find(child => child.tag === "tbody").children;
|
||||
const cell = (key, index = 0) => rows()[index].children.find(child => child.attributes["data-column"] === key);
|
||||
const headers = () => table.children.find(child => child.tag === "thead").children[0].children;
|
||||
return { app, table, rows, cell, headers, summaries };
|
||||
}
|
||||
|
||||
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" }] });
|
||||
h.app.opportunitiesTable.renderOpportunitiesTable();
|
||||
assert.equal(h.headers().length, 10);
|
||||
assert.equal(h.headers().some(header => header.attributes["data-column"] === "actions"), false);
|
||||
const name = h.cell("name").children[0];
|
||||
const customer = h.cell("customer").children[0];
|
||||
assert.equal(name.children[0].tag, "a");
|
||||
assert.equal(name.children[0].textContent, "Synthetic opportunity");
|
||||
assert.ok(name.children[0].href.endsWith("TEST-1"));
|
||||
assert.equal(name.children[1].children[0].className, "opportunities-extension-products-count");
|
||||
assert.equal(name.children[1].children[0].textContent, "1");
|
||||
assert.equal(customer.children[0].textContent, "Synthetic customer");
|
||||
assert.ok(customer.children[0].href.endsWith("party%2Fid"));
|
||||
assert.equal(customer.children[1].children[0].title, "View customer consumption");
|
||||
assert.equal(findClass(h.cell("optyNumber"), "opportunities-extension-copy-button").title, "Copy opportunity number");
|
||||
assert.equal(findClass(h.cell("name"), "opportunities-extension-action-button"), undefined);
|
||||
assert.equal(findClass(h.cell("customer"), "opportunities-extension-products-count"), undefined);
|
||||
});
|
||||
|
||||
test("products remain expandable with AI summary and a ten-column detail row after sorting", () => {
|
||||
const h = setup([synthetic("B", "Zulu"), synthetic("A", "Alpha")]);
|
||||
h.app.productsContentState.opptyProductsByNumber.set("B", { items: [{ ProdGroupName: "Cloud", RevnAmount: "10" }] });
|
||||
h.app.opportunitiesTable.renderOpportunitiesTable();
|
||||
findClass(h.cell("name"), "opportunities-extension-products-count").click();
|
||||
assert.deepEqual(h.summaries, ["B"]);
|
||||
assert.equal(h.rows()[1].className, "opportunities-extension-products-detail-row");
|
||||
assert.equal(h.rows()[1].children[0].colSpan, 10);
|
||||
assert.ok(findClass(h.rows()[1], "opportunities-extension-products-table"));
|
||||
h.headers()[0].children[0].click();
|
||||
assert.equal(h.cell("name").children[0].children[0].textContent, "Alpha");
|
||||
assert.equal(h.cell("name", 1).children[0].children[0].textContent, "Zulu");
|
||||
assert.equal(h.rows()[2].children[0].colSpan, 10);
|
||||
findClass(h.cell("name", 1), "opportunities-extension-products-count").click();
|
||||
assert.equal(h.rows().length, 2);
|
||||
assert.deepEqual(h.summaries, ["B"], "collapsing does not generate another summary");
|
||||
});
|
||||
|
||||
test("customer consumption still opens immediately, uses the right customer and preserves loading state", async () => {
|
||||
const h = setup();
|
||||
const opens = [];
|
||||
const requests = [];
|
||||
h.app.consumptionPlansView.openAccountPlansModal = (...args) => opens.push(args);
|
||||
h.app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ synthetic: true });
|
||||
h.app.platformContentMessages.sendRuntimeMessage = async message => { requests.push(message); return { ok: true, plans: [] }; };
|
||||
findClass(h.cell("customer"), "opportunities-extension-action-button").click();
|
||||
assert.equal(opens[0][2], "loading");
|
||||
assert.equal(findClass(h.cell("customer"), "opportunities-extension-action-button").disabled, true);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(requests[0].partyNumber, "reg-123");
|
||||
assert.equal(requests[0].type, "opportunitiesExtension.requestAccountPlans");
|
||||
assert.equal(opens.at(-1)[2], "ready");
|
||||
assert.equal(findClass(h.cell("customer"), "opportunities-extension-action-button").disabled, false);
|
||||
});
|
||||
|
||||
test("pending, zero-count, failed and missing-data rows preserve their layout and full values", () => {
|
||||
const h = setup([{ ...synthetic(), StatusCode: "WON_PENDING" }]);
|
||||
const actions = () => h.cell("name").children[0].children[1];
|
||||
assert.equal(actions().children.length, 0, "space remains reserved while products load");
|
||||
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [], error: "Synthetic failure" });
|
||||
h.app.opportunitiesTable.renderOpportunitiesTable();
|
||||
assert.equal(actions().children[0].textContent, "0");
|
||||
assert.equal(actions().children[0].title, "Synthetic failure");
|
||||
assert.equal(h.cell("status").children[0].textContent, "WON_PENDING");
|
||||
assert.equal(h.cell("revenue").textContent, "$123,456.78");
|
||||
assert.ok(h.cell("lastUpdateDate").textContent.includes("2026"));
|
||||
const missing = { Name: "No identifiers", StatusCode: "OPEN", ForecastGroup_c: "FORECAST" };
|
||||
h.app.opportunitiesFilters.getSortedOpportunities = () => [missing];
|
||||
h.app.opportunitiesTable.setOpportunitiesTableState({ status: "ready", items: [missing] });
|
||||
assert.equal(findClass(h.cell("name"), "opportunities-extension-products-count"), undefined);
|
||||
assert.equal(h.cell("customer").children[0].children[0].textContent, "-");
|
||||
});
|
||||
|
||||
test("loading, empty and error messages span exactly the visible columns", () => {
|
||||
const h = setup();
|
||||
for (const status of ["loading", "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);
|
||||
}
|
||||
});
|
||||
|
||||
test("table styles reserve compact widths, prioritize Name/Customer and keep nested tables untouched", () => {
|
||||
const read = file => fs.readFileSync(path.join(__dirname, "..", file), "utf8");
|
||||
const css = read("src/features/opportunities/styles.css");
|
||||
const fixed = [...css.matchAll(/th\[data-column="\w+"\] \{ width: (\d+)px;/g)].reduce((sum, match) => sum + Number(match[1]), 0);
|
||||
assert.equal(fixed, 916);
|
||||
const flexible = [...css.matchAll(/th\[data-column="(\w+)"\] \{ width: auto;/g)].map(match => match[1]);
|
||||
assert.deepEqual(flexible, ["name", "customer"]);
|
||||
assert.match(css, /th\[data-column="owner"\] \{ width: 132px;/);
|
||||
assert.doesNotMatch(css, /width: (?:calc|clamp)\(/);
|
||||
assert.match(css, /min-width: 1120px/);
|
||||
assert.match(css, /min-height: 2\.7em/);
|
||||
assert.match(css, /padding-bottom: 38px/);
|
||||
const actions = css.match(/\.opportunities-extension-cell-actions \{([^}]+)\}/)[1];
|
||||
assert.match(actions, /position: absolute/);
|
||||
assert.match(actions, /bottom: 10px/);
|
||||
assert.match(actions, /height: 24px/);
|
||||
assert.match(css, /td\[data-column="customer"\] \{\s*position: relative;/);
|
||||
assert.match(css, /grid-template-columns: minmax\(0, 1fr\) 12px/);
|
||||
assert.doesNotMatch(css, /text-overflow: ellipsis|overflow: hidden|th:nth-child|td:nth-child/);
|
||||
for (const width of [1120, 1280, 1440, 1840]) {
|
||||
const remaining = width - fixed;
|
||||
assert.equal(fixed + remaining / 2 + remaining / 2, width);
|
||||
assert.ok(remaining > 0);
|
||||
}
|
||||
const allCss = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css"].map(read).join("\n");
|
||||
assert.ok(composeScript("content").includes(`const redwoodStyles = ${JSON.stringify(allCss)};`));
|
||||
});
|
||||
Reference in New Issue
Block a user