Mocks, simulators, emulators and compiler checks are never reported here as hardware validation.
The test suite proves the bookkeeping; only a tag proves the radio.
1.0.0; this grid is what makes that
reproducible — and extendable, because no one person owns every chipset, OS version
and tag type worth covering.
Before you trust a combination
Run the rows that apply to the device, chipset and tag type you are shipping on.
When something misbehaves
Find the row and quote it. “Row 17 fails on a Pixel 6a, Android 15” can be acted on; “NFC
doesn’t work” cannot.
How to use this
Each row is a claim the library makes, the smallest setup that tests it, and what “passed” means. Record the device, the OS version and the date next to the row when you run it — a row passing on one Android version says nothing about another, and the version is the part people forget to write down. Legend: ✅ passed · ❌ failed · ⬜ not recorded hereThe columns start empty on purpose. Rather than carry one person’s results as though they were
universal, this records only what has been reported against a named device and OS version.
Reading, both platforms
Android only
Card emulation, Android only
iOS only
Web
The soak run
Row 45, and the one worth doing even if nothing else is: 30 consecutive operations per platform, without restarting the app. The automated half of this already runs in CI —src/core/__tests__/soak.test.ts
performs thirty iterations of every entry point and asserts the library ends with no
open session, no native listener and every handle released. That proves the
bookkeeping. What it cannot prove is that the platform agrees, which is what this
row is for.
Procedure:
- Read the same tag thirty times with
withTag, without restarting. - Thirty times, start a scan and cancel it before presenting a tag.
- Thirty times, present a tag and pull it away mid-operation.
- Thirty times, open a session and background the app before closing it.
- Then read a tag normally.
A soak result is worth reporting whether it holds or not — it is the one piece of
evidence CI cannot produce.
Device information, Android only
The two rows that need no tag at all. They describe the device, so a wrong answer is visible the moment you look at the phone.A
null from row 46 on an API 34 device is a pass, not a failure: filling those numbers in is
the manufacturer’s choice and many have not. What would be a failure is a throw, or numbers that
do not match the hardware in your hand. Worth reporting either way, with the exact model.The rows most worth covering
Not every row is equally informative. If you are only going to run some:- Two Android devices with different NFC controllers, one with MIFARE Classic and one without. Rows 13 and 14 are the whole reason.
- One iPhone at the floor (iOS 16.4) and one current. Row 34’s timing and row 37’s diagnosis are the ones that differ.
- Every tag type in the first table. They are cheap, and each one has caught a real bug in some library.
- Row 21 on an API 37 device, because it is the only place this library makes an inference it has not been able to check. If it turns out to be wrong, the fix is a one-line default and the docs already say it is provisional.
- Row 45 on both platforms.
- Row 46 on any Android 14 phone. It costs one call and no tag, and how many devices actually fill the numbers in is not something the documentation anywhere answers.
Reporting a result is a pull request against this page, or an issue quoting the row number, the
device, the OS version and the date. Both are equally welcome, and a failure is more useful than a
pass.