mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
286 lines
9.2 KiB
JavaScript
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");
|
|
});
|
|
});
|