From bb05bbe14639a8822a2ea51fe3dbb2d1b88c916a Mon Sep 17 00:00:00 2001 From: "Abdullah M. Nazif" Date: Thu, 8 Oct 2026 19:30:17 +0000 Subject: [PATCH] fix(core): test the minimum font size when fitting text (#5283) --- .../core/src/text/fitTextFontSize.test.ts | 37 +++++++++++++++++++ packages/core/src/text/fitTextFontSize.ts | 7 +++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/core/src/text/fitTextFontSize.test.ts b/packages/core/src/text/fitTextFontSize.test.ts index 7afe3e80c5..0fb2dc36a4 100644 --- a/packages/core/src/text/fitTextFontSize.test.ts +++ b/packages/core/src/text/fitTextFontSize.test.ts @@ -70,4 +70,41 @@ describe("fitTextFontSize", () => { expect(mockPrepare).toHaveBeenNthCalledWith(1, "test", "900 80px Outfit"); expect(mockPrepare).toHaveBeenNthCalledWith(2, "test", "900 70px Outfit"); }); + + it.each([ + { baseFontSize: 78, minFontSize: 43, step: 2 }, + { baseFontSize: 60, minFontSize: 31, step: 4 }, + { baseFontSize: 55, minFontSize: 42.5, step: 3.5 }, + { baseFontSize: 48, minFontSize: 42, step: 10 }, + { baseFontSize: 44, minFontSize: 43, step: 2 }, + ])("accepts text that only fits at the minimum with %j", (options) => { + mockLayout.mockImplementation(() => { + const font = mockPrepare.mock.lastCall?.[1] ?? ""; + const size = Number(/ ([\d.]+)px /.exec(font)?.[1]); + return { height: 90, lineCount: size <= options.minFontSize ? 1 : 2 }; + }); + + expect(fitTextFontSize("fits at the floor", options)).toEqual({ + fontSize: options.minFontSize, + fits: true, + }); + expect(mockPrepare).toHaveBeenLastCalledWith( + "fits at the floor", + `900 ${options.minFontSize}px Outfit`, + ); + }); + + it("measures the minimum exactly once when it is already on a size step", () => { + mockLayout.mockReturnValue({ height: 180, lineCount: 2 }); + expect(fitTextFontSize("too long", { baseFontSize: 48, minFontSize: 42, step: 2 })).toEqual({ + fontSize: 42, + fits: false, + }); + expect(mockPrepare.mock.calls.map((call) => call[1])).toEqual([ + "900 48px Outfit", + "900 46px Outfit", + "900 44px Outfit", + "900 42px Outfit", + ]); + }); }); diff --git a/packages/core/src/text/fitTextFontSize.ts b/packages/core/src/text/fitTextFontSize.ts index f4526dd955..a56622282b 100644 --- a/packages/core/src/text/fitTextFontSize.ts +++ b/packages/core/src/text/fitTextFontSize.ts @@ -35,13 +35,18 @@ export function fitTextFontSize(text: string, options?: Partial) const opts = { ...DEFAULTS, ...options }; const lineHeightRatio = 1.2; - for (let size = opts.baseFontSize; size >= opts.minFontSize; size -= opts.step) { + for ( + let size = opts.baseFontSize; + size >= opts.minFontSize; + size = Math.max(opts.minFontSize, size - opts.step) + ) { const font = `${opts.fontWeight} ${size}px ${opts.fontFamily}`; const prepared = prepare(text, font); const { lineCount } = layout(prepared, opts.maxWidth, size * lineHeightRatio); if (lineCount <= 1) { return { fontSize: size, fits: true }; } + if (size === opts.minFontSize) break; } return { fontSize: opts.minFontSize, fits: false };