assessment-model/cypress/e2e/live-tracking/pibi-section.cy.js
Khalim Conn-Kowlessar 2fc87d4dbf updated PIBI ui
2026-05-07 11:26:22 +00:00

286 lines
9.2 KiB
JavaScript

/**
* Live Tracking — PibiSection (flat TanStack Table redesign)
*
* Tests the approver flow for the per-measure PIBI request log rendered
* directly on the DealPage (pibi-surveys tab), not in a drawer.
*
* 1. Empty state renders with a "Log first PIBI" prompt
* 2. Flat table shows existing rows (no batch grouping)
* 3. Every row has always-editable cells (measure select, date inputs)
* 4. Save button is disabled when row is clean, enabled after editing
* 5. Save calls PATCH for existing rows
* 6. Delete calls DELETE
* 7. "+ Add row" appends a blank row; save calls POST
* 8. "Mark all complete" PATCHes all incomplete rows
* 9. Scope badges appear for approved / proposed measures
*
* Requires LIVE_PORTFOLIO_SLUG; skipped otherwise.
* All network calls are intercepted — no real DB / HubSpot round-trips.
*/
const PORTFOLIO_SLUG = Cypress.env("LIVE_PORTFOLIO_SLUG");
const PORTFOLIO_ID_GLOB = "*";
function stubGet(pibiRequests = []) {
cy.intercept(
"GET",
`/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-requests*`,
{ body: { pibiRequests } },
).as("getPibiRequests");
}
function stubMeasures(
approvedMeasures = ["ASHP", "CWI"],
instructedMeasures = [],
) {
cy.intercept(
"GET",
`/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-measures*`,
{ body: { pibiMeasures: approvedMeasures, approvedMeasures, instructedMeasures } },
).as("getPibiMeasures");
}
function stubPost(response = { ok: true, insertedCount: 1, hubspotSync: "ok" }) {
cy.intercept(
"POST",
`/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-requests`,
{ body: response },
).as("postPibiRequest");
}
function stubPatch(id, response = { ok: true, hubspotSync: "ok" }) {
cy.intercept(
"PATCH",
`/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-requests/${id}`,
{ body: response },
).as(`patchPibiRequest-${id}`);
}
function stubDelete(id, response = { ok: true, hubspotSync: "ok" }) {
cy.intercept(
"DELETE",
`/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-requests/${id}*`,
{ body: response },
).as(`deletePibiRequest-${id}`);
}
function openDealPageAtPibiTab() {
cy.visit(`/portfolio/${PORTFOLIO_SLUG}/your-projects/live`);
cy.contains("button, [role=tab]", "Properties").click();
cy.get("[data-testid=property-row-link]").first().click();
cy.get("[data-testid=deal-page-tab-pibi-surveys]").click();
}
describe("PibiSection", function () {
before(function () {
if (!PORTFOLIO_SLUG) {
cy.log("LIVE_PORTFOLIO_SLUG not set — skipping PibiSection specs");
this.skip();
}
});
beforeEach(() => {
stubMeasures();
});
// ── Cycle 1: Empty state ──────────────────────────────────────────────────
it("shows empty state with Log first PIBI prompt when no requests exist", () => {
stubGet([]);
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-empty-state]").should("be.visible");
cy.get("[data-testid=pibi-empty-state]").should(
"contain.text",
"No PIBIs logged yet",
);
cy.get("[data-testid=pibi-empty-add-row]").should("be.visible");
});
// ── Cycle 2: Flat table renders rows (no batch groups) ────────────────────
it("renders a flat table of rows with no batch grouping", () => {
stubGet([
{
id: "1",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
{
id: "2",
measureName: "CWI",
orderedAt: "2026-04-01T00:00:00.000Z",
completedAt: null,
},
]);
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-row-1]").should("be.visible");
cy.get("[data-testid=pibi-row-2]").should("be.visible");
cy.get("[data-testid=pibi-batch-group]").should("not.exist");
});
// ── Cycle 3: Always-editable cells ────────────────────────────────────────
it("renders measure select and date inputs as always-editable cells", () => {
stubGet([
{
id: "5",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-measure-select-5]").should("be.visible");
cy.get("[data-testid=pibi-ordered-date-5]").should("be.visible");
cy.get("[data-testid=pibi-completed-date-5]").should("be.visible");
});
// ── Cycle 4: Save disabled when clean, enabled after editing ──────────────
it("shows Save disabled on load, enabled after editing a cell", () => {
stubGet([
{
id: "6",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-save-6]").should("be.disabled");
cy.get("[data-testid=pibi-ordered-date-6]").clear().type("2026-06-01");
cy.get("[data-testid=pibi-save-6]").should("not.be.disabled");
});
// ── Cycle 5: Save calls PATCH ─────────────────────────────────────────────
it("calls PATCH with updated fields when approver clicks Save", () => {
stubGet([
{
id: "10",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
stubPatch("10");
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-completed-date-10]").type("2026-05-15");
cy.get("[data-testid=pibi-save-10]").click();
cy.wait("@patchPibiRequest-10").then((interception) => {
expect(interception.request.body.dealId).to.be.a("string");
expect(interception.request.body.completedAt).to.include("2026-05-15");
});
});
// ── Cycle 6: Delete calls DELETE ──────────────────────────────────────────
it("calls DELETE when approver clicks Delete on a row", () => {
stubGet([
{
id: "20",
measureName: "EWI",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
stubDelete("20");
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-delete-20]").click();
cy.wait("@deletePibiRequest-20");
});
// ── Cycle 7: Add row → POST ───────────────────────────────────────────────
it("appends a blank row on add-row click and POSTs on save", () => {
stubGet([]);
stubPost({ ok: true, insertedCount: 1, hubspotSync: "ok" });
cy.intercept("GET", `/api/portfolio/${PORTFOLIO_ID_GLOB}/pibi-requests*`, {
body: {
pibiRequests: [
{
id: "99",
measureName: "ASHP",
orderedAt: new Date().toISOString(),
completedAt: null,
},
],
},
}).as("getPibiRequestsAfter");
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-empty-add-row]").click();
cy.get("[data-testid=pibi-section]").find("tr[data-testid^=pibi-row-new]").should("have.length", 1);
cy.get("[data-testid=pibi-section]")
.find("[data-testid^=pibi-save-new]")
.click();
cy.wait("@postPibiRequest").then((interception) => {
expect(interception.request.body.measureNames).to.be.an("array").with.length(1);
expect(interception.request.body.orderedAt).to.be.a("string");
});
});
// ── Cycle 8: Mark all complete ────────────────────────────────────────────
it("PATCHes all incomplete rows when Mark all complete is clicked", () => {
stubGet([
{
id: "30",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
{
id: "31",
measureName: "CWI",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
stubPatch("30");
stubPatch("31");
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-mark-all-complete]").click();
cy.wait("@patchPibiRequest-30").then((i) => {
expect(i.request.body.completedAt).to.be.a("string");
});
cy.wait("@patchPibiRequest-31");
});
// ── Cycle 9: Scope badges ─────────────────────────────────────────────────
it("shows Approved badge for a measure in approvedMeasures", () => {
stubGet([
{
id: "40",
measureName: "ASHP",
orderedAt: "2026-05-01T00:00:00.000Z",
completedAt: null,
},
]);
stubMeasures(["ASHP"], []);
openDealPageAtPibiTab();
cy.wait("@getPibiRequests");
cy.get("[data-testid=pibi-row-40]").should("contain.text", "Approved");
});
});