screen.getByText('3 mm') can never match an element containing a no-break space, even when the component renders perfectly. Testing Library’s default normalizer collapses U+00A0 to a plain space before comparing, while your matcher string is compared raw. The two are never equal.

Why this wastes time

The failure reads like a bug in the formatting code — the output “looks right” in the DOM dump, because U+00A0 and U+0020 are visually identical. The rendered output is correct the whole time; only the query is wrong.

Root cause

Testing Library’s default normalizer trims, then runs replace(/\s+/g, ' ') over the element’s text. JavaScript’s \s character class includes U+00A0 (along with the rest of Unicode Zs, plus U+FEFF, U+2028, U+2029).

So for <span>3\u00A0mm</span>:

SideValue compared
DOM text, after normalization3 + U+0020 + mm
Matcher string (never normalized)3 + U+00A0 + mm

Not equal. No amount of fixing the formatter helps.

Fix — turn collapsing off for that query

import { getDefaultNormalizer, render, screen } from '@testing-library/react'
import { Data } from '../src/components/Data'
 
const NBSP = '\u00A0'
const literal = { normalizer: getDefaultNormalizer({ collapseWhitespace: false }) }
 
render(<Data value={3} unit="mm" locale="hu" />)
expect(screen.getByText(`3${NBSP}mm`, literal)).toBeDefined()

getDefaultNormalizer({ collapseWhitespace: false }) keeps trimming (usually what you want) but stops the whitespace squash. Define literal once at module scope and pass it to every query that asserts on a formatted number.

The wrong fix, and why it matters

The obvious alternative is to give up and assert on a plain space:

expect(screen.getByText('3 mm')).toBeDefined()   // DO NOT

This passes. It also passes when the formatter emits a breaking U+0020 — which is the exact regression the test exists to catch. Because the normalizer collapses both, a plain-space matcher can no longer tell a correct render from a broken one. Turning off collapsing keeps the test meaningful; matching a plain space quietly guts it.

Why muetal is full of NBSP

Law 2 (mono owns every number) routes all numeric output through one component, so U+00A0 shows up in two independent places:

  • formatMeasure() joins value and unit with \u00A0 so 3 mm never wraps across a line — packages/ui/src/lib/format.ts.
  • Hungarian’s thousands separator is itself U+00A0. formatNumber(1200, 'hu') returns 1 + U+00A0 + 200, so formatMeasure(1200, 'db', 'hu') contains two no-break spaces.

Related, verified: Intl.NumberFormat('hu-HU').format(1000) returns "1000" — no grouping at all for 4-digit values. muetal passes useGrouping: true explicitly to force 1 + U+00A0 + 000. If you drop that option, the grouping tests fail rather than the NBSP ones.

Applies beyond getByText

  • getByRole(role, { name }) matches the accessible name, whose computation also normalizes whitespace. Do not try to assert an NBSP through name:; assert on the text node instead.
  • toHaveTextContent (jest-dom) normalizes whitespace by default and takes { normalizeWhitespace: false } as a third argument. muetal does not install jest-dom today — worth knowing if it is added later.
  • The same collapse applies to any Unicode space the formatter may emit: narrow no-break space (U+202F), thin space (U+2009), figure space (U+2007). Some locales/Intl versions use these instead of U+00A0, so assert on the value the formatter actually returns rather than on the one you assume.

Debug in one line

Before assuming the formatter is broken, print codepoints:

console.log([...el.textContent!].map(c => c.codePointAt(0)!.toString(16)))

a0 in the output means the render was right and the query was wrong.

  • muetal — project overview and the three laws
  • bsd-sed-unicode-escape-gotcha — the sibling trap: writing \u00A0 into a source file from the shell and getting the wrong bytes