Skip to main content
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.
This page is the checklist for the radio half. The library was validated on the maintainer’s own devices and tags before 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 here
The 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:
  1. Read the same tag thirty times with withTag, without restarting.
  2. Thirty times, start a scan and cancel it before presenting a tag.
  3. Thirty times, present a tag and pull it away mid-operation.
  4. Thirty times, open a session and background the app before closing it.
  5. Then read a tag normally.
Passed when the thirtieth iteration behaves like the first, and step 5 still works. Failed if anything starts reporting systemBusy, if scanning stops starting, or if latency grows across iterations — all three are the signature of a session or listener that was not released.
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.
Rows 39 and 40 need an entitlement Apple grants case by case, and rows 30 to 33 need a terminal, so those stay thin until somebody who has one reports back. They are implemented and compile against the real frameworks; what they lack is a witness.
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.