/* catcrafts.net Copyright (C) 2026 Catcrafts The source code of this website is made available for viewing purposes only. No permission is granted to copy, modify, distribute, or create derivative works. */ // The shop front: prices rendered from the same integers the checkout // charges, the currency conversions the shop card carries, and the copy // around them. Runs in both shop states — coming-soon asserts the closed // shape, launch re-arms the open one with no edit here. // // The goods half addresses whatever priced product the compiled catalogue // lists first — its slug, its variants and its photo are read off the record, // and every expected figure is formatted with the same Money routines the // renderer uses. While the catalogue lists no goods (as since 2026-09-05) that // half prints a note and is skipped; the donation checks run regardless. import std; import Catcrafts.Shared; import Catcrafts.E2eHarness; using namespace Catcrafts; using namespace Catcrafts::E2e; namespace { void PricedProduct(TestServer& srv, const Product& pr) { const std::string shop = "/shop/" + pr.slug; const std::string incl = Money::FormatEuro(pr.priceInclMinor); const std::string net = Money::FormatEuro(Money::NetFromGross(pr.priceInclMinor)); // The price is rendered from the same integers the checkout charges, with // the derived ex-VAT twin alongside — asserting both pins the arithmetic. srv.BodyHas(shop, incl, "product page shows the (from-)price"); srv.BodyHas(shop, net, "product page shows the derived ex-VAT price"); srv.BodyHas("/shop", incl, "shop card shows the (from-)price"); // "from" is a claim that other colours cost more, so it appears exactly // when there is more than one colour to choose from. if (pr.variants.size() > 1) { srv.BodyHas(shop, ">from<", "product page marks the price as a from-price"); Check(pr.CheapestVariant() && pr.CheapestVariant()->priceInclMinor == pr.priceInclMinor, "the advertised from-price is the cheapest colour's"); } else { srv.BodyLacks(shop, ">from<", "a single-price product is not a from-price"); } // Every colour is priced in the selector, label and euro amount together, // and the form carries the exact data blob the preview computes from. if (srv.ShopOpen(pr.slug)) { for (const Variant& v : pr.variants) { srv.BodyHas(shop, v.label + " " + Money::FormatEuro(v.priceInclMinor), "colour selector prices " + v.slug); } srv.BodyHas(shop, "data-cc=", "form embeds the pricing blob"); srv.BodyHas(shop, "id=\"cc-total\"", "live total element present"); } else { srv.BodyHas(shop, "Coming soon", "coming-soon notice on the buy panel"); srv.BodyHas("/shop", "coming soon", "shop card carries the coming-soon badge"); srv.BodyLacks(shop, "