carlos / carloku Public

Every plan, band and currency slot equals its exported amount, Free included; a figure token is not found inside a grouped number

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev f291a9ec33dfb6bf95a710cedb06c76c6db10914 parent a7dae7f
2 files changed, +10 −3
  • copy-review/pricing/build.mjs +3 −1
  • test/pricing-pricebook.test.mjs +7 −2
diff --git a/copy-review/pricing/build.mjs b/copy-review/pricing/build.mjs
index 6e08bca..470b5b2 100644
--- a/copy-review/pricing/build.mjs
+++ b/copy-review/pricing/build.mjs
@@ -142,7 +142,9 @@ for (const r of recs) byBand[r.band].push(r.name);
// must checks that an approved string carries a figure as a whole token
// (so "10" is not found inside "100"), not merely as a substring.
const must = (id, text, what) => {
- const re = new RegExp("(^|[^0-9.])" + text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?![0-9.])");
+ // A whole token: not followed by a digit, a decimal point, or a
+ // grouping comma that continues the number ("10" is not in "10,000").
+ const re = new RegExp("(^|[^0-9.])" + text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?![0-9.]|,[0-9])");
if (!re.test(S[id])) throw new Error(`${id} (${JSON.stringify(S[id])}) does not carry ${what} ${JSON.stringify(text)} from the book`);
};
for (const p of ["community", "business"]) must(`plan.${p}.min`, String(plansByKey[p].min_quantity), "the minimum");
diff --git a/test/pricing-pricebook.test.mjs b/test/pricing-pricebook.test.mjs
index c0fb639..3e0ee83 100644
--- a/test/pricing-pricebook.test.mjs
+++ b/test/pricing-pricebook.test.mjs
@@ -65,12 +65,17 @@ test('the static page, before any script runs, is the default currency at band A
});
test('plan prices and the band table are the book, per band and currency', () => {
- for (const [k, id] of [['personal', 'plan.personal.price'], ['community', 'plan.community.price'], ['business', 'plan.business.price']]) {
+ // Every plan, band and currency slot is exactly its exported amount with
+ // the plan's suffix (review fourteen: a prefix match let "$30/y per
+ // member" pass for "$3/y per member").
+ for (const [k, id, suffix] of [['personal', 'plan.personal.price', '/y'], ['community', 'plan.community.price', '/y per member'], ['business', 'plan.business.price', '/y per member']]) {
const v = attr(id, 'data-band-values');
for (const band of ['A', 'B', 'C', 'D']) for (const cur of ['USD', 'EUR', 'GBP']) {
- assert.ok(v[band][cur].startsWith(money(sym[cur], planOf(k).annual_unit_minor[cur.toLowerCase()][band])), `${k} ${band} ${cur}: ${v[band][cur]}`);
+ assert.equal(v[band][cur], money(sym[cur], planOf(k).annual_unit_minor[cur.toLowerCase()][band]) + suffix, `${k} ${band} ${cur}`);
}
}
+ // The free plan's price in every currency.
+ assert.deepEqual(attr('plan.free.price', 'data-currency-values'), Object.fromEntries(['USD', 'EUR', 'GBP'].map((cur) => [cur, money(sym[cur], planOf('free').annual_unit_minor[cur.toLowerCase()].A)])));
assert.equal(attr('plan.personal.price', 'data-band-values').D.GBP, '£7.70/y');
assert.equal(attr('plan.business.price', 'data-band-values').D.GBP, '£15.75/y per member');
const cells = valuesOf(page.slice(page.indexOf('class="price-table"'), page.indexOf('class="price-table rates"')));