From f4edb67acb2894541100fc865e9dab9914114121 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 30 May 2026 21:50:34 +0300 Subject: [PATCH] test(font): window centering, clamping, and key stability --- .../computeLineRenderModel.test.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/entities/Font/lib/dualFontLayout/computeLineRenderModel/computeLineRenderModel.test.ts b/src/entities/Font/lib/dualFontLayout/computeLineRenderModel/computeLineRenderModel.test.ts index cdd79ad..db8206e 100644 --- a/src/entities/Font/lib/dualFontLayout/computeLineRenderModel/computeLineRenderModel.test.ts +++ b/src/entities/Font/lib/dualFontLayout/computeLineRenderModel/computeLineRenderModel.test.ts @@ -84,4 +84,94 @@ describe('computeLineRenderModel', () => { expect(model.leftText).toBe('AB'); expect(model.rightText).toBe('C'); }); + + it('centers window of size 3 on the split index', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + { char: 'C', widthA: 10, widthB: 10 }, + { char: 'D', widthA: 10, widthB: 10 }, + { char: 'E', widthA: 10, widthB: 10 }, + ]); + // Slider past A and B (~thresholds 43.33%, 46.67%); not past C (50%). + // split = 2 → halfWindow = 1 → windowStart = 1, windowEnd = 4 + const model = computeLineRenderModel(line, 48, 300, 3); + expect(model.leftText).toBe('A'); + expect(model.windowChars.map(w => w.char)).toEqual(['B', 'C', 'D']); + expect(model.rightText).toBe('E'); + }); + + it('clamps window at line start when slider is near 0', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + { char: 'C', widthA: 10, widthB: 10 }, + { char: 'D', widthA: 10, widthB: 10 }, + { char: 'E', widthA: 10, widthB: 10 }, + ]); + const model = computeLineRenderModel(line, 0, 300, 3); + expect(model.leftText).toBe(''); + expect(model.windowChars.map(w => w.char)).toEqual(['A', 'B', 'C']); + expect(model.rightText).toBe('DE'); + }); + + it('clamps window at line end when slider is near 100', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + { char: 'C', widthA: 10, widthB: 10 }, + { char: 'D', widthA: 10, widthB: 10 }, + { char: 'E', widthA: 10, widthB: 10 }, + ]); + const model = computeLineRenderModel(line, 100, 300, 3); + expect(model.leftText).toBe('AB'); + expect(model.windowChars.map(w => w.char)).toEqual(['C', 'D', 'E']); + expect(model.rightText).toBe(''); + }); + + it('treats whole line as window when line is shorter than windowSize', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + ]); + const model = computeLineRenderModel(line, 50, 300, 5); + expect(model.leftText).toBe(''); + expect(model.windowChars.map(w => w.char)).toEqual(['A', 'B']); + expect(model.rightText).toBe(''); + }); + + it('produces stable keys across slider movement within the same line', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + { char: 'C', widthA: 10, widthB: 10 }, + { char: 'D', widthA: 10, widthB: 10 }, + { char: 'E', widthA: 10, widthB: 10 }, + ]); + const a = computeLineRenderModel(line, 40, 300, 3); + const b = computeLineRenderModel(line, 60, 300, 3); + // Chars that appear in both windows must carry identical keys. + for (const charA of a.windowChars) { + const charB = b.windowChars.find(w => w.char === charA.char); + if (charB !== undefined) { + expect(charB.key).toBe(charA.key); + } + } + }); + + it('marks isPast=true for chars before the split and false for chars after', () => { + const line = makeLine([ + { char: 'A', widthA: 10, widthB: 10 }, + { char: 'B', widthA: 10, widthB: 10 }, + { char: 'C', widthA: 10, widthB: 10 }, + { char: 'D', widthA: 10, widthB: 10 }, + { char: 'E', widthA: 10, widthB: 10 }, + ]); + // split = 2 → A,B past; C,D,E not + const model = computeLineRenderModel(line, 48, 300, 5); + const expected = new Map([['A', true], ['B', true], ['C', false], ['D', false], ['E', false]]); + for (const wc of model.windowChars) { + expect(wc.isPast).toBe(expected.get(wc.char)); + } + }); });