add cypress spec for pibi-measure selection flow

Verifies that an approver can open the drawer, tick/untick PIBI
measures, save the selection, and that the POST to pibi-measures route
(which pushes measures_for_pibi_ordered to HubSpot) is intercepted with
the correct payload. Uses cy.intercept stubs for both GET and POST.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-05 19:15:35 +00:00
parent 1767733441
commit 084391b9dc

View file

@ -0,0 +1,161 @@
/**
* Live Tracking PIBI measure selection flow (issue #254)
*
* Verifies the approver flow for selecting which measures on a deal go for
* PIBI:
* 1. the approver opens the property drawer at the PIBI section,
* 2. ticks and unticks measures in the multi-select,
* 3. saves the selection the drawer reflects the ticked state,
* 4. the POST hits the pibi-measures route which pushes
* `measures_for_pibi_ordered` back to HubSpot.
*
* Mirrors `instruct-measure.cy.js`. The spec uses `cy.intercept` so the
* HubSpot push side-effect is observable without a real CRM round-trip.
*/
const PORTFOLIO_SLUG = Cypress.env("LIVE_PORTFOLIO_SLUG");
const TARGET_DEAL_NAME = Cypress.env("LIVE_PIBI_DEAL_NAME");
describe("PIBI measure selection — approver flow", function () {
before(function () {
if (!PORTFOLIO_SLUG) {
cy.log(
"LIVE_PORTFOLIO_SLUG env var not set — skipping live tracking specs",
);
this.skip();
}
});
function openDrawerAtPibiSection() {
cy.visit(`/portfolio/${PORTFOLIO_SLUG}/your-projects/live`);
// Open a property row to get the detail drawer.
cy.contains("button, [role=tab]", "Measures").click();
if (TARGET_DEAL_NAME) {
cy.contains("[data-testid=measures-row]", TARGET_DEAL_NAME).click();
} else {
cy.get("[data-testid=measures-row]").first().click();
}
cy.get("[data-testid=property-detail-drawer]").should("be.visible");
// Scroll to the PIBI section.
cy.get("[data-testid=drawer-section-pibi]").should("exist").scrollIntoView();
}
it("fetches the PIBI state and shows the multi-select for approvers", () => {
// Stub the GET so we control the initial state.
cy.intercept(
"GET",
`/api/portfolio/*/pibi-measures*`,
{
body: {
pibiMeasures: ["ASHP"],
approvedMeasures: ["ASHP", "Solar PV"],
instructedMeasures: [],
},
},
).as("getPibiMeasures");
openDrawerAtPibiSection();
cy.wait("@getPibiMeasures");
// The multi-select should be visible for approvers.
cy.get("[data-testid=pibi-measure-selector]").should("be.visible");
// ASHP should be checked (was in pibiMeasures).
cy.get("[data-testid=pibi-measure-checkbox-ASHP]").should(
"be.checked",
);
});
it("lets an approver tick/untick selections and POST to the route", () => {
// Stub GET to return a clean state.
cy.intercept(
"GET",
`/api/portfolio/*/pibi-measures*`,
{
body: {
pibiMeasures: [],
approvedMeasures: ["ASHP", "Solar PV"],
instructedMeasures: [],
},
},
).as("getPibiMeasures");
// Intercept the POST so we can assert the body.
cy.intercept(
"POST",
`/api/portfolio/*/pibi-measures`,
).as("savePibiMeasures");
openDrawerAtPibiSection();
cy.wait("@getPibiMeasures");
cy.get("[data-testid=pibi-measure-selector]").should("be.visible");
// Both approved measures (ASHP, Solar PV) should be pre-ticked since
// pibiMeasures was empty and approvedMeasures had values.
cy.get("[data-testid=pibi-measure-checkbox-ASHP]").should("be.checked");
cy.get("[data-testid=pibi-measure-checkbox-Solar PV]").should("be.checked");
// Untick ASHP.
cy.get("[data-testid=pibi-measure-option-ASHP]").click();
cy.get("[data-testid=pibi-measure-checkbox-ASHP]").should("not.be.checked");
// Save the selection.
cy.get("[data-testid=pibi-selector-save]").click();
cy.wait("@savePibiMeasures").then((intercepted) => {
// Body should reflect the new selection (Solar PV only).
expect(intercepted.request.body).to.have.property("dealId");
expect(intercepted.request.body.measureNames).to.include("Solar PV");
expect(intercepted.request.body.measureNames).not.to.include("ASHP");
// Route returns ok=true.
expect(intercepted.response.statusCode).to.be.oneOf([200, 201]);
expect(intercepted.response.body).to.have.property("ok", true);
expect(intercepted.response.body).to.have.property(
"hubspotSync",
);
});
// No error banner visible.
cy.get("[data-testid=pibi-selector-error]").should("not.exist");
});
it("pushes measures_for_pibi_ordered to HubSpot on a successful save", () => {
cy.intercept(
"GET",
`/api/portfolio/*/pibi-measures*`,
{
body: {
pibiMeasures: ["CWI"],
approvedMeasures: ["CWI"],
instructedMeasures: [],
},
},
).as("getPibiMeasures");
// Stub the POST to confirm the property pushed.
cy.intercept("POST", `/api/portfolio/*/pibi-measures`, {
body: { ok: true, hubspotSync: "ok" },
}).as("savePibiMeasures");
openDrawerAtPibiSection();
cy.wait("@getPibiMeasures");
cy.get("[data-testid=pibi-selector-save]").click();
cy.wait("@savePibiMeasures").then((intercepted) => {
// Confirm the POST body contains the right measures.
expect(intercepted.request.body).to.have.property("measureNames");
// Response signals a successful HubSpot push.
expect(intercepted.response.body.hubspotSync).to.equal("ok");
});
});
});