Updated the calendar to improve opportunity display and navigation
This commit is contained in:
@@ -25,9 +25,10 @@ function setup(items = [opportunity("A", "2026-11-15")]) {
|
||||
const host = new Element("host");
|
||||
document = { createElement: tag => new Element(tag), getElementById: () => host };
|
||||
const state = { status: "ready", items };
|
||||
const h = { state, filtered: items, version: 1, instances: [], loads: 0, host, document };
|
||||
const h = { state, filtered: items, version: 1, range: { startCloseDate: "2026-09-01", endCloseDate: "2027-03-01" }, instances: [], loads: 0, host, document };
|
||||
class Calendar {
|
||||
constructor(element, options) { this.element = element; this.options = options; this.events = options.events; this.dates = []; h.instances.push(this); }
|
||||
setOption(key, value) { this.options[key] = value; }
|
||||
render() { this.rendered = true; }
|
||||
destroy() { this.destroyed = true; }
|
||||
updateSize() { this.resized = true; }
|
||||
@@ -37,7 +38,7 @@ function setup(items = [opportunity("A", "2026-11-15")]) {
|
||||
addEventSource(events) { this.events = events; }
|
||||
}
|
||||
h.api = factory({ environment: { document }, loadLibrary: () => { h.loads++; return { Calendar }; },
|
||||
getState: () => state, getItems: () => h.filtered, getVersion: () => h.version,
|
||||
getState: () => state, getItems: () => h.filtered, getVersion: () => h.version, getRange: () => h.range,
|
||||
formatRevenue: () => "$100", formatDate: value => value.slice(0, 10), getOwnerName: () => "Owner",
|
||||
createDetailLink: (text, href) => { const a = new Element("a"); a.textContent = text; a.href = href; return a; },
|
||||
createCopyButton: () => new Element("button"), createCloseIcon: () => new Element("svg"),
|
||||
@@ -57,10 +58,6 @@ test("calendar controls, accessibility labels and missing dates are English", ()
|
||||
const h = setup([opportunity("A", "2026-11-15"), opportunity("B", null)]);
|
||||
const { options } = h.activate();
|
||||
assert.equal(options.locale, "en");
|
||||
assert.deepEqual(plain(options.buttonText), { today: "Today", month: "Month", week: "Week" });
|
||||
assert.deepEqual(plain(options.buttonHints), { prev: "Previous", next: "Next", today: "Today", dayGridMonth: "Month", dayGridWeek: "Week" });
|
||||
assert.equal(options.moreLinkHint(1), "View 1 more opportunity");
|
||||
assert.equal(options.moreLinkHint(3), "View 3 more opportunities");
|
||||
assert.ok(h.nodes().some(node => node.attributes["aria-label"] === "Opportunity views"));
|
||||
assert.ok(h.nodes().some(node => node.textContent === "No valid Close Date (1)"));
|
||||
});
|
||||
@@ -126,7 +123,7 @@ test("initial calendar date selects the next close date, last past date or today
|
||||
assert.equal(api.initialDate([opportunity("A", null)], now), "2026-10-05");
|
||||
});
|
||||
|
||||
test("overview is the default; tabs lazily initialize and reuse a monthly/weekly calendar", () => {
|
||||
test("overview is the default; tabs lazily initialize and reuse a scrollable monthly calendar", () => {
|
||||
const h = setup(); h.mount();
|
||||
const surface = h.byClass("opportunities-extension-calendar");
|
||||
assert.equal(h.table.hidden, false); assert.equal(surface.hidden, true); assert.equal(h.loads, 0);
|
||||
@@ -137,13 +134,13 @@ test("overview is the default; tabs lazily initialize and reuse a monthly/weekly
|
||||
tabs[1].click();
|
||||
const instance = h.instances[0];
|
||||
assert.equal(h.table.hidden, true); assert.equal(surface.hidden, false);
|
||||
assert.equal(instance.options.initialView, "dayGridMonth");
|
||||
assert.equal(instance.options.initialView, "referenceMonths");
|
||||
assert.equal(instance.options.height, "100%");
|
||||
assert.equal(instance.options.expandRows, true);
|
||||
assert.equal(instance.options.headerToolbar.right, "dayGridMonth,dayGridWeek");
|
||||
|
||||
assert.equal(instance.options.editable, false);
|
||||
assert.equal(instance.options.dayMaxEvents, 2);
|
||||
assert.equal(instance.options.moreLinkText(7), "... +7");
|
||||
assert.equal(instance.options.dayMaxEvents, false);
|
||||
assert.equal(instance.options.headerToolbar, false);
|
||||
assert.deepEqual(plain(instance.options.validRange), {start:"2026-09-01",end:"2027-03-01"});
|
||||
assert.equal(instance.options.weekNumbers, true);
|
||||
tabs[0].click(); tabs[1].click();
|
||||
assert.equal(h.loads, 1); assert.equal(h.instances.length, 1); assert.equal(instance.resized, true);
|
||||
@@ -151,24 +148,15 @@ test("overview is the default; tabs lazily initialize and reuse a monthly/weekly
|
||||
assert.equal(h.table.hidden, false); assert.equal(h.document.activeElement, tabs[0]);
|
||||
});
|
||||
|
||||
test("responsive event limit reserves room for every week and the overflow control", () => {
|
||||
const { api } = setup();
|
||||
assert.equal(api.visibleEventLimit(240, 6, 21), 0);
|
||||
assert.equal(api.visibleEventLimit(500, 5, 21), 1);
|
||||
assert.equal(api.visibleEventLimit(800, 5, 21), 2);
|
||||
assert.equal(api.visibleEventLimit(500, 1, 21), 2);
|
||||
assert.equal(api.visibleEventLimit(0, 6, 21), 0);
|
||||
});
|
||||
|
||||
test("calendar follows filtered data; only a new Reference request changes its date", () => {
|
||||
const h = setup([opportunity("A", "2026-11-15"), opportunity("B", "2026-12-12")]);
|
||||
const calendar = h.activate();
|
||||
h.filtered = [h.state.items[1]]; h.api.render(h.filtered, h.state);
|
||||
assert.deepEqual(plain(calendar.events.map(event => event.id)), ["B"]);
|
||||
assert.equal(calendar.dates.length, 0);
|
||||
h.version++; h.state.items = [opportunity("C", "2027-02-20")]; h.filtered = h.state.items;
|
||||
h.range = { startCloseDate: "2026-12-01", endCloseDate: "2027-03-01" }; h.version++; h.state.items = [opportunity("C", "2027-02-20")]; h.filtered = h.state.items;
|
||||
h.api.render(h.filtered, h.state);
|
||||
assert.deepEqual(calendar.dates, ["2027-02-20"]);
|
||||
assert.deepEqual(calendar.dates, ["2026-12-01"]);
|
||||
assert.deepEqual(plain(calendar.events.map(event => event.id)), ["C"]);
|
||||
h.filtered = []; h.api.render(h.filtered, h.state);
|
||||
assert.equal(calendar.events.length, 0);
|
||||
@@ -194,7 +182,7 @@ test("event details close on changed data, loading/error hide stale calendar con
|
||||
assert.ok(h.nodes().some(node => node.attributes.role === "dialog"));
|
||||
h.api.render([], h.state);
|
||||
assert.ok(!h.nodes().some(node => node.attributes.role === "dialog"));
|
||||
for (const status of ["loading", "error", "empty"]) {
|
||||
for (const status of ["loading", "error"]) {
|
||||
h.api.render([], { status, items: [], message: "State message" });
|
||||
assert.equal(h.byClass("opportunities-calendar-grid").hidden, true);
|
||||
assert.equal(h.byClass("opportunities-calendar-undated").hidden, true);
|
||||
@@ -202,29 +190,6 @@ test("event details close on changed data, loading/error hide stale calendar con
|
||||
}
|
||||
});
|
||||
|
||||
test("overflow uses our dialog instead of the FullCalendar offsetParent-dependent popover", () => {
|
||||
const h = setup([opportunity("A", "2026-11-15"), opportunity("B", "2026-11-15"), opportunity("C", "2026-11-15")]);
|
||||
const calendar = h.activate();
|
||||
const trigger = h.document.createElement("a");
|
||||
const allSegs = calendar.events.map(event => ({ event }));
|
||||
const open = () => calendar.options.moreLinkClick({ date: new Date("2026-11-15T00:00:00Z"), allSegs,
|
||||
jsEvent: { currentTarget: trigger, preventDefault() {} } });
|
||||
assert.equal(open(), true, "truthy non-string result suppresses vendor popover");
|
||||
const list = h.byClass("opportunities-calendar-day-list");
|
||||
assert.equal(list.children.length, 3);
|
||||
assert.ok(list.children.every(button => button.children.length === 2 && button.children[1].textContent.includes(" | FORECAST | $100")));
|
||||
list.children[2].click();
|
||||
assert.ok(h.nodes().some(node => node.tag === "h2" && node.textContent === "<Opportunity C>"));
|
||||
h.api.closeDetails();
|
||||
assert.equal(h.document.activeElement, trigger);
|
||||
open(); h.api.selectView("overview");
|
||||
assert.ok(!h.nodes().some(node => node.attributes.role === "dialog"));
|
||||
h.api.selectView("calendar"); open();
|
||||
const dialog = h.nodes().find(node => node.attributes.role === "dialog");
|
||||
dialog.listeners.keydown({ key: "Escape", preventDefault() {}, stopPropagation() {} });
|
||||
assert.ok(!h.nodes().some(node => node.attributes.role === "dialog"));
|
||||
});
|
||||
|
||||
test("calendar events show their stage and handle missing stage values", () => {
|
||||
const h = setup();
|
||||
const calendar = h.activate();
|
||||
@@ -237,3 +202,13 @@ test("calendar events show their stage and handle missing stage values", () => {
|
||||
assert.equal(domNodes[0].children[1].title, `A | ${label} | $100`);
|
||||
}
|
||||
});
|
||||
|
||||
test("Reference boundaries exclude the next period and keep empty months visible", () => {
|
||||
const h = setup([opportunity("before", "2026-08-31"), opportunity("first", "2026-09-01"), opportunity("last", "2026-11-30"), opportunity("next", "2026-12-01")]);
|
||||
h.range = {startCloseDate:"2026-09-01",endCloseDate:"2026-12-01"};
|
||||
const calendar = h.activate();
|
||||
assert.deepEqual(plain(calendar.events.map(e=>e.id)), ["first","last"]);
|
||||
h.api.render([], {status:"empty",items:[]});
|
||||
assert.equal(h.byClass("opportunities-calendar-grid").hidden,false);
|
||||
assert.equal(calendar.events.length,0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user