add cypress spec for instruct-measure flow

Approver opens the drawer at the Measures section, picks a measure from
the catalogue dropdown, submits, and the spec asserts the optimistic
chip, the POST payload + response shape, and that the approval log
surfaces the new approval row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-05 18:58:08 +00:00
parent c5083cbc78
commit f3ce506653

View file

@ -0,0 +1,93 @@
/**
* Live Tracking Instruct measure flow (issue #253)
*
* Verifies the approver flow for instructing a measure that the
* coordinator did not propose:
* 1. the approver opens the property drawer at the Measures section,
* 2. picks a measure from the canonical catalogue dropdown and submits,
* 3. the drawer reflects the new instructed measure (optimistic chip),
* 4. the POST hits the instructed-measures route which pushes
* `instructed_measures` back to HubSpot,
* 5. the approval log surface shows a row for the new approval.
*
* Mirrors `halted-state.cy.js` / `domna-survey.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_INSTRUCT_DEAL_NAME");
const INSTRUCT_MEASURE = "Loft insulation";
describe("Instruct measure — approver flow", function () {
before(function () {
if (!PORTFOLIO_SLUG) {
cy.log(
"LIVE_PORTFOLIO_SLUG env var not set — skipping live tracking specs",
);
this.skip();
}
});
function openDrawerForTargetDeal() {
cy.visit(`/portfolio/${PORTFOLIO_SLUG}/your-projects/live`);
// Switch to the Measures tab — the easiest way into the drawer at the
// Measures section.
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");
cy.get("[data-testid=drawer-section-measures]").should("exist");
}
it("lets an approver instruct a measure and reflects it in the drawer + approval log", () => {
// Capture the API call so we can assert the payload that would be
// pushed to HubSpot under `instructed_measures`.
cy.intercept(
"POST",
`/api/portfolio/*/instructed-measures`,
).as("instructMeasure");
openDrawerForTargetDeal();
// Approver-only form is visible at the bottom of the Measures section.
cy.get("[data-testid=instruct-measure-select]").should("be.visible");
cy.get("[data-testid=instruct-measure-select]").select(INSTRUCT_MEASURE);
cy.get("[data-testid=instruct-measure-submit]")
.should("not.be.disabled")
.click();
// Wait for the POST to land and assert the body shape that the
// service uses to drive the HubSpot push.
cy.wait("@instructMeasure").then((intercepted) => {
expect(intercepted.request.body).to.deep.include({
measureName: INSTRUCT_MEASURE,
});
// Response from our route signals the HubSpot sync outcome — it is
// either "ok" (mock recorded the push) or "failed" (network error).
// We accept either here so the spec stays portable across envs.
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");
});
// Drawer reflects the instructed measure as an optimistic chip.
cy.get("[data-testid=instructed-measures-list]").should("be.visible");
cy.get("[data-testid=instructed-measure-chip]")
.should("contain.text", INSTRUCT_MEASURE);
// No error banner.
cy.get("[data-testid=instruct-measure-error]").should("not.exist");
// Approval log section reveals the new approval row when expanded.
cy.contains("Approval Log").click();
cy.contains(INSTRUCT_MEASURE).should("exist");
});
});