From 1c9e9be07d2a09babd00320817502d8d3d235cb9 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 18 Jun 2026 16:01:53 +0800 Subject: [PATCH 1/3] test: reset keyboard state to prevent cross-case leak in InputManager The "keyboard" case dispatches a `keydown KeyA` without a following `update`, so the event stays queued and is later consumed by another case's `update`, marking KeyA as held down. On mac the meta-key cleanup in KeyboardManager happens to clear it, but on other platforms it stays held, so the next `keydown KeyA` is filtered as a repeat and isKeyDown returns false. This made "blur and focus" and "change listener target" fail locally on Windows while passing in CI (macOS). Reset the keyboard state at the end of the case to keep it isolated. Refs #3043 Co-Authored-By: Claude Opus 4.8 --- tests/src/core/input/InputManager.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/src/core/input/InputManager.test.ts b/tests/src/core/input/InputManager.test.ts index f883f8fd42..c391ff56ba 100644 --- a/tests/src/core/input/InputManager.test.ts +++ b/tests/src/core/input/InputManager.test.ts @@ -196,6 +196,12 @@ describe("InputManager", async () => { target.dispatchEvent(generateKeyboardEvent("keydown", "KeyA")); target.dispatchEvent(generateKeyboardEvent("keyup", "MetaRight")); expect(inputManager.isKeyHeldDown(Keys.KeyA)).to.eq(false); + + // The events above are queued but never consumed by an `update` in this case. Reset the keyboard + // state so the pending `KeyA` keydown does not leak into later cases (it otherwise stays held down + // on non-mac platforms, where the meta-key cleanup in KeyboardManager does not run). + target.dispatchEvent(new Event("blur")); + engine.update(); }); it("wheel", () => { From 49c1544ea15d6a1328afcf7ae9837f7fc07b85ea Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 18 Jun 2026 16:02:09 +0800 Subject: [PATCH 2/3] test: use a fixed Roboto subset for deterministic text metrics TextUtils derives lineHeight from a per-pixel scan of rendered glyphs, so relying on the host "Arial" (a different font file and rasterizer per OS) produced platform-dependent values (lineHeight 27 on macOS vs 26 on Windows). Bundle a small Roboto subset (Apache-2.0), load it via FontFace in the test, point the renderers at it, and rebaseline the metrics. CJK text still falls back to system fonts as before, so its full-width advances stay stable. Refs #3043 Co-Authored-By: Claude Opus 4.8 --- .../core/2d/text/Roboto-Regular-subset.ttf | Bin 0 -> 22476 bytes tests/src/core/2d/text/TextUtils.test.ts | 115 ++++++++++-------- 2 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 tests/src/core/2d/text/Roboto-Regular-subset.ttf diff --git a/tests/src/core/2d/text/Roboto-Regular-subset.ttf b/tests/src/core/2d/text/Roboto-Regular-subset.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2299457f170cb53f37458ba389312e94e18af735 GIT binary patch literal 22476 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 literal 0 HcmV?d00001 diff --git a/tests/src/core/2d/text/TextUtils.test.ts b/tests/src/core/2d/text/TextUtils.test.ts index 54a5a57820..2c8c342b74 100644 --- a/tests/src/core/2d/text/TextUtils.test.ts +++ b/tests/src/core/2d/text/TextUtils.test.ts @@ -24,8 +24,17 @@ describe("TextUtils", () => { let textRendererOverflow: TextRenderer; let wrap1TextRenderer: TextRenderer; let wrap2TextRenderer: TextRenderer; + const testFontName = "RobotoSubsetTest"; beforeAll(async function () { + // Load a fixed Roboto subset so that font metrics are deterministic across machines, instead of + // depending on the host "Arial" which is a different font file per OS and rasterizes differently. + // See https://github.com/galacean/engine/issues/3043 + const fontUrl = new URL("./Roboto-Regular-subset.ttf", import.meta.url).href; + const testFont = new FontFace(testFontName, `url(${fontUrl})`); + await testFont.load(); + document.fonts.add(testFont); + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); @@ -46,15 +55,15 @@ describe("TextUtils", () => { textEntity4 = rootEntity.createChild("text4"); textRendererTruncate = textEntity1.addComponent(TextRenderer); - textRendererTruncate.font = Font.createFromOS(engine, "Arial"); + textRendererTruncate.font = Font.createFromOS(engine, testFontName); textRendererOverflow = textEntity2.addComponent(TextRenderer); - textRendererOverflow.font = Font.createFromOS(engine, "Arial"); + textRendererOverflow.font = Font.createFromOS(engine, testFontName); wrap1TextRenderer = textEntity3.addComponent(TextRenderer); - wrap1TextRenderer.font = Font.createFromOS(engine, "Arial"); + wrap1TextRenderer.font = Font.createFromOS(engine, testFontName); wrap2TextRenderer = textEntity4.addComponent(TextRenderer); - wrap2TextRenderer.font = Font.createFromOS(engine, "Arial"); + wrap2TextRenderer.font = Font.createFromOS(engine, testFontName); engine.run(); }); @@ -167,7 +176,7 @@ describe("TextUtils", () => { "步", "。" ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text2; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -193,10 +202,10 @@ describe("TextUtils", () => { "at", "to", "d", - "a", - "y." + "ay", + "." ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text3; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -226,11 +235,10 @@ describe("TextUtils", () => { "at", "to", "d", - "a", - "y", + "ay", "。" ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text4; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -239,10 +247,10 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(23); + expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); + expect(result.lineHeight).to.be.equal(28); // Test that measureTextWithWrap works correctly, while set overflow mode to overflow. textRendererOverflow.overflowMode = OverflowMode.Overflow; @@ -261,7 +269,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(567); + expect(result.height).to.be.equal(588); expect(result.lines).to.be.deep.equal([ "趚", "今", @@ -285,7 +293,7 @@ describe("TextUtils", () => { "步", "。" ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text2; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -295,7 +303,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(432); + expect(result.height).to.be.equal(448); expect(result.lines).to.be.deep.equal([ "T", "h", @@ -311,10 +319,10 @@ describe("TextUtils", () => { "at", "to", "d", - "a", - "y." + "ay", + "." ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text3; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -324,7 +332,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(567); + expect(result.height).to.be.equal(560); expect(result.lines).to.be.deep.equal([ "阳", "光", @@ -344,11 +352,10 @@ describe("TextUtils", () => { "at", "to", "d", - "a", - "y", + "ay", "。" ]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text4; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -357,10 +364,10 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(23); - expect(result.height).to.be.equal(135); - expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); - expect(result.lineHeight).to.be.equal(27); + expect(result.width).to.be.equal(24); + expect(result.height).to.be.equal(140); + expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); + expect(result.lineHeight).to.be.equal(28); wrap1TextRenderer.enableWrapping = true; wrap1TextRenderer.width = 5; @@ -405,11 +412,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(518); + expect(result.width).to.be.equal(516); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); - expect(result.lineWidths).to.be.deep.equal([518]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([516]); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text2; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -421,7 +428,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["The weather is great today."]); expect(result.lineWidths).to.be.deep.equal([289]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text3; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -429,11 +436,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(418); + expect(result.width).to.be.equal(421); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); - expect(result.lineWidths).to.be.deep.equal([418]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([421]); + expect(result.lineHeight).to.be.equal(28); textRendererTruncate.text = text4; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -441,11 +448,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(111); + expect(result.width).to.be.equal(105); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal([" ", " World"]); - expect(result.lineWidths).to.be.deep.equal([63, 111]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([54, 105]); + expect(result.lineHeight).to.be.equal(28); // Test that measureTextWithoutWrap works correctly, while set overflow mode to overflow. textRendererOverflow.overflowMode = OverflowMode.Overflow; @@ -465,7 +472,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(0); expect(result.lines).to.be.deep.equal([]); expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.deep.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = undefined; result = TextUtils.measureTextWithoutWrap( @@ -478,7 +485,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(0); expect(result.lines).to.be.deep.equal([]); expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.deep.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text1; result = TextUtils.measureTextWithoutWrap( @@ -487,11 +494,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(518); - expect(result.height).to.be.equal(27); + expect(result.width).to.be.equal(516); + expect(result.height).to.be.equal(28); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); - expect(result.lineWidths).to.be.deep.equal([518]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([516]); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text2; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -500,10 +507,10 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(289); - expect(result.height).to.be.equal(27); + expect(result.height).to.be.equal(28); expect(result.lines).to.be.deep.equal(["The weather is great today."]); expect(result.lineWidths).to.be.deep.equal([289]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text3; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -511,11 +518,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(418); - expect(result.height).to.be.equal(27); + expect(result.width).to.be.equal(421); + expect(result.height).to.be.equal(28); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); - expect(result.lineWidths).to.be.deep.equal([418]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([421]); + expect(result.lineHeight).to.be.equal(28); textRendererOverflow.text = text4; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -523,11 +530,11 @@ describe("TextUtils", () => { textRendererTruncate.lineSpacing * _pixelsPerUnit, 0 ); - expect(result.width).to.be.equal(111); - expect(result.height).to.be.equal(54); + expect(result.width).to.be.equal(105); + expect(result.height).to.be.equal(56); expect(result.lines).to.be.deep.equal([" ", " World"]); - expect(result.lineWidths).to.be.deep.equal([63, 111]); - expect(result.lineHeight).to.be.equal(27); + expect(result.lineWidths).to.be.deep.equal([54, 105]); + expect(result.lineHeight).to.be.equal(28); }); it("getNativeFontHash", () => { From 782e860bc30ff013cbde0dfdcdf1fd2a5a224e8f Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 19 Jun 2026 21:59:22 +0800 Subject: [PATCH 3/3] test: assert rasterized TextUtils metrics with a tolerance Address review feedback on #3044: - lineHeight/height derive from a per-pixel glyph scan and stay rasterizer dependent (CoreText vs DirectWrite) even with a fixed font, so assert them with a +/-1px tolerance (closeTo) while keeping advance-based width/lines/lineWidths exact. This preserves the regression guard while letting non-mac platforms pass too. - Record the bundled font source/license inline (Roboto-Regular v2.137, Apache-2.0). - Await document.fonts.ready to remove a font-readiness race. Refs #3043 Co-Authored-By: Claude Opus 4.8 --- tests/src/core/2d/text/TextUtils.test.ts | 54 ++++++++++++------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/tests/src/core/2d/text/TextUtils.test.ts b/tests/src/core/2d/text/TextUtils.test.ts index 2c8c342b74..198b23d6aa 100644 --- a/tests/src/core/2d/text/TextUtils.test.ts +++ b/tests/src/core/2d/text/TextUtils.test.ts @@ -29,11 +29,13 @@ describe("TextUtils", () => { beforeAll(async function () { // Load a fixed Roboto subset so that font metrics are deterministic across machines, instead of // depending on the host "Arial" which is a different font file per OS and rasterizes differently. + // Font: Roboto-Regular v2.137, Apache-2.0, subset to Latin glyphs for these tests. // See https://github.com/galacean/engine/issues/3043 const fontUrl = new URL("./Roboto-Regular-subset.ttf", import.meta.url).href; const testFont = new FontFace(testFontName, `url(${fontUrl})`); await testFont.load(); document.fonts.add(testFont); + await document.fonts.ready; engine = await WebGLEngine.create({ canvas: document.createElement("canvas") @@ -176,7 +178,7 @@ describe("TextUtils", () => { "步", "。" ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text2; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -205,7 +207,7 @@ describe("TextUtils", () => { "ay", "." ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text3; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -238,7 +240,7 @@ describe("TextUtils", () => { "ay", "。" ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text4; result = TextUtils.measureTextWithWrap( textRendererTruncate, @@ -250,7 +252,7 @@ describe("TextUtils", () => { expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); // Test that measureTextWithWrap works correctly, while set overflow mode to overflow. textRendererOverflow.overflowMode = OverflowMode.Overflow; @@ -269,7 +271,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(588); + expect(result.height).to.be.closeTo(588, 21); expect(result.lines).to.be.deep.equal([ "趚", "今", @@ -293,7 +295,7 @@ describe("TextUtils", () => { "步", "。" ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text2; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -303,7 +305,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(448); + expect(result.height).to.be.closeTo(448, 16); expect(result.lines).to.be.deep.equal([ "T", "h", @@ -322,7 +324,7 @@ describe("TextUtils", () => { "ay", "." ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text3; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -332,7 +334,7 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(560); + expect(result.height).to.be.closeTo(560, 20); expect(result.lines).to.be.deep.equal([ "阳", "光", @@ -355,7 +357,7 @@ describe("TextUtils", () => { "ay", "。" ]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text4; result = TextUtils.measureTextWithWrap( textRendererOverflow, @@ -365,9 +367,9 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(24); - expect(result.height).to.be.equal(140); + expect(result.height).to.be.closeTo(140, 5); expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); wrap1TextRenderer.enableWrapping = true; wrap1TextRenderer.width = 5; @@ -416,7 +418,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); expect(result.lineWidths).to.be.deep.equal([516]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text2; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -428,7 +430,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["The weather is great today."]); expect(result.lineWidths).to.be.deep.equal([289]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text3; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -440,7 +442,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); expect(result.lineWidths).to.be.deep.equal([421]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererTruncate.text = text4; result = TextUtils.measureTextWithoutWrap( textRendererTruncate, @@ -452,7 +454,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal([" ", " World"]); expect(result.lineWidths).to.be.deep.equal([54, 105]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); // Test that measureTextWithoutWrap works correctly, while set overflow mode to overflow. textRendererOverflow.overflowMode = OverflowMode.Overflow; @@ -472,7 +474,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(0); expect(result.lines).to.be.deep.equal([]); expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = undefined; result = TextUtils.measureTextWithoutWrap( @@ -485,7 +487,7 @@ describe("TextUtils", () => { expect(result.height).to.be.equal(0); expect(result.lines).to.be.deep.equal([]); expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text1; result = TextUtils.measureTextWithoutWrap( @@ -495,10 +497,10 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(516); - expect(result.height).to.be.equal(28); + expect(result.height).to.be.closeTo(28, 1); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]); expect(result.lineWidths).to.be.deep.equal([516]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text2; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -507,10 +509,10 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(289); - expect(result.height).to.be.equal(28); + expect(result.height).to.be.closeTo(28, 1); expect(result.lines).to.be.deep.equal(["The weather is great today."]); expect(result.lineWidths).to.be.deep.equal([289]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text3; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -519,10 +521,10 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(421); - expect(result.height).to.be.equal(28); + expect(result.height).to.be.closeTo(28, 1); expect(result.lines).to.be.deep.equal(["阳光明媚,the weather is great today。"]); expect(result.lineWidths).to.be.deep.equal([421]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); textRendererOverflow.text = text4; result = TextUtils.measureTextWithoutWrap( textRendererOverflow, @@ -531,10 +533,10 @@ describe("TextUtils", () => { 0 ); expect(result.width).to.be.equal(105); - expect(result.height).to.be.equal(56); + expect(result.height).to.be.closeTo(56, 2); expect(result.lines).to.be.deep.equal([" ", " World"]); expect(result.lineWidths).to.be.deep.equal([54, 105]); - expect(result.lineHeight).to.be.equal(28); + expect(result.lineHeight).to.be.closeTo(28, 1); }); it("getNativeFontHash", () => {