209 lines
8.7 KiB
TypeScript
209 lines
8.7 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* apps/web/scripts/order-title-crypto-ticker-smoke.ts (cp615, Ken)
|
|
*
|
|
* Ken's two screenshots: he LIKES a barter order's title listing accepted
|
|
* cryptos by TICKER ("… for BCH, BTC, ETH, or SOL"), and DISLIKES a crypto
|
|
* order's title listing its payment rails by FULL NAME ("… for Litecoin (LTC),
|
|
* Dogecoin (DOGE), …") — which also ran to three lines and bled right up to the
|
|
* Message button. This pins both halves of the fix so no layer regresses:
|
|
*
|
|
* PART 1 (runtime — the shared title builder orderTitle.ts):
|
|
* • a crypto order's payment rails render as TICKERS in the title (matching
|
|
* the barter path): "…for LTC, DOGE, …", never "…for Litecoin (LTC), …"
|
|
* • a FIAT rail (Cash in person) keeps its full name in the title, and a
|
|
* crypto rail beside it is still a ticker
|
|
* • the BARTER path is untouched (accepted tickers verbatim)
|
|
* • tickers are never translated (same in a non-English locale)
|
|
* • the "I accept:" detail line (displayNamesForMethods) is UNTOUCHED — it
|
|
* still spells rails out in full, proving only the title changed
|
|
*
|
|
* PART 2 (static — OrderCard.svelte, comments stripped):
|
|
* • the title <h3> clamps to 2 lines on mobile / 1 line on desktop
|
|
* (ellipsised), and reserves enough desktop right-pad to clear the
|
|
* top-right cluster — NOT the old sm:line-clamp-none / line-clamp-3 /
|
|
* sm:pr-28 that let a long title bleed into the Message button.
|
|
*
|
|
* Source greps strip comments first, so this fix's own comments can't satisfy
|
|
* (or trip) the static checks.
|
|
*/
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { orderTitleParts, type OrderTitleInput } from '../src/lib/utils/orderTitle';
|
|
import { displayNamesForMethods } from '../src/lib/payments/display';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const WEB = resolve(HERE, '..');
|
|
|
|
let pass = 0;
|
|
let fail = 0;
|
|
const ok = (m: string) => {
|
|
pass++;
|
|
console.log(` \u2713 ${m}`);
|
|
};
|
|
const bad = (m: string, d = '') => {
|
|
fail++;
|
|
console.log(` \u2717 ${m}`);
|
|
if (d) console.log(` ${d}`);
|
|
};
|
|
|
|
/** Strip // line comments, block comments, and HTML/Svelte comments so a grep
|
|
* sees only live code — a fix's comment necessarily names the pattern it added. */
|
|
function stripComments(src: string): string {
|
|
return src
|
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
.replace(/<!--[\s\S]*?-->/g, '')
|
|
.replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
|
}
|
|
|
|
const settlementOf = (o: OrderTitleInput, goodsLabel?: string, locale = 'en'): string =>
|
|
String(
|
|
(orderTitleParts(o, (n) => String(n), goodsLabel, { locale }).values as Record<string, unknown>)
|
|
.settlement
|
|
);
|
|
|
|
// ── PART 1: crypto rails render as tickers in the title ───────────────
|
|
{
|
|
const s = settlementOf({
|
|
side: 'sell',
|
|
asset: 'BLURT',
|
|
fiat_currency: 'USD',
|
|
amount_min: 5,
|
|
amount_max: 25,
|
|
payment_methods: ['pay_ltc', 'pay_doge', 'pay_usdt', 'pay_dai', 'pay_bch', 'pay_dash', 'pay_xrp', 'pay_sol']
|
|
});
|
|
const expected = 'LTC, DOGE, USDT, DAI, BCH, DASH, XRP, or SOL';
|
|
if (s === expected) ok(`crypto order title lists rails as tickers: "${s}"`);
|
|
else bad('crypto order title should list rails as tickers', `got "${s}" want "${expected}"`);
|
|
if (!/Litecoin|Dogecoin|\(LTC\)|\(DOGE\)/.test(s))
|
|
ok('crypto order title carries no full rail names / parenthesised tickers');
|
|
else bad('crypto order title leaked a full rail name', s);
|
|
}
|
|
{
|
|
const s = settlementOf({
|
|
side: 'sell',
|
|
asset: 'BTC',
|
|
fiat_currency: 'USD',
|
|
amount_min: 100,
|
|
amount_max: null,
|
|
payment_methods: ['pay_ltc', 'pay_xmr']
|
|
});
|
|
if (s === 'LTC or XMR') ok(`crypto-for-crypto title: "${s}"`);
|
|
else bad('crypto-for-crypto title should be "LTC or XMR"', s);
|
|
}
|
|
|
|
// ── Fiat rail keeps its name; crypto rail beside it stays a ticker ────
|
|
{
|
|
const s = settlementOf({
|
|
side: 'sell',
|
|
asset: 'BLURT',
|
|
fiat_currency: 'USD',
|
|
amount_min: 10,
|
|
amount_max: null,
|
|
payment_methods: ['cash_in_person', 'pay_btc']
|
|
});
|
|
if (/Cash \(in person\)/.test(s)) ok(`fiat rail keeps its full name in the title: "${s}"`);
|
|
else bad('fiat rail should keep its full name in the title', s);
|
|
if (/\bBTC\b/.test(s) && !/Bitcoin \(BTC\)/.test(s))
|
|
ok('crypto rail beside a fiat rail still renders as a ticker');
|
|
else bad('crypto rail beside a fiat rail should render as a ticker', s);
|
|
}
|
|
|
|
// ── Barter path unchanged (accepted tickers verbatim) ────────────────
|
|
{
|
|
const s = settlementOf(
|
|
{
|
|
side: 'sell',
|
|
asset: 'BARTER',
|
|
fiat_currency: 'USD',
|
|
amount_min: null,
|
|
amount_max: null,
|
|
accepted_assets: ['BTC', 'XMR']
|
|
},
|
|
'bananas'
|
|
);
|
|
if (s === 'BTC or XMR') ok(`barter title still lists accepted tickers verbatim: "${s}"`);
|
|
else bad('barter title should list accepted tickers verbatim', s);
|
|
}
|
|
|
|
// ── Tickers are never translated (locale only changes the joiner) ────
|
|
{
|
|
const es = settlementOf({
|
|
side: 'sell',
|
|
asset: 'BLURT',
|
|
fiat_currency: 'USD',
|
|
amount_min: 5,
|
|
amount_max: null,
|
|
payment_methods: ['pay_ltc', 'pay_doge', 'pay_sol']
|
|
}, undefined, 'es');
|
|
if (/\bLTC\b/.test(es) && /\bDOGE\b/.test(es) && /\bSOL\b/.test(es))
|
|
ok(`tickers stay identical in a non-English locale: "${es}"`);
|
|
else bad('tickers must never be translated across locales', es);
|
|
}
|
|
|
|
// ── "I accept:" detail line unchanged (still full names) ─────────────
|
|
{
|
|
const names = displayNamesForMethods(['pay_ltc', 'pay_doge']);
|
|
if (names.join(', ') === 'Litecoin (LTC), Dogecoin (DOGE)')
|
|
ok('"I accept:" line still spells rails out in full — only the title changed');
|
|
else bad('"I accept:" line should be unchanged (full names)', names.join(', '));
|
|
}
|
|
|
|
// ── PART 2: OrderCard title clamp + right-pad (static) ───────────────
|
|
{
|
|
const card = stripComments(readFileSync(resolve(WEB, 'src/lib/components/OrderCard.svelte'), 'utf8'));
|
|
const m = card.match(/<h3\s+class="([^"]*line-clamp[^"]*)"/);
|
|
const cls = m ? m[1] : '';
|
|
if (!cls) {
|
|
bad('could not find the title <h3> class in OrderCard.svelte');
|
|
} else {
|
|
if (/\bline-clamp-2\b/.test(cls)) ok('title clamps to 2 lines on mobile (line-clamp-2)');
|
|
else bad('title should clamp to 2 lines on mobile (line-clamp-2)', cls);
|
|
|
|
if (/\bsm:line-clamp-1\b/.test(cls))
|
|
ok('title clamps to 1 line on desktop (sm:line-clamp-1, auto-ellipsised)');
|
|
else bad('title should clamp to 1 line on desktop (sm:line-clamp-1)', cls);
|
|
|
|
if (!/sm:line-clamp-none|\bline-clamp-3\b/.test(cls))
|
|
ok('old unclamped-desktop / 3-line-mobile classes are gone');
|
|
else bad('old sm:line-clamp-none / line-clamp-3 still present', cls);
|
|
|
|
// Desktop pad clears the top-right EXPIRY CHIP — the single title line
|
|
// sits at the chip row; the Message button is lower, over the identity,
|
|
// so the title need only clear the chip. The chip is COMPACT in LTR
|
|
// ("Expires in 82d") but a whole phrase in RTL ("…روز دیگر منقضی میشود"),
|
|
// so the two directions carry different pads, each mirrored to the
|
|
// correct side (cp620). LTR pads the RIGHT; a sane window [8rem,11rem]
|
|
// clears the compact chip without the old dead space (the retired
|
|
// sm:pr-[13rem] was sized for the button, not the chip). RTL pads the
|
|
// LEFT and must be wide enough (≥12rem) for the verbose phrase.
|
|
const ltrStep = cls.match(/sm:ltr:pr-(\d+)\b/);
|
|
const ltrRem = ltrStep ? parseInt(ltrStep[1], 10) / 4 : NaN;
|
|
if (ltrStep && ltrRem >= 8 && ltrRem <= 11)
|
|
ok(`LTR title pads the right just enough to clear the compact chip (sm:ltr:pr-${ltrStep[1]} = ${ltrRem}rem)`);
|
|
else bad('LTR title should pad the RIGHT in [8rem,11rem] — clears the compact chip without dead space', cls);
|
|
|
|
const rtlArb = cls.match(/sm:rtl:pl-\[(\d+(?:\.\d+)?)rem\]/);
|
|
const rtlStep = cls.match(/sm:rtl:pl-(\d+)\b/);
|
|
const rtlRem = rtlArb ? parseFloat(rtlArb[1]) : rtlStep ? parseInt(rtlStep[1], 10) / 4 : NaN;
|
|
if (rtlRem >= 12)
|
|
ok(`RTL title mirrors the pad to the LEFT, wide enough for the verbose phrase (${rtlArb ? rtlArb[0] : rtlStep?.[0]} = ${rtlRem}rem)`);
|
|
else bad('RTL title should pad the LEFT ≥12rem for the verbose expiry phrase', cls);
|
|
|
|
// The old symmetric physical pad must be gone (it did not mirror for RTL).
|
|
if (!/\bsm:pr-\[13rem\]\b/.test(cls) && !/\bsm:pr-28\b/.test(cls))
|
|
ok('retired symmetric sm:pr-[13rem] / sm:pr-28 physical pad is gone');
|
|
else bad('old physical sm:pr-[13rem] / sm:pr-28 still present (should be ltr:/rtl: mirrored)', cls);
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log('\u2500'.repeat(56));
|
|
if (fail === 0) {
|
|
console.log(`\u2713 all ${pass} order-title-crypto-ticker scenarios passed`);
|
|
} else {
|
|
console.log(`\u2717 ${fail} FAILED, ${pass} passed`);
|
|
process.exit(1);
|
|
}
|