From dffb5c888ff57fb9ffca481e3792653d8edf6874 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Wed, 29 Jul 2026 16:48:26 +0800 Subject: [PATCH] test(web): cover the normalized SGR fold and the wide-pair clear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's coverage gate rejected `ansi.ts` after the state normalization: the new fold carries shapes the existing cases never reached. Added, each expectation checked against a real terminal where the terminal is the authority — three of my first guesses were wrong and the implementation was right: - `48;2;R;G;B` (the extended-background arm and its 4-parameter span), a bright foreground and background (`91`/`101`, which resolve to `--dsw-*` tokens rather than literal rgb), a `38` with no kind byte, re-opening an attribute already in force, and the bare `\x1b[m` reset. - Clearing a wide pair from the spacer side: `中x\r\bA` shows `A x`, since the backspace clamps at column 0 and writing the lead blanks its spacer instead of letting the `x` slide left. An erase reaching the lead does the same, so `中x\x1b[1K|` shows three blanks before the bar. --- .../client/ui-primitives/tests/ansi.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/client/ui-primitives/tests/ansi.spec.ts b/packages/client/ui-primitives/tests/ansi.spec.ts index d3efd5ffb3..1231acb459 100644 --- a/packages/client/ui-primitives/tests/ansi.spec.ts +++ b/packages/client/ui-primitives/tests/ansi.spec.ts @@ -407,6 +407,27 @@ describe('parseAnsiLines: bounded state and true widths', () => { ]]) }) + it('folds the remaining SGR shapes the model has to carry', () => { + // A 48-background in extended form, so the `48` arm and the `2`-span both run. + expect(parseAnsiLines(`${ESC}[48;2;1;2;3mA\r${ESC}[KB`)).toEqual([[ + { text: 'B', style: { backgroundColor: 'rgb(1, 2, 3)' } }, + ]]) + // A bright foreground and a bright background, the 90-97 / 100-107 arms. + expect(parseAnsiLines(`${ESC}[91mA\r${ESC}[KB`)).toEqual([[ + { text: 'B', style: { color: 'var(--dsw-alias-state-error-secondary)' } }, + ]]) + expect(parseAnsiLines(`${ESC}[101mA\r${ESC}[KB`)).toEqual([[ + { text: 'B', style: { backgroundColor: 'rgb(255, 85, 85)' } }, + ]]) + // An extended form with no recognized kind byte consumes nothing extra. + expect(parseAnsiLines(`${ESC}[38mA\r${ESC}[KB`)).toEqual([[{ text: 'B', style: undefined }]]) + // Re-opening an attribute already in force does not duplicate it, and a bare + // `\x1b[m` resets exactly as `\x1b[0m` does. + expect(parseAnsiLines(`${ESC}[1m${ESC}[1mA${ESC}[mB\r${ESC}[KC`)).toEqual([[ + { text: 'C', style: undefined }, + ]]) + }) + it('treats a text-presentation symbol as one column', () => { // Verified in a real terminal: `A✓B` redrawn with `XY` shows `XYB`, so the // check mark is ONE column. Taking the whole U+2600-U+27BF block as wide @@ -416,6 +437,17 @@ describe('parseAnsiLines: bounded state and true widths', () => { expect(onlySpan('A\u{1f600}B\rXY')).toEqual({ text: 'XY B', style: undefined }) }) + it('clears a wide pair from either side, including through an erase', () => { + // Verified in a real terminal (`A x`): the redraw puts the cursor at column + // 0, the backspace clamps there, and writing `A` over the wide lead blanks + // its spacer rather than letting the `x` slide left. + expect(onlySpan(`\u4e2dx\r${BS}A`)).toEqual({ text: 'A x', style: undefined }) + // An erase reaching the lead blanks its spacer through the same helper. + // Verified in a real terminal (` |`): 1K blanks through the cursor column, + // so the wide glyph's two cells and the `x` all become blanks. + expect(onlySpan(`\u4e2dx${ESC}[1K|`)).toEqual({ text: ' |', style: undefined }) + }) + it('blanks both halves of a wide pair when either is overwritten', () => { // A terminal cannot leave one cell of a two-cell glyph standing, so writing // over the spacer clears the lead as well.