mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(release): act on the fourth Codex pass
- wrapText left a word longer than the line budget intact, so a valid 60-character unbroken highlight rendered past the edge of the 1200px card and was cropped. Overlong words are now broken at the budget, and a test asserts no returned line can exceed it. The previous test asserting the old behavior encoded the bug. - The announcement commands are documented as `pnpm --silent run`: reproduced that plain `pnpm run release:notes:telegram > post` writes pnpm's lifecycle banner into the file ahead of the post, so the advertised paste-ready output was not paste-ready. 186 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c3aebecac
commit
62359c80fc
8 files changed
+60
-11
No files matched your search
@@ -61,8 +61,13 @@ export function escapeXml(text) {
|
||||
|
||||
/**
|
||||
* Greedy word wrap by character budget. SVG has no automatic text layout and
|
||||
* font metrics vary by host, so the budget is conservative; a single word
|
||||
* longer than the budget gets its own line rather than being cut.
|
||||
* font metrics vary by host, so the budget is conservative.
|
||||
*
|
||||
* Every returned line is at most `maxChars` long, including when a single
|
||||
* word exceeds the budget: such a word is broken at the budget rather than
|
||||
* left whole. Leaving it whole is what a naive wrap does, and a 60-character
|
||||
* unbroken headline — well inside the validated limit — then rendered past
|
||||
* the edge of the 1200px card and was cropped.
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {number} maxChars
|
||||
@@ -75,6 +80,20 @@ export function wrapText(text, maxChars, maxLines) {
|
||||
let current = '';
|
||||
|
||||
for (const word of words) {
|
||||
if (word.length > maxChars) {
|
||||
if (current) {
|
||||
lines.push(current);
|
||||
}
|
||||
|
||||
for (let start = 0; start < word.length; start += maxChars) {
|
||||
lines.push(word.slice(start, start + maxChars));
|
||||
}
|
||||
|
||||
// Keep the final chunk open so a following short word can join it.
|
||||
current = lines.pop() ?? '';
|
||||
continue;
|
||||
}
|
||||
|
||||
const candidate = current ? `${current} ${word}` : word;
|
||||
|
||||
if (candidate.length <= maxChars || !current) {
|
||||
|
||||
@@ -78,13 +78,33 @@ describe('wrapText', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('gives an overlong single word its own line instead of cutting it', () => {
|
||||
it('breaks a word longer than the budget instead of overflowing the card', () => {
|
||||
assert.deepEqual(wrapText('supercalifragilistic ok', 10, 3), [
|
||||
'supercalifragilistic',
|
||||
'supercalif',
|
||||
'ragilistic',
|
||||
'ok',
|
||||
]);
|
||||
});
|
||||
|
||||
it('never returns a line wider than the budget', () => {
|
||||
// A 60-character unbroken highlight is valid input; on a 34-character
|
||||
// feature-card budget it must still fit inside the canvas.
|
||||
for (const [text, maxChars, maxLines] of [
|
||||
['W'.repeat(60), 34, 2],
|
||||
['W'.repeat(60), 30, 2],
|
||||
['W'.repeat(60), 60, 1],
|
||||
['a'.repeat(200), 34, 2],
|
||||
['short words only here', 34, 2],
|
||||
]) {
|
||||
for (const line of wrapText(text, maxChars, maxLines)) {
|
||||
assert.ok(
|
||||
line.length <= maxChars,
|
||||
`"${line}" is ${line.length} > ${maxChars}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('ellipsizes the last kept line on overflow', () => {
|
||||
const lines = wrapText('aaa bbb ccc ddd eee', 3, 2);
|
||||
|
||||
|
||||
Reference in new issue
Block a user