diff --git a/examples/basic/.eslintignore b/examples/basic/.eslintignore
deleted file mode 100644
index bda2aca85..000000000
--- a/examples/basic/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-jest-setup.ts
diff --git a/examples/basic/.eslintrc b/examples/basic/.eslintrc
deleted file mode 100644
index 59f14136a..000000000
--- a/examples/basic/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@callstack"
-}
diff --git a/examples/basic/eslint.config.mjs b/examples/basic/eslint.config.mjs
new file mode 100644
index 000000000..8023fb27a
--- /dev/null
+++ b/examples/basic/eslint.config.mjs
@@ -0,0 +1,13 @@
+import testingLibrary from 'eslint-plugin-testing-library';
+import tseslint from 'typescript-eslint';
+
+export default [
+ {
+ ignores: ['node_modules/**', 'jest-setup.ts'],
+ },
+ ...tseslint.configs.recommended,
+ {
+ files: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
+ ...testingLibrary.configs['flat/react'],
+ },
+];
diff --git a/examples/basic/package.json b/examples/basic/package.json
index 8ad7c2cd9..57c248bae 100644
--- a/examples/basic/package.json
+++ b/examples/basic/package.json
@@ -22,13 +22,14 @@
"devDependencies": {
"@babel/core": "^7.24.0",
"@testing-library/react-native": "^14.0.0-beta.0",
- "@types/eslint": "^8.56.10",
"@types/jest": "^29.5.12",
"@types/react": "~19.1.10",
- "eslint": "^8.57.0",
+ "eslint": "^9.0.0",
+ "eslint-plugin-testing-library": "^7.1.1",
"jest": "^29.7.0",
"test-renderer": "0.14.0",
- "typescript": "~5.9.2"
+ "typescript": "~5.9.2",
+ "typescript-eslint": "^8.0.0"
},
"private": true,
"packageManager": "yarn@4.0.1"
diff --git a/examples/basic/yarn.lock b/examples/basic/yarn.lock
index 94459f86d..4c26f570d 100644
--- a/examples/basic/yarn.lock
+++ b/examples/basic/yarn.lock
@@ -1236,45 +1236,91 @@ __metadata:
languageName: node
linkType: hard
-"@eslint-community/eslint-utils@npm:^4.2.0":
- version: 4.7.0
- resolution: "@eslint-community/eslint-utils@npm:4.7.0"
+"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1":
+ version: 4.9.1
+ resolution: "@eslint-community/eslint-utils@npm:4.9.1"
dependencies:
eslint-visitor-keys: "npm:^3.4.3"
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
- checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf
+ checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02
languageName: node
linkType: hard
-"@eslint-community/regexpp@npm:^4.6.1":
- version: 4.12.1
- resolution: "@eslint-community/regexpp@npm:4.12.1"
- checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6
+"@eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2":
+ version: 4.12.2
+ resolution: "@eslint-community/regexpp@npm:4.12.2"
+ checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d
languageName: node
linkType: hard
-"@eslint/eslintrc@npm:^2.1.4":
- version: 2.1.4
- resolution: "@eslint/eslintrc@npm:2.1.4"
+"@eslint/config-array@npm:^0.21.1":
+ version: 0.21.1
+ resolution: "@eslint/config-array@npm:0.21.1"
+ dependencies:
+ "@eslint/object-schema": "npm:^2.1.7"
+ debug: "npm:^4.3.1"
+ minimatch: "npm:^3.1.2"
+ checksum: 10c0/2f657d4edd6ddcb920579b72e7a5b127865d4c3fb4dda24f11d5c4f445a93ca481aebdbd6bf3291c536f5d034458dbcbb298ee3b698bc6c9dd02900fe87eec3c
+ languageName: node
+ linkType: hard
+
+"@eslint/config-helpers@npm:^0.4.2":
+ version: 0.4.2
+ resolution: "@eslint/config-helpers@npm:0.4.2"
+ dependencies:
+ "@eslint/core": "npm:^0.17.0"
+ checksum: 10c0/92efd7a527b2d17eb1a148409d71d80f9ac160b565ac73ee092252e8bf08ecd08670699f46b306b94f13d22e88ac88a612120e7847570dd7cdc72f234d50dcb4
+ languageName: node
+ linkType: hard
+
+"@eslint/core@npm:^0.17.0":
+ version: 0.17.0
+ resolution: "@eslint/core@npm:0.17.0"
+ dependencies:
+ "@types/json-schema": "npm:^7.0.15"
+ checksum: 10c0/9a580f2246633bc752298e7440dd942ec421860d1946d0801f0423830e67887e4aeba10ab9a23d281727a978eb93d053d1922a587d502942a713607f40ed704e
+ languageName: node
+ linkType: hard
+
+"@eslint/eslintrc@npm:^3.3.1":
+ version: 3.3.3
+ resolution: "@eslint/eslintrc@npm:3.3.3"
dependencies:
ajv: "npm:^6.12.4"
debug: "npm:^4.3.2"
- espree: "npm:^9.6.0"
- globals: "npm:^13.19.0"
+ espree: "npm:^10.0.1"
+ globals: "npm:^14.0.0"
ignore: "npm:^5.2.0"
import-fresh: "npm:^3.2.1"
- js-yaml: "npm:^4.1.0"
+ js-yaml: "npm:^4.1.1"
minimatch: "npm:^3.1.2"
strip-json-comments: "npm:^3.1.1"
- checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573
+ checksum: 10c0/532c7acc7ddd042724c28b1f020bd7bf148fcd4653bb44c8314168b5f772508c842ce4ee070299cac51c5c5757d2124bdcfcef5551c8c58ff9986e3e17f2260d
+ languageName: node
+ linkType: hard
+
+"@eslint/js@npm:9.39.2":
+ version: 9.39.2
+ resolution: "@eslint/js@npm:9.39.2"
+ checksum: 10c0/00f51c52b04ac79faebfaa65a9652b2093b9c924e945479f1f3945473f78aee83cbc76c8d70bbffbf06f7024626575b16d97b66eab16182e1d0d39daff2f26f5
+ languageName: node
+ linkType: hard
+
+"@eslint/object-schema@npm:^2.1.7":
+ version: 2.1.7
+ resolution: "@eslint/object-schema@npm:2.1.7"
+ checksum: 10c0/936b6e499853d1335803f556d526c86f5fe2259ed241bc665000e1d6353828edd913feed43120d150adb75570cae162cf000b5b0dfc9596726761c36b82f4e87
languageName: node
linkType: hard
-"@eslint/js@npm:8.57.1":
- version: 8.57.1
- resolution: "@eslint/js@npm:8.57.1"
- checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223
+"@eslint/plugin-kit@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "@eslint/plugin-kit@npm:0.4.1"
+ dependencies:
+ "@eslint/core": "npm:^0.17.0"
+ levn: "npm:^0.4.1"
+ checksum: 10c0/51600f78b798f172a9915dffb295e2ffb44840d583427bc732baf12ecb963eb841b253300e657da91d890f4b323d10a1bd12934bf293e3018d8bb66fdce5217b
languageName: node
linkType: hard
@@ -1681,14 +1727,20 @@ __metadata:
languageName: node
linkType: hard
-"@humanwhocodes/config-array@npm:^0.13.0":
- version: 0.13.0
- resolution: "@humanwhocodes/config-array@npm:0.13.0"
+"@humanfs/core@npm:^0.19.1":
+ version: 0.19.1
+ resolution: "@humanfs/core@npm:0.19.1"
+ checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67
+ languageName: node
+ linkType: hard
+
+"@humanfs/node@npm:^0.16.6":
+ version: 0.16.7
+ resolution: "@humanfs/node@npm:0.16.7"
dependencies:
- "@humanwhocodes/object-schema": "npm:^2.0.3"
- debug: "npm:^4.3.1"
- minimatch: "npm:^3.0.5"
- checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e
+ "@humanfs/core": "npm:^0.19.1"
+ "@humanwhocodes/retry": "npm:^0.4.0"
+ checksum: 10c0/9f83d3cf2cfa37383e01e3cdaead11cd426208e04c44adcdd291aa983aaf72d7d3598844d2fe9ce54896bb1bf8bd4b56883376611c8905a19c44684642823f30
languageName: node
linkType: hard
@@ -1699,10 +1751,10 @@ __metadata:
languageName: node
linkType: hard
-"@humanwhocodes/object-schema@npm:^2.0.3":
- version: 2.0.3
- resolution: "@humanwhocodes/object-schema@npm:2.0.3"
- checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c
+"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2":
+ version: 0.4.3
+ resolution: "@humanwhocodes/retry@npm:0.4.3"
+ checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42
languageName: node
linkType: hard
@@ -2078,33 +2130,6 @@ __metadata:
languageName: node
linkType: hard
-"@nodelib/fs.scandir@npm:2.1.5":
- version: 2.1.5
- resolution: "@nodelib/fs.scandir@npm:2.1.5"
- dependencies:
- "@nodelib/fs.stat": "npm:2.0.5"
- run-parallel: "npm:^1.1.9"
- checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb
- languageName: node
- linkType: hard
-
-"@nodelib/fs.stat@npm:2.0.5":
- version: 2.0.5
- resolution: "@nodelib/fs.stat@npm:2.0.5"
- checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d
- languageName: node
- linkType: hard
-
-"@nodelib/fs.walk@npm:^1.2.8":
- version: 1.2.8
- resolution: "@nodelib/fs.walk@npm:1.2.8"
- dependencies:
- "@nodelib/fs.scandir": "npm:2.1.5"
- fastq: "npm:^1.6.0"
- checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1
- languageName: node
- linkType: hard
-
"@npmcli/agent@npm:^3.0.0":
version: 3.0.0
resolution: "@npmcli/agent@npm:3.0.0"
@@ -2410,17 +2435,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/eslint@npm:^8.56.10":
- version: 8.56.12
- resolution: "@types/eslint@npm:8.56.12"
- dependencies:
- "@types/estree": "npm:*"
- "@types/json-schema": "npm:*"
- checksum: 10c0/e4ca426abe9d55f82b69a3250bec78b6d340ad1e567f91c97ecc59d3b2d6a1d8494955ac62ad0ea14b97519db580611c02be8277cbea370bdfb0f96aa2910504
- languageName: node
- linkType: hard
-
-"@types/estree@npm:*":
+"@types/estree@npm:^1.0.6":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5
@@ -2471,7 +2486,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/json-schema@npm:*":
+"@types/json-schema@npm:^7.0.15":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
@@ -2528,7 +2543,142 @@ __metadata:
languageName: node
linkType: hard
-"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0":
+"@typescript-eslint/eslint-plugin@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/eslint-plugin@npm:8.54.0"
+ dependencies:
+ "@eslint-community/regexpp": "npm:^4.12.2"
+ "@typescript-eslint/scope-manager": "npm:8.54.0"
+ "@typescript-eslint/type-utils": "npm:8.54.0"
+ "@typescript-eslint/utils": "npm:8.54.0"
+ "@typescript-eslint/visitor-keys": "npm:8.54.0"
+ ignore: "npm:^7.0.5"
+ natural-compare: "npm:^1.4.0"
+ ts-api-utils: "npm:^2.4.0"
+ peerDependencies:
+ "@typescript-eslint/parser": ^8.54.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/e533c8285880b883e02a833f378597c2776e6b0c20a5935440e2a02c1c42f40069a8badcf6d581bb4ec35a6856a806c4b66674c1c15c33cd64cc6b9c0cdd1dad
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/parser@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/parser@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/scope-manager": "npm:8.54.0"
+ "@typescript-eslint/types": "npm:8.54.0"
+ "@typescript-eslint/typescript-estree": "npm:8.54.0"
+ "@typescript-eslint/visitor-keys": "npm:8.54.0"
+ debug: "npm:^4.4.3"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/60a1cfe94bc23086f03701640f4d83d7e37b8f4d729011e0f029e5accf2b3d099c50938c0a798a399e86046279432ff663f33102ba4338c4c82f7acead2bcbac
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/project-service@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/project-service@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/tsconfig-utils": "npm:^8.54.0"
+ "@typescript-eslint/types": "npm:^8.54.0"
+ debug: "npm:^4.4.3"
+ peerDependencies:
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/3392ae259199021a80616a44d9484d1c363f61bc5c631dff2d08c6a906c98716a20caa7b832b8970120a1eb1eb2de3ee890cd527d6edb04f532f4e48a690a792
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/scope-manager@npm:8.54.0, @typescript-eslint/scope-manager@npm:^8.51.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/scope-manager@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.54.0"
+ "@typescript-eslint/visitor-keys": "npm:8.54.0"
+ checksum: 10c0/794740a5c0c1afc38d71e6bc59cc62870286e40d99f15e9760e76fb3d4197e961ee151c286c428535c404f5137721242a14da21350b749d0feb1f589f167814f
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/tsconfig-utils@npm:8.54.0, @typescript-eslint/tsconfig-utils@npm:^8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0"
+ peerDependencies:
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/e8598b0f051650c085d749002138d12249a3efd03e7de02e9e7913939dddd649d159b91f29ca3d28f5ee798b3f528a7195688e23c5e0b315d534e7af20a0c99a
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/type-utils@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/type-utils@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.54.0"
+ "@typescript-eslint/typescript-estree": "npm:8.54.0"
+ "@typescript-eslint/utils": "npm:8.54.0"
+ debug: "npm:^4.4.3"
+ ts-api-utils: "npm:^2.4.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/ad807800d8b2662f823505249a84a6f5b1246b192a7ff08c49f298e220e4d9bb3d76f1f0852510421e030161604a4b939bff87f11b9074f118a3bd1d26139c6f
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/types@npm:8.54.0, @typescript-eslint/types@npm:^8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/types@npm:8.54.0"
+ checksum: 10c0/2219594fe5e8931ff91fd1b7a2606d33cd4f093d43f9ca71bcaa37f106ef79ad51f830dea51392f7e3d8bca77f7077ef98733f87bc008fad2f0bbd9ea5fb8a40
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/typescript-estree@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/typescript-estree@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/project-service": "npm:8.54.0"
+ "@typescript-eslint/tsconfig-utils": "npm:8.54.0"
+ "@typescript-eslint/types": "npm:8.54.0"
+ "@typescript-eslint/visitor-keys": "npm:8.54.0"
+ debug: "npm:^4.4.3"
+ minimatch: "npm:^9.0.5"
+ semver: "npm:^7.7.3"
+ tinyglobby: "npm:^0.2.15"
+ ts-api-utils: "npm:^2.4.0"
+ peerDependencies:
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/1a1a7c0a318e71f3547ab5573198d36165ea152c50447ef92e6326303f9a5c397606201ba80c7b86a725dcdd2913e924be94466a0c33b1b0c3ee852059e646b6
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/utils@npm:8.54.0, @typescript-eslint/utils@npm:^8.51.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/utils@npm:8.54.0"
+ dependencies:
+ "@eslint-community/eslint-utils": "npm:^4.9.1"
+ "@typescript-eslint/scope-manager": "npm:8.54.0"
+ "@typescript-eslint/types": "npm:8.54.0"
+ "@typescript-eslint/typescript-estree": "npm:8.54.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/949a97dca8024d39666e04ecdf2d4e12722f5064c387901e72bdcc7adafb96cf650a070dc79f9dd46fa1aae6ac2b5eac5ae3fe5a6979385208c28809a1bd143f
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/visitor-keys@npm:8.54.0":
+ version: 8.54.0
+ resolution: "@typescript-eslint/visitor-keys@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:8.54.0"
+ eslint-visitor-keys: "npm:^4.2.1"
+ checksum: 10c0/f83a9aa92f7f4d1fdb12cbca28c6f5704c36371264606b456388b2c869fc61e73c86d3736556e1bb6e253f3a607128b5b1bf6c68395800ca06f18705576faadd
+ languageName: node
+ linkType: hard
+
+"@ungap/structured-clone@npm:^1.3.0":
version: 1.3.0
resolution: "@ungap/structured-clone@npm:1.3.0"
checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a
@@ -2599,7 +2749,7 @@ __metadata:
languageName: node
linkType: hard
-"acorn@npm:^8.14.0, acorn@npm:^8.9.0":
+"acorn@npm:^8.14.0, acorn@npm:^8.15.0":
version: 8.15.0
resolution: "acorn@npm:8.15.0"
bin:
@@ -3426,7 +3576,7 @@ __metadata:
languageName: node
linkType: hard
-"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
+"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
dependencies:
@@ -3490,6 +3640,18 @@ __metadata:
languageName: node
linkType: hard
+"debug@npm:^4.4.3":
+ version: 4.4.3
+ resolution: "debug@npm:4.4.3"
+ dependencies:
+ ms: "npm:^2.1.3"
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6
+ languageName: node
+ linkType: hard
+
"dedent@npm:^1.0.0":
version: 1.6.0
resolution: "dedent@npm:1.6.0"
@@ -3574,15 +3736,6 @@ __metadata:
languageName: node
linkType: hard
-"doctrine@npm:^3.0.0":
- version: 3.0.0
- resolution: "doctrine@npm:3.0.0"
- dependencies:
- esutils: "npm:^2.0.2"
- checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520
- languageName: node
- linkType: hard
-
"dotenv-expand@npm:~11.0.6":
version: 11.0.7
resolution: "dotenv-expand@npm:11.0.7"
@@ -3752,79 +3905,99 @@ __metadata:
languageName: node
linkType: hard
-"eslint-scope@npm:^7.2.2":
- version: 7.2.2
- resolution: "eslint-scope@npm:7.2.2"
+"eslint-plugin-testing-library@npm:^7.1.1":
+ version: 7.15.4
+ resolution: "eslint-plugin-testing-library@npm:7.15.4"
+ dependencies:
+ "@typescript-eslint/scope-manager": "npm:^8.51.0"
+ "@typescript-eslint/utils": "npm:^8.51.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ checksum: 10c0/5c35dd1c0dbb6bbbae5f379c4508d0efd377fd99ba75ee63607650a7c695ae88240984d547329a18a5d0ba3126d51c1cdf4b904856242214dcfac3f13835f051
+ languageName: node
+ linkType: hard
+
+"eslint-scope@npm:^8.4.0":
+ version: 8.4.0
+ resolution: "eslint-scope@npm:8.4.0"
dependencies:
esrecurse: "npm:^4.3.0"
estraverse: "npm:^5.2.0"
- checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116
+ checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0
languageName: node
linkType: hard
-"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
+"eslint-visitor-keys@npm:^3.4.3":
version: 3.4.3
resolution: "eslint-visitor-keys@npm:3.4.3"
checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820
languageName: node
linkType: hard
-"eslint@npm:^8.57.0":
- version: 8.57.1
- resolution: "eslint@npm:8.57.1"
+"eslint-visitor-keys@npm:^4.2.1":
+ version: 4.2.1
+ resolution: "eslint-visitor-keys@npm:4.2.1"
+ checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43
+ languageName: node
+ linkType: hard
+
+"eslint@npm:^9.0.0":
+ version: 9.39.2
+ resolution: "eslint@npm:9.39.2"
dependencies:
- "@eslint-community/eslint-utils": "npm:^4.2.0"
- "@eslint-community/regexpp": "npm:^4.6.1"
- "@eslint/eslintrc": "npm:^2.1.4"
- "@eslint/js": "npm:8.57.1"
- "@humanwhocodes/config-array": "npm:^0.13.0"
+ "@eslint-community/eslint-utils": "npm:^4.8.0"
+ "@eslint-community/regexpp": "npm:^4.12.1"
+ "@eslint/config-array": "npm:^0.21.1"
+ "@eslint/config-helpers": "npm:^0.4.2"
+ "@eslint/core": "npm:^0.17.0"
+ "@eslint/eslintrc": "npm:^3.3.1"
+ "@eslint/js": "npm:9.39.2"
+ "@eslint/plugin-kit": "npm:^0.4.1"
+ "@humanfs/node": "npm:^0.16.6"
"@humanwhocodes/module-importer": "npm:^1.0.1"
- "@nodelib/fs.walk": "npm:^1.2.8"
- "@ungap/structured-clone": "npm:^1.2.0"
+ "@humanwhocodes/retry": "npm:^0.4.2"
+ "@types/estree": "npm:^1.0.6"
ajv: "npm:^6.12.4"
chalk: "npm:^4.0.0"
- cross-spawn: "npm:^7.0.2"
+ cross-spawn: "npm:^7.0.6"
debug: "npm:^4.3.2"
- doctrine: "npm:^3.0.0"
escape-string-regexp: "npm:^4.0.0"
- eslint-scope: "npm:^7.2.2"
- eslint-visitor-keys: "npm:^3.4.3"
- espree: "npm:^9.6.1"
- esquery: "npm:^1.4.2"
+ eslint-scope: "npm:^8.4.0"
+ eslint-visitor-keys: "npm:^4.2.1"
+ espree: "npm:^10.4.0"
+ esquery: "npm:^1.5.0"
esutils: "npm:^2.0.2"
fast-deep-equal: "npm:^3.1.3"
- file-entry-cache: "npm:^6.0.1"
+ file-entry-cache: "npm:^8.0.0"
find-up: "npm:^5.0.0"
glob-parent: "npm:^6.0.2"
- globals: "npm:^13.19.0"
- graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.0"
imurmurhash: "npm:^0.1.4"
is-glob: "npm:^4.0.0"
- is-path-inside: "npm:^3.0.3"
- js-yaml: "npm:^4.1.0"
json-stable-stringify-without-jsonify: "npm:^1.0.1"
- levn: "npm:^0.4.1"
lodash.merge: "npm:^4.6.2"
minimatch: "npm:^3.1.2"
natural-compare: "npm:^1.4.0"
optionator: "npm:^0.9.3"
- strip-ansi: "npm:^6.0.1"
- text-table: "npm:^0.2.0"
+ peerDependencies:
+ jiti: "*"
+ peerDependenciesMeta:
+ jiti:
+ optional: true
bin:
eslint: bin/eslint.js
- checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1
+ checksum: 10c0/bb88ca8fd16bb7e1ac3e13804c54d41c583214460c0faa7b3e7c574e69c5600c7122295500fb4b0c06067831111db740931e98da1340329527658e1cf80073d3
languageName: node
linkType: hard
-"espree@npm:^9.6.0, espree@npm:^9.6.1":
- version: 9.6.1
- resolution: "espree@npm:9.6.1"
+"espree@npm:^10.0.1, espree@npm:^10.4.0":
+ version: 10.4.0
+ resolution: "espree@npm:10.4.0"
dependencies:
- acorn: "npm:^8.9.0"
+ acorn: "npm:^8.15.0"
acorn-jsx: "npm:^5.3.2"
- eslint-visitor-keys: "npm:^3.4.1"
- checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460
+ eslint-visitor-keys: "npm:^4.2.1"
+ checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b
languageName: node
linkType: hard
@@ -3838,12 +4011,12 @@ __metadata:
languageName: node
linkType: hard
-"esquery@npm:^1.4.2":
- version: 1.6.0
- resolution: "esquery@npm:1.6.0"
+"esquery@npm:^1.5.0":
+ version: 1.7.0
+ resolution: "esquery@npm:1.7.0"
dependencies:
estraverse: "npm:^5.1.0"
- checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2
+ checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793
languageName: node
linkType: hard
@@ -4108,15 +4281,6 @@ __metadata:
languageName: node
linkType: hard
-"fastq@npm:^1.6.0":
- version: 1.19.1
- resolution: "fastq@npm:1.19.1"
- dependencies:
- reusify: "npm:^1.0.4"
- checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630
- languageName: node
- linkType: hard
-
"fb-watchman@npm:^2.0.0":
version: 2.0.2
resolution: "fb-watchman@npm:2.0.2"
@@ -4172,12 +4336,12 @@ __metadata:
languageName: node
linkType: hard
-"file-entry-cache@npm:^6.0.1":
- version: 6.0.1
- resolution: "file-entry-cache@npm:6.0.1"
+"file-entry-cache@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "file-entry-cache@npm:8.0.0"
dependencies:
- flat-cache: "npm:^3.0.4"
- checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd
+ flat-cache: "npm:^4.0.0"
+ checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638
languageName: node
linkType: hard
@@ -4225,14 +4389,13 @@ __metadata:
languageName: node
linkType: hard
-"flat-cache@npm:^3.0.4":
- version: 3.2.0
- resolution: "flat-cache@npm:3.2.0"
+"flat-cache@npm:^4.0.0":
+ version: 4.0.1
+ resolution: "flat-cache@npm:4.0.1"
dependencies:
flatted: "npm:^3.2.9"
- keyv: "npm:^4.5.3"
- rimraf: "npm:^3.0.2"
- checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75
+ keyv: "npm:^4.5.4"
+ checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc
languageName: node
linkType: hard
@@ -4417,12 +4580,10 @@ __metadata:
languageName: node
linkType: hard
-"globals@npm:^13.19.0":
- version: 13.24.0
- resolution: "globals@npm:13.24.0"
- dependencies:
- type-fest: "npm:^0.20.2"
- checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd
+"globals@npm:^14.0.0":
+ version: 14.0.0
+ resolution: "globals@npm:14.0.0"
+ checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d
languageName: node
linkType: hard
@@ -4433,13 +4594,6 @@ __metadata:
languageName: node
linkType: hard
-"graphemer@npm:^1.4.0":
- version: 1.4.0
- resolution: "graphemer@npm:1.4.0"
- checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31
- languageName: node
- linkType: hard
-
"has-flag@npm:^3.0.0":
version: 3.0.0
resolution: "has-flag@npm:3.0.0"
@@ -4588,6 +4742,13 @@ __metadata:
languageName: node
linkType: hard
+"ignore@npm:^7.0.5":
+ version: 7.0.5
+ resolution: "ignore@npm:7.0.5"
+ checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d
+ languageName: node
+ linkType: hard
+
"image-size@npm:^1.0.2":
version: 1.2.1
resolution: "image-size@npm:1.2.1"
@@ -4749,13 +4910,6 @@ __metadata:
languageName: node
linkType: hard
-"is-path-inside@npm:^3.0.3":
- version: 3.0.3
- resolution: "is-path-inside@npm:3.0.3"
- checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05
- languageName: node
- linkType: hard
-
"is-stream@npm:^2.0.0":
version: 2.0.1
resolution: "is-stream@npm:2.0.1"
@@ -5364,6 +5518,17 @@ __metadata:
languageName: node
linkType: hard
+"js-yaml@npm:^4.1.1":
+ version: 4.1.1
+ resolution: "js-yaml@npm:4.1.1"
+ dependencies:
+ argparse: "npm:^2.0.1"
+ bin:
+ js-yaml: bin/js-yaml.js
+ checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7
+ languageName: node
+ linkType: hard
+
"jsbn@npm:1.1.0":
version: 1.1.0
resolution: "jsbn@npm:1.1.0"
@@ -5433,7 +5598,7 @@ __metadata:
languageName: node
linkType: hard
-"keyv@npm:^4.5.3":
+"keyv@npm:^4.5.4":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
dependencies:
@@ -6056,7 +6221,7 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
+"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
dependencies:
@@ -6065,7 +6230,7 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4":
+"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
@@ -6825,13 +6990,6 @@ __metadata:
languageName: node
linkType: hard
-"queue-microtask@npm:^1.2.2":
- version: 1.2.3
- resolution: "queue-microtask@npm:1.2.3"
- checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102
- languageName: node
- linkType: hard
-
"queue@npm:6.0.2":
version: 6.0.2
resolution: "queue@npm:6.0.2"
@@ -7201,13 +7359,6 @@ __metadata:
languageName: node
linkType: hard
-"reusify@npm:^1.0.4":
- version: 1.1.0
- resolution: "reusify@npm:1.1.0"
- checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa
- languageName: node
- linkType: hard
-
"rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
@@ -7225,10 +7376,10 @@ __metadata:
dependencies:
"@babel/core": "npm:^7.24.0"
"@testing-library/react-native": "npm:^14.0.0-beta.0"
- "@types/eslint": "npm:^8.56.10"
"@types/jest": "npm:^29.5.12"
"@types/react": "npm:~19.1.10"
- eslint: "npm:^8.57.0"
+ eslint: "npm:^9.0.0"
+ eslint-plugin-testing-library: "npm:^7.1.1"
expo: "npm:^54.0.32"
expo-status-bar: "npm:~3.0.9"
jest: "npm:^29.7.0"
@@ -7239,18 +7390,10 @@ __metadata:
react-native-web: "npm:^0.21.0"
test-renderer: "npm:0.14.0"
typescript: "npm:~5.9.2"
+ typescript-eslint: "npm:^8.0.0"
languageName: unknown
linkType: soft
-"run-parallel@npm:^1.1.9":
- version: 1.2.0
- resolution: "run-parallel@npm:1.2.0"
- dependencies:
- queue-microtask: "npm:^1.2.2"
- checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39
- languageName: node
- linkType: hard
-
"safe-buffer@npm:5.2.1":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
@@ -7304,6 +7447,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.7.3":
+ version: 7.7.4
+ resolution: "semver@npm:7.7.4"
+ bin:
+ semver: bin/semver.js
+ checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2
+ languageName: node
+ linkType: hard
+
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
@@ -7839,13 +7991,6 @@ __metadata:
languageName: node
linkType: hard
-"text-table@npm:^0.2.0":
- version: 0.2.0
- resolution: "text-table@npm:0.2.0"
- checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c
- languageName: node
- linkType: hard
-
"thenify-all@npm:^1.0.0":
version: 1.6.0
resolution: "thenify-all@npm:1.6.0"
@@ -7871,7 +8016,7 @@ __metadata:
languageName: node
linkType: hard
-"tinyglobby@npm:^0.2.11":
+"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
dependencies:
@@ -7921,6 +8066,15 @@ __metadata:
languageName: node
linkType: hard
+"ts-api-utils@npm:^2.4.0":
+ version: 2.4.0
+ resolution: "ts-api-utils@npm:2.4.0"
+ peerDependencies:
+ typescript: ">=4.8.4"
+ checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083
+ languageName: node
+ linkType: hard
+
"ts-interface-checker@npm:^0.1.9":
version: 0.1.13
resolution: "ts-interface-checker@npm:0.1.13"
@@ -7944,13 +8098,6 @@ __metadata:
languageName: node
linkType: hard
-"type-fest@npm:^0.20.2":
- version: 0.20.2
- resolution: "type-fest@npm:0.20.2"
- checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3
- languageName: node
- linkType: hard
-
"type-fest@npm:^0.21.3":
version: 0.21.3
resolution: "type-fest@npm:0.21.3"
@@ -7965,6 +8112,21 @@ __metadata:
languageName: node
linkType: hard
+"typescript-eslint@npm:^8.0.0":
+ version: 8.54.0
+ resolution: "typescript-eslint@npm:8.54.0"
+ dependencies:
+ "@typescript-eslint/eslint-plugin": "npm:8.54.0"
+ "@typescript-eslint/parser": "npm:8.54.0"
+ "@typescript-eslint/typescript-estree": "npm:8.54.0"
+ "@typescript-eslint/utils": "npm:8.54.0"
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: ">=4.8.4 <6.0.0"
+ checksum: 10c0/0ba92aa22c0aa10c88b0f4732950ed64245947f1c4ac17328dff94b43eaeddd3068595788725781fba07a87cc964304a075b3e37f9a86312173498fcc6ab4338
+ languageName: node
+ linkType: hard
+
"typescript@npm:~5.9.2":
version: 5.9.3
resolution: "typescript@npm:5.9.3"
diff --git a/website/docs/13.x/docs/guides/common-mistakes.mdx b/website/docs/13.x/docs/guides/common-mistakes.mdx
new file mode 100644
index 000000000..984ce0635
--- /dev/null
+++ b/website/docs/13.x/docs/guides/common-mistakes.mdx
@@ -0,0 +1,587 @@
+# Common Mistakes with React Native Testing Library
+
+> **Note:** This guide is adapted from Kent C. Dodds' article ["Common mistakes with React Testing Library"](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) for React Native Testing Library v13. The original article focuses on web React, but the principles apply to React Native as well. This adaptation includes React Native-specific examples and ARIA-compatible accessibility attributes.
+
+React Native Testing Library guiding principle is:
+
+> "The more your tests resemble the way your software is used, the more confidence they can give you."
+
+This guide outlines some common mistakes people make when using React Native Testing Library and how to avoid them.
+
+## Using the wrong query {#using-the-wrong-query}
+
+Importance: high
+
+React Native Testing Library provides several query types. Here's the priority order:
+
+1. **Queries that reflect user experience:**
+ - `getByRole` - most accessible
+ - `getByLabelText` - accessible label
+ - `getByPlaceholderText` - `TextInput` placeholder text
+ - `getByText` - text content
+ - `getByDisplayValue` - `TextInput` input value
+
+2. **Semantic queries:**
+ - `getByTestId` - only if nothing else works
+
+Here's an example of using the right query:
+
+```tsx
+import { TextInput, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('finds input by label', () => {
+ render(
+
+
+
+ );
+
+ // ✅ Good - uses accessible label
+ const input = screen.getByLabelText('Username');
+
+ // ✅ Also good - uses placeholder
+ const inputByPlaceholder = screen.getByPlaceholderText('Enter username');
+
+ // ❌ Bad - uses testID when accessible queries work
+ // const input = screen.getByTestId('username-input');
+});
+```
+
+## Not using `*ByRole` query most of the time {#not-using-byrole-most-of-the-time}
+
+Importance: high
+
+`getByRole` is the most accessible query and should be your first choice. It queries elements by their semantic role:
+
+```tsx
+import { Pressable, Text, TextInput, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('uses role queries', () => {
+ render(
+
+
+ Submit
+
+
+
+ );
+
+ // ✅ Good - uses role query
+ const button = screen.getByRole('button', { name: 'Submit' });
+ const searchbox = screen.getByRole('searchbox', { name: 'Search' });
+
+ expect(button).toBeOnTheScreen();
+ expect(searchbox).toBeOnTheScreen();
+});
+```
+
+Common roles in React Native include:
+
+- `button` - pressable elements
+- `text` - static text
+- `header` / `heading` - headers
+- `searchbox` - search inputs
+- `switch` - toggle switches
+- `checkbox` - checkboxes
+- `radio` - radio buttons
+- And more...
+
+Note: React Native supports both ARIA-compatible (`role`) and legacy (`accessibilityRole`) props. Prefer `role` for consistency with web standards.
+
+## Using the wrong assertion {#using-the-wrong-assertion}
+
+Importance: high
+
+React Native Testing Library provides built-in Jest matchers. Make sure you're using the right ones:
+
+```tsx
+import { Pressable, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('button is disabled', () => {
+ render(
+
+ Submit
+
+ );
+
+ const button = screen.getByRole('button', { name: 'Submit' });
+
+ // ✅ Good - uses RNTL matcher
+ expect(button).toBeDisabled();
+
+ // ❌ Bad - doesn't use RNTL matcher
+ expect(button.props['aria-disabled']).toBe(true);
+});
+```
+
+Common matchers include:
+
+- `toBeOnTheScreen()` - checks if element is rendered (replaces `toBeInTheDocument()`)
+- `toBeDisabled()` - checks if element is disabled
+- `toHaveTextContent()` - checks text content
+- `toHaveAccessibleName()` - checks accessible name
+- And more...
+
+## Using `query*` variants for anything except checking for non-existence {#using-query-variants-for-anything-except-checking-for-non-existence}
+
+Importance: high
+
+Use `queryBy*` only when checking that an element doesn't exist:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('checks non-existence', () => {
+ render(
+
+ Hello
+
+ );
+
+ // ✅ Good - uses queryBy for non-existence check
+ expect(screen.queryByText('Goodbye')).not.toBeOnTheScreen();
+
+ // ❌ Bad - uses queryBy when element should exist
+ // const element = screen.queryByText('Hello');
+ // expect(element).toBeOnTheScreen();
+
+ // ✅ Good - uses getBy when element should exist
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+## Using `waitFor` to wait for elements that can be queried with `find*` {#using-waitfor-to-wait-for-elements-that-can-be-queried-with-find}
+
+Importance: high
+
+Use `findBy*` queries instead of `waitFor` + `getBy*`:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen, waitFor } from '@testing-library/react-native';
+
+test('waits for element', async () => {
+ const Component = () => {
+ const [show, setShow] = React.useState(false);
+
+ React.useEffect(() => {
+ setTimeout(() => setShow(true), 100);
+ }, []);
+
+ return {show && Loaded};
+ };
+
+ render();
+
+ // ✅ Good - uses findBy query
+ const element = await screen.findByText('Loaded');
+ expect(element).toBeOnTheScreen();
+
+ // ❌ Bad - uses waitFor + getBy
+ // await waitFor(() => {
+ // expect(screen.getByText('Loaded')).toBeOnTheScreen();
+ // });
+});
+```
+
+## Performing side-effects in `waitFor` {#performing-side-effects-in-waitfor}
+
+Importance: high
+
+Don't perform side-effects in `waitFor` callbacks:
+
+```tsx
+import { Pressable, Text, View } from 'react-native';
+import { render, screen, waitFor, fireEvent } from '@testing-library/react-native';
+
+test('avoids side effects in waitFor', async () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+ return (
+
+ setCount(count + 1)}>
+ Increment
+
+ Count: {count}
+
+ );
+ };
+
+ render();
+
+ const button = screen.getByRole('button');
+
+ // ❌ Bad - side effect in waitFor
+ // await waitFor(() => {
+ // fireEvent.press(button);
+ // expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ // });
+
+ // ✅ Good - side effect outside waitFor
+ fireEvent.press(button);
+ await waitFor(() => {
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ });
+});
+```
+
+## Using `UNSAFE_root` to query for elements {#using-unsafe-root-to-query-for-elements}
+
+Importance: high
+
+React Native Testing Library provides an `UNSAFE_root` object that gives access to the root element, but you should avoid using it directly:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render } from '@testing-library/react-native';
+
+test('finds element incorrectly', () => {
+ const { UNSAFE_root } = render(
+
+ Hello
+
+ );
+
+ // ❌ Bad - using UNSAFE_root directly
+ const element = UNSAFE_root.findAll((node) => node.props.testID === 'message')[0];
+
+ // ✅ Good - use proper queries
+ // const element = screen.getByTestId('message');
+});
+```
+
+Instead, use the proper query methods from `screen` or the `render` result. The `UNSAFE_root` is a low-level API that you rarely need.
+
+## Passing an empty callback to `waitFor` {#passing-an-empty-callback-to-waitfor}
+
+Importance: high
+
+Don't pass an empty callback to `waitFor`:
+
+```tsx
+import { View } from 'react-native';
+import { render, waitFor } from '@testing-library/react-native';
+
+test('waits correctly', async () => {
+ render();
+
+ // ❌ Bad - empty callback
+ // await waitFor(() => {});
+
+ // ✅ Good - meaningful assertion
+ await waitFor(() => {
+ expect(screen.getByTestId('test')).toBeOnTheScreen();
+ });
+});
+```
+
+## Not using `screen` {#not-using-screen}
+
+Importance: medium
+
+You can get all the queries from the `render` result:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render } from '@testing-library/react-native';
+
+test('renders component', () => {
+ const { getByText } = render(
+
+ Hello
+
+ );
+
+ expect(getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+But you can also get them from the `screen` object:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('renders component', () => {
+ render(
+
+ Hello
+
+ );
+
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+Using `screen` has several benefits:
+
+1. You don't need to destructure `getByText` from `render`
+2. It's more consistent with the Testing Library ecosystem
+
+## Wrapping things in `act` unnecessarily {#wrapping-things-in-act-unnecessarily}
+
+Importance: medium
+
+React Native Testing Library's `userEvent` methods are already wrapped in `act`, so you don't need to wrap them yourself:
+
+```tsx
+import { Pressable, Text, View } from 'react-native';
+import { render, fireEvent, screen } from '@testing-library/react-native';
+
+test('updates on press', () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+ return (
+
+ setCount(count + 1)}>
+ Count: {count}
+
+
+ );
+ };
+
+ render();
+
+ const button = screen.getByRole('button');
+
+ // ✅ Good - fireEvent is already wrapped in act
+ fireEvent.press(button);
+
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+
+ // ❌ Bad - unnecessary act wrapper
+ // act(() => {
+ // fireEvent.press(button);
+ // });
+});
+```
+
+## Not using User Event API
+
+Importance: medium
+
+`userEvent` provides a more realistic way to simulate user interactions:
+
+```tsx
+import { Pressable, Text, TextInput, View } from 'react-native';
+import { render, screen, userEvent } from '@testing-library/react-native';
+
+test('uses userEvent', async () => {
+ const user = userEvent.setup();
+
+ const Component = () => {
+ const [value, setValue] = React.useState('');
+ return (
+
+
+ setValue('')}>
+ Clear
+
+
+ );
+ };
+
+ render();
+
+ const input = screen.getByLabelText('Name');
+ const button = screen.getByRole('button', { name: 'Clear' });
+
+ // ✅ Good - uses userEvent for realistic interactions
+ await user.type(input, 'John');
+ expect(input).toHaveValue('John');
+
+ await user.press(button);
+ expect(input).toHaveValue('');
+});
+```
+
+`userEvent` methods are async and must be awaited. Available methods include:
+
+- `press()` - simulates a press
+- `longPress()` - simulates long press
+- `type()` - simulates typing
+- `clear()` - clears text input
+- `paste()` - simulates pasting
+- `scrollTo()` - simulates scrolling
+
+## Not querying by text {#not-querying-by-text}
+
+Importance: medium
+
+In React Native, text is rendered in `` components. You should query by the text content that users see:
+
+```tsx
+import { Text, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('finds text correctly', () => {
+ render(
+
+ Hello World
+
+ );
+
+ // ✅ Good - queries by visible text
+ expect(screen.getByText('Hello World')).toBeOnTheScreen();
+
+ // ❌ Bad - queries by testID when text is available
+ // expect(screen.getByTestId('greeting')).toBeOnTheScreen();
+});
+```
+
+## Not using Testing Library ESLint plugins {#not-using-testing-library-eslint-plugins}
+
+Importance: medium
+
+There's an ESLint plugin for Testing Library: [`eslint-plugin-testing-library`](https://github.com/testing-library/eslint-plugin-testing-library). This plugin can help you avoid common mistakes and will automatically fix your code in many cases.
+
+You can install it with:
+
+```bash
+yarn add --dev eslint-plugin-testing-library
+```
+
+And configure it in your `eslint.config.js` (flat config):
+
+```js
+import testingLibrary from 'eslint-plugin-testing-library';
+
+export default [testingLibrary.configs['flat/react']];
+```
+
+Note: Unlike React Testing Library, React Native Testing Library has built-in Jest matchers, so you don't need `eslint-plugin-jest-dom`.
+
+## Using `cleanup` {#using-cleanup}
+
+Importance: medium
+
+React Native Testing Library automatically cleans up after each test. You don't need to call `cleanup()` manually unless you're using the `pure` export (which doesn't include automatic cleanup).
+
+If you want to disable automatic cleanup for a specific test, you can use:
+
+```tsx
+import { render } from '@testing-library/react-native/pure';
+
+test('does not cleanup', () => {
+ // This test won't cleanup automatically
+ render();
+ // ... your test
+});
+```
+
+But in most cases, you don't need to worry about cleanup at all - it's handled automatically.
+
+## Using `get*` variants as assertions {#using-get-variants-as-assertions}
+
+Importance: low
+
+`getBy*` queries throw errors when elements aren't found, so they work as assertions. However, for better error messages, you might want to combine them with explicit matchers:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('uses getBy as assertion', () => {
+ render(
+
+ Hello
+
+ );
+
+ // ✅ Good - getBy throws if not found, so it's an assertion
+ const element = screen.getByText('Hello');
+ expect(element).toBeOnTheScreen();
+
+ // ✅ Also good - more explicit
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+
+ // ❌ Bad - redundant assertion
+ // const element = screen.getByText('Hello');
+ // expect(element).not.toBeNull(); // getBy already throws if null
+});
+```
+
+## Having multiple assertions in a single `waitFor` callback {#having-multiple-assertions-in-a-single-waitfor-callback}
+
+Importance: low
+
+Keep `waitFor` callbacks focused on a single assertion:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen, waitFor } from '@testing-library/react-native';
+
+test('waits with single assertion', async () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+
+ React.useEffect(() => {
+ setTimeout(() => setCount(1), 100);
+ }, []);
+
+ return (
+
+ Count: {count}
+
+ );
+ };
+
+ render();
+
+ // ✅ Good - single assertion per waitFor
+ await waitFor(() => {
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ });
+
+ // If you need multiple assertions, do them after waitFor
+ expect(screen.getByText('Count: 1')).toHaveTextContent('Count: 1');
+
+ // ❌ Bad - multiple assertions in waitFor
+ // await waitFor(() => {
+ // expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ // expect(screen.getByText('Count: 1')).toHaveTextContent('Count: 1');
+ // });
+});
+```
+
+## Using `wrapper` as the variable name {#using-wrapper-as-the-variable-name}
+
+Importance: low
+
+This is not really a "mistake" per se, but it's a common pattern that can be improved. When you use the `wrapper` option in `render`, you might be tempted to name your wrapper component `Wrapper`:
+
+```tsx
+import { View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('renders with wrapper', () => {
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+ );
+
+ render(Content, {
+ wrapper: Wrapper,
+ });
+
+ expect(screen.getByTestId('content')).toBeOnTheScreen();
+});
+```
+
+This works fine, but it's more conventional to name it something more descriptive like `ThemeProvider` or `AllTheProviders` (if you're wrapping with multiple providers). This makes it clearer what the wrapper is doing.
+
+## Summary
+
+The key principles to remember:
+
+1. **Use the right query** - Prefer `getByRole` as your first choice, use `findBy*` for async elements, and `queryBy*` only for checking non-existence
+2. **Use proper assertions** - Use RNTL's built-in matchers (`toBeOnTheScreen()`, `toBeDisabled()`, etc.) instead of asserting on props directly
+3. **Handle async operations correctly** - `userEvent` methods are async and must be awaited; `render()` and `fireEvent` are synchronous
+4. **Use `waitFor` correctly** - Avoid side-effects in callbacks, use `findBy*` instead when possible, and keep callbacks focused
+5. **Follow accessibility best practices** - Prefer ARIA attributes (`role`, `aria-label`) over `accessibility*` props
+6. **Organize code well** - Use `screen` over destructuring, prefer `userEvent` over `fireEvent`, and don't use `cleanup()`
+
+By following these principles, your tests will be more maintainable, accessible, and reliable.
diff --git a/website/docs/13.x/docs/guides/llm-guidelines.mdx b/website/docs/13.x/docs/guides/llm-guidelines.mdx
new file mode 100644
index 000000000..f222c5263
--- /dev/null
+++ b/website/docs/13.x/docs/guides/llm-guidelines.mdx
@@ -0,0 +1,222 @@
+# LLM Guidelines for React Native Testing Library
+
+Actionable guidelines for writing tests with React Native Testing Library (RNTL) v13.
+
+## Core APIs
+
+### render
+
+```tsx
+const result = render(, options?); // Sync in v13
+```
+
+| Option | Description |
+| ---------------- | ---------------------------------------------------------------- |
+| `wrapper` | React component to wrap the rendered component (e.g., providers) |
+| `createNodeMock` | Function to create mock refs |
+
+| Return | Description |
+| --------------------- | -------------------------------------- |
+| `rerender(component)` | Re-render with a new component |
+| `unmount()` | Unmount the rendered component |
+| `toJSON()` | Get JSON representation for snapshots |
+| `debug(options?)` | Print the component tree to console |
+| `root` | Root element of the rendered component |
+
+### screen
+
+**Prefer `screen`** over destructuring from `render()`. Provides all query methods after `render()` is called.
+
+```tsx
+render();
+screen.getByRole('button'); // Access queries via screen
+```
+
+### renderHook
+
+```tsx
+const { result, rerender, unmount } = renderHook(() => useMyHook(), options?); // Sync in v13
+```
+
+| Option | Description |
+| -------------- | -------------------------------------------------- |
+| `initialProps` | Initial props passed to the hook |
+| `wrapper` | React component to wrap the hook (e.g., providers) |
+
+| Return | Description |
+| ------------------ | -------------------------------- |
+| `result.current` | Current return value of the hook |
+| `rerender(props?)` | Re-render hook with new props |
+| `unmount()` | Unmount the hook |
+
+## Query Selection
+
+- **Prefer `getByRole`** as first choice for querying elements
+- **Query priority**: `getByRole` → `getByLabelText` → `getByPlaceholderText` → `getByText` → `getByDisplayValue` → `getByTestId` (last resort)
+- **Use `findBy*`** for elements that appear asynchronously (after API calls, timeouts, state updates)
+- **Use `queryBy*` ONLY** for checking non-existence (with `.not.toBeOnTheScreen()`)
+- **Never use `getBy*`** for non-existence checks
+- **Avoid `container.queryAll()`** - use `screen` queries instead
+- **Query by visible text**, not `testID` when text is available
+
+## Assertions
+
+- **Use RNTL matchers** - prefer semantic matchers over prop assertions
+- **Combine queries with matchers**: `expect(screen.getByText('Hello')).toBeOnTheScreen()`
+- **No redundant null checks** - `getBy*` already throws if not found
+
+## Jest Matchers Reference
+
+| Matcher | Description |
+| --------------------------------- | ------------------------------------------------------------------------------------------- |
+| `toBeOnTheScreen()` | Element is present in the element tree |
+| `toBeVisible()` | Element is visible (checks style, `aria-hidden`, `accessibilityElementsHidden`, ancestors) |
+| `toBeEmptyElement()` | Element has no children or text content |
+| `toContainElement(element)` | Element contains another element |
+| `toBeEnabled()` | Element is not disabled (checks `aria-disabled`, `accessibilityState`, ancestors) |
+| `toBeDisabled()` | Element has `aria-disabled` or `accessibilityState={{ disabled: true }}` (checks ancestors) |
+| `toBeBusy()` | Element has `aria-busy` or `accessibilityState={{ busy: true }}` |
+| `toBeChecked()` | Element has `aria-checked` or `accessibilityState={{ checked: true }}` |
+| `toBePartiallyChecked()` | Element has `aria-checked="mixed"` or `accessibilityState={{ checked: 'mixed' }}` |
+| `toBeSelected()` | Element has `aria-selected` or `accessibilityState={{ selected: true }}` |
+| `toBeExpanded()` | Element has `aria-expanded` or `accessibilityState={{ expanded: true }}` |
+| `toBeCollapsed()` | Element has `aria-expanded={false}` or `accessibilityState={{ expanded: false }}` |
+| `toHaveTextContent(text)` | Element has matching text content |
+| `toHaveDisplayValue(value)` | TextInput has matching display value |
+| `toHaveAccessibleName(name?)` | Element has matching `aria-label`, `accessibilityLabel`, or text content |
+| `toHaveAccessibilityValue(value)` | Element has matching `aria-value*` or `accessibilityValue` |
+| `toHaveStyle(style)` | Element has matching style |
+| `toHaveProp(name, value?)` | Element has prop (use semantic matchers when possible) |
+
+## User Interactions
+
+**Prefer `userEvent`** over `fireEvent` for realistic user interaction simulation. `userEvent` triggers the complete event sequence that real users would produce.
+
+### userEvent (Preferred, Async)
+
+```tsx
+const user = userEvent.setup();
+```
+
+| Method | Description |
+| ------------------------------------------ | ----------------------------------------------------------------------------------- |
+| `await user.press(element)` | Press an element (triggers `pressIn`, `pressOut`, `press`) |
+| `await user.longPress(element, options?)` | Long press with optional `{ duration }` |
+| `await user.type(element, text, options?)` | Type into TextInput (triggers `focus`, `keyPress`, `change`, `changeText` per char) |
+| `await user.clear(element)` | Clear TextInput (select all + backspace) |
+| `await user.paste(element, text)` | Paste text into TextInput |
+| `await user.scrollTo(element, options)` | Scroll a ScrollView with `{ y }` or `{ x }` offset |
+
+### fireEvent (Low-level, Sync in v13)
+
+Use only when `userEvent` doesn't support the event or when you need direct control.
+
+| Method | Description |
+| ---------------------------------------- | --------------------------------------------- |
+| `fireEvent(element, eventName, ...data)` | Fire any event by name |
+| `fireEvent.press(element)` | Fire `onPress` only (no `pressIn`/`pressOut`) |
+| `fireEvent.changeText(element, text)` | Fire `onChangeText` directly |
+| `fireEvent.scroll(element, eventData)` | Fire `onScroll` with event data |
+
+## Sync vs Async APIs (v13)
+
+- **`render()`, `fireEvent.*`, `renderHook()` are synchronous** - no `await` needed
+- **`userEvent.*` is asynchronous** - requires `await`
+- **Don't wrap in `act()`** - `render` and `fireEvent` handle it internally
+- **For React 19**: use async variants: `renderAsync()`, `fireEventAsync.*()` with `await`.
+
+## waitFor Usage
+
+- **Use `findBy*`** instead of `waitFor` + `getBy*` when waiting for elements
+- **Never perform side-effects** (like `fireEvent.press()`) inside `waitFor` callbacks
+- **One assertion per `waitFor`** callback
+- **Never pass empty callbacks** - always include a meaningful assertion
+- **Place side-effects before `waitFor`** - perform actions, then wait for result
+
+## Code Organization
+
+- **Use `screen`** instead of destructuring from `render()`: `screen.getByText()` not `const { getByText } = render()`
+- **Prefer `userEvent`** over `fireEvent` for realistic interactions
+- **Don't use `cleanup()`** - handled automatically
+- **Name wrappers descriptively**: `ThemeProvider` not `Wrapper`
+- **Install ESLint plugin**: `eslint-plugin-testing-library`
+
+## Quick Checklist
+
+- Using `getByRole` as first choice?
+- Using `findBy*` for async elements (not `waitFor` + `getBy*`)?
+- Using `queryBy*` only for non-existence?
+- Using RNTL matchers (`toBeOnTheScreen()`, `toBeDisabled()`, etc.)?
+- Using `screen` not destructuring from `render()`?
+- Avoiding side-effects in `waitFor`?
+- Using `userEvent` when appropriate?
+
+## Example: Good Pattern
+
+```tsx
+import { render, screen } from '@testing-library/react-native';
+import userEvent from '@testing-library/react-native';
+import { Pressable, Text, TextInput, View } from 'react-native';
+
+test('user can submit form', async () => {
+ const user = userEvent.setup();
+
+ const Component = () => {
+ const [name, setName] = React.useState('');
+ const [submitted, setSubmitted] = React.useState(false);
+
+ return (
+
+
+ setSubmitted(true)}>
+ Submit
+
+ {submitted && Form submitted!}
+
+ );
+ };
+
+ render();
+
+ // getByRole as first choice
+ const input = screen.getByRole('textbox', { name: 'Name' });
+ const button = screen.getByRole('button', { name: 'Submit' });
+
+ // userEvent for realistic interactions (async)
+ await user.type(input, 'John Doe');
+ await user.press(button);
+
+ // findBy* for async elements
+ const successMessage = await screen.findByRole('alert');
+
+ // RNTL matchers
+ expect(successMessage).toBeOnTheScreen();
+ expect(successMessage).toHaveTextContent('Form submitted!');
+});
+```
+
+## Example: Anti-Patterns
+
+```tsx
+// getBy* for non-existence
+expect(screen.getByText('Error')).not.toBeOnTheScreen();
+
+// waitFor + getBy* instead of findBy*
+await waitFor(() => {
+ expect(screen.getByText('Loaded')).toBeOnTheScreen();
+});
+
+// Side-effect in waitFor
+await waitFor(async () => {
+ fireEvent.press(button);
+ expect(screen.getByText('Result')).toBeOnTheScreen();
+});
+
+// accessibility* props instead of ARIA
+;
+
+// Destructuring from render
+const { getByText } = render();
+```
+
+By following these guidelines, your tests will be more maintainable, accessible, and reliable.
diff --git a/website/docs/14.x/docs/guides/_meta.json b/website/docs/14.x/docs/guides/_meta.json
index 5ae12d7e2..ffb1036dc 100644
--- a/website/docs/14.x/docs/guides/_meta.json
+++ b/website/docs/14.x/docs/guides/_meta.json
@@ -1 +1 @@
-["how-to-query", "troubleshooting", "faq", "community-resources"]
+["how-to-query", "common-mistakes", "troubleshooting", "faq", "community-resources"]
diff --git a/website/docs/14.x/docs/guides/common-mistakes.mdx b/website/docs/14.x/docs/guides/common-mistakes.mdx
new file mode 100644
index 000000000..7982a956e
--- /dev/null
+++ b/website/docs/14.x/docs/guides/common-mistakes.mdx
@@ -0,0 +1,587 @@
+# Common Mistakes with React Native Testing Library
+
+> **Note:** This guide is adapted from Kent C. Dodds' article ["Common mistakes with React Testing Library"](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) for React Native Testing Library v14. The original article focuses on web React, but the principles apply to React Native as well. This adaptation includes React Native-specific examples, async API usage (v14), and ARIA-compatible accessibility attributes.
+
+React Native Testing Library guiding principle is:
+
+> "The more your tests resemble the way your software is used, the more confidence they can give you."
+
+This guide outlines some common mistakes people make when using React Native Testing Library and how to avoid them.
+
+## Using the wrong query {#using-the-wrong-query}
+
+Importance: high
+
+React Native Testing Library provides several query types. Here's the priority order:
+
+1. **Queries that reflect user experience:**
+ - `getByRole` - most accessible
+ - `getByLabelText` - accessible label
+ - `getByPlaceholderText` - `TextInput` placeholder text
+ - `getByText` - text content
+ - `getByDisplayValue` - `TextInput` input value
+
+2. **Semantic queries:**
+ - `getByTestId` - only if nothing else works
+
+Here's an example of using the right query:
+
+```tsx
+import { TextInput, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('finds input by label', async () => {
+ await render(
+
+
+
+ );
+
+ // ✅ Good - uses accessible label
+ const input = screen.getByLabelText('Username');
+
+ // ✅ Also good - uses placeholder
+ const inputByPlaceholder = screen.getByPlaceholderText('Enter username');
+
+ // ❌ Bad - uses testID when accessible queries work
+ // const input = screen.getByTestId('username-input');
+});
+```
+
+## Not using `*ByRole` query most of the time {#not-using-byrole-most-of-the-time}
+
+Importance: high
+
+`getByRole` is the most accessible query and should be your first choice. It queries elements by their semantic role:
+
+```tsx
+import { Pressable, Text, TextInput, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('uses role queries', async () => {
+ await render(
+
+
+ Submit
+
+
+
+ );
+
+ // ✅ Good - uses role query
+ const button = screen.getByRole('button', { name: 'Submit' });
+ const searchbox = screen.getByRole('searchbox', { name: 'Search' });
+
+ expect(button).toBeOnTheScreen();
+ expect(searchbox).toBeOnTheScreen();
+});
+```
+
+Common roles in React Native include:
+
+- `button` - pressable elements
+- `text` - static text
+- `header` / `heading` - headers
+- `searchbox` - search inputs
+- `switch` - toggle switches
+- `checkbox` - checkboxes
+- `radio` - radio buttons
+- And more...
+
+Note: React Native supports both ARIA-compatible (`role`) and legacy (`accessibilityRole`) props. Prefer `role` for consistency with web standards.
+
+## Using the wrong assertion {#using-the-wrong-assertion}
+
+Importance: high
+
+React Native Testing Library provides built-in Jest matchers. Make sure you're using the right ones:
+
+```tsx
+import { Pressable, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('button is disabled', async () => {
+ await render(
+
+ Submit
+
+ );
+
+ const button = screen.getByRole('button', { name: 'Submit' });
+
+ // ✅ Good - uses RNTL matcher
+ expect(button).toBeDisabled();
+
+ // ❌ Bad - doesn't use RNTL matcher
+ expect(button.props['aria-disabled']).toBe(true);
+});
+```
+
+Common matchers include:
+
+- `toBeOnTheScreen()` - checks if element is rendered (replaces `toBeInTheDocument()`)
+- `toBeDisabled()` - checks if element is disabled
+- `toHaveTextContent()` - checks text content
+- `toHaveAccessibleName()` - checks accessible name
+- And more...
+
+## Using `query*` variants for anything except checking for non-existence {#using-query-variants-for-anything-except-checking-for-non-existence}
+
+Importance: high
+
+Use `queryBy*` only when checking that an element doesn't exist:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('checks non-existence', async () => {
+ await render(
+
+ Hello
+
+ );
+
+ // ✅ Good - uses queryBy for non-existence check
+ expect(screen.queryByText('Goodbye')).not.toBeOnTheScreen();
+
+ // ❌ Bad - uses queryBy when element should exist
+ // const element = screen.queryByText('Hello');
+ // expect(element).toBeOnTheScreen();
+
+ // ✅ Good - uses getBy when element should exist
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+## Using `waitFor` to wait for elements that can be queried with `find*` {#using-waitfor-to-wait-for-elements-that-can-be-queried-with-find}
+
+Importance: high
+
+Use `findBy*` queries instead of `waitFor` + `getBy*`:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen, waitFor } from '@testing-library/react-native';
+
+test('waits for element', async () => {
+ const Component = () => {
+ const [show, setShow] = React.useState(false);
+
+ React.useEffect(() => {
+ setTimeout(() => setShow(true), 100);
+ }, []);
+
+ return {show && Loaded};
+ };
+
+ await render();
+
+ // ✅ Good - uses findBy query
+ const element = await screen.findByText('Loaded');
+ expect(element).toBeOnTheScreen();
+
+ // ❌ Bad - uses waitFor + getBy
+ // await waitFor(() => {
+ // expect(screen.getByText('Loaded')).toBeOnTheScreen();
+ // });
+});
+```
+
+## Performing side-effects in `waitFor` {#performing-side-effects-in-waitfor}
+
+Importance: high
+
+Don't perform side-effects in `waitFor` callbacks:
+
+```tsx
+import { Pressable, Text, View } from 'react-native';
+import { render, screen, waitFor, fireEvent } from '@testing-library/react-native';
+
+test('avoids side effects in waitFor', async () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+ return (
+
+ setCount(count + 1)}>
+ Increment
+
+ Count: {count}
+
+ );
+ };
+
+ await render();
+
+ const button = screen.getByRole('button');
+
+ // ❌ Bad - side effect in waitFor
+ // await waitFor(async () => {
+ // await fireEvent.press(button);
+ // expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ // });
+
+ // ✅ Good - side effect outside waitFor
+ await fireEvent.press(button);
+ await waitFor(() => {
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ });
+});
+```
+
+## Using `container` to query for elements {#using-container-to-query-for-elements}
+
+Importance: high
+
+React Native Testing Library provides a `container` object that has a `queryAll` method, but you should avoid using it directly:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render } from '@testing-library/react-native';
+
+test('finds element incorrectly', async () => {
+ const { container } = await render(
+
+ Hello
+
+ );
+
+ // ❌ Bad - using container.queryAll directly
+ const element = container.queryAll((node) => node.props.testID === 'message')[0];
+
+ // ✅ Good - use proper queries
+ // const element = screen.getByTestId('message');
+});
+```
+
+Instead, use the proper query methods from `screen` or the `render` result. The `container` is a low-level API that you rarely need.
+
+## Passing an empty callback to `waitFor` {#passing-an-empty-callback-to-waitfor}
+
+Importance: high
+
+Don't pass an empty callback to `waitFor`:
+
+```tsx
+import { View } from 'react-native';
+import { render, waitFor } from '@testing-library/react-native';
+
+test('waits correctly', async () => {
+ await render();
+
+ // ❌ Bad - empty callback
+ // await waitFor(() => {});
+
+ // ✅ Good - meaningful assertion
+ await waitFor(() => {
+ expect(screen.getByTestId('test')).toBeOnTheScreen();
+ });
+});
+```
+
+## Not using `screen` {#not-using-screen}
+
+Importance: medium
+
+You can get all the queries from the `render` result:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render } from '@testing-library/react-native';
+
+test('renders component', async () => {
+ const { getByText } = await render(
+
+ Hello
+
+ );
+
+ expect(getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+But you can also get them from the `screen` object:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('renders component', async () => {
+ await render(
+
+ Hello
+
+ );
+
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+});
+```
+
+Using `screen` has several benefits:
+
+1. You don't need to destructure `getByText` from `render`
+2. It's more consistent with the Testing Library ecosystem
+
+## Wrapping things in `act` unnecessarily {#wrapping-things-in-act-unnecessarily}
+
+Importance: medium
+
+React Native Testing Library's `render`, `renderHook`, `userEvent`, and `fireEvent` are already wrapped in `act`, so you don't need to wrap them yourself:
+
+```tsx
+import { Pressable, Text, View } from 'react-native';
+import { render, fireEvent, screen } from '@testing-library/react-native';
+
+test('updates on press', async () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+ return (
+
+ setCount(count + 1)}>
+ Count: {count}
+
+
+ );
+ };
+
+ await render();
+
+ const button = screen.getByRole('button');
+
+ // ✅ Good - fireEvent is already wrapped in act
+ await fireEvent.press(button);
+
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+
+ // ❌ Bad - unnecessary act wrapper
+ // await act(async () => {
+ // await fireEvent.press(button);
+ // });
+});
+```
+
+## Not using User Event API
+
+Importance: medium
+
+`userEvent` provides a more realistic way to simulate user interactions:
+
+```tsx
+import { Pressable, Text, TextInput, View } from 'react-native';
+import { render, screen, userEvent } from '@testing-library/react-native';
+
+test('uses userEvent', async () => {
+ const user = userEvent.setup();
+
+ const Component = () => {
+ const [value, setValue] = React.useState('');
+ return (
+
+
+ setValue('')}>
+ Clear
+
+
+ );
+ };
+
+ await render();
+
+ const input = screen.getByLabelText('Name');
+ const button = screen.getByRole('button', { name: 'Clear' });
+
+ // ✅ Good - uses userEvent for realistic interactions
+ await user.type(input, 'John');
+ expect(input).toHaveValue('John');
+
+ await user.press(button);
+ expect(input).toHaveValue('');
+});
+```
+
+`userEvent` methods are async and must be awaited. Available methods include:
+
+- `press()` - simulates a press
+- `longPress()` - simulates long press
+- `type()` - simulates typing
+- `clear()` - clears text input
+- `paste()` - simulates pasting
+- `scrollTo()` - simulates scrolling
+
+## Not querying by text {#not-querying-by-text}
+
+Importance: medium
+
+In React Native, text is rendered in `` components. You should query by the text content that users see:
+
+```tsx
+import { Text, View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('finds text correctly', async () => {
+ await render(
+
+ Hello World
+
+ );
+
+ // ✅ Good - queries by visible text
+ expect(screen.getByText('Hello World')).toBeOnTheScreen();
+
+ // ❌ Bad - queries by testID when text is available
+ // expect(screen.getByTestId('greeting')).toBeOnTheScreen();
+});
+```
+
+## Not using Testing Library ESLint plugins {#not-using-testing-library-eslint-plugins}
+
+Importance: medium
+
+There's an ESLint plugin for Testing Library: [`eslint-plugin-testing-library`](https://github.com/testing-library/eslint-plugin-testing-library). This plugin can help you avoid common mistakes and will automatically fix your code in many cases.
+
+You can install it with:
+
+```bash
+yarn add --dev eslint-plugin-testing-library
+```
+
+And configure it in your `eslint.config.js` (flat config):
+
+```js
+import testingLibrary from 'eslint-plugin-testing-library';
+
+export default [testingLibrary.configs['flat/react']];
+```
+
+Note: Unlike React Testing Library, React Native Testing Library has built-in Jest matchers, so you don't need `eslint-plugin-jest-dom`.
+
+## Using `cleanup` {#using-cleanup}
+
+Importance: medium
+
+React Native Testing Library automatically cleans up after each test. You don't need to call `cleanup()` manually unless you're using the `pure` export (which doesn't include automatic cleanup).
+
+If you want to disable automatic cleanup for a specific test, you can use:
+
+```tsx
+import { render } from '@testing-library/react-native/pure';
+
+test('does not cleanup', async () => {
+ // This test won't cleanup automatically
+ await render();
+ // ... your test
+});
+```
+
+But in most cases, you don't need to worry about cleanup at all - it's handled automatically.
+
+## Using `get*` variants as assertions {#using-get-variants-as-assertions}
+
+Importance: low
+
+`getBy*` queries throw errors when elements aren't found, so they work as assertions. However, for better error messages, you might want to combine them with explicit matchers:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('uses getBy as assertion', async () => {
+ await render(
+
+ Hello
+
+ );
+
+ // ✅ Good - getBy throws if not found, so it's an assertion
+ const element = screen.getByText('Hello');
+ expect(element).toBeOnTheScreen();
+
+ // ✅ Also good - more explicit
+ expect(screen.getByText('Hello')).toBeOnTheScreen();
+
+ // ❌ Bad - redundant assertion
+ // const element = screen.getByText('Hello');
+ // expect(element).not.toBeNull(); // getBy already throws if null
+});
+```
+
+## Having multiple assertions in a single `waitFor` callback {#having-multiple-assertions-in-a-single-waitfor-callback}
+
+Importance: low
+
+Keep `waitFor` callbacks focused on a single assertion:
+
+```tsx
+import { View, Text } from 'react-native';
+import { render, screen, waitFor } from '@testing-library/react-native';
+
+test('waits with single assertion', async () => {
+ const Component = () => {
+ const [count, setCount] = React.useState(0);
+
+ React.useEffect(() => {
+ setTimeout(() => setCount(1), 100);
+ }, []);
+
+ return (
+
+ Count: {count}
+
+ );
+ };
+
+ await render();
+
+ // ✅ Good - single assertion per waitFor
+ await waitFor(() => {
+ expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ });
+
+ // If you need multiple assertions, do them after waitFor
+ expect(screen.getByText('Count: 1')).toHaveTextContent('Count: 1');
+
+ // ❌ Bad - multiple assertions in waitFor
+ // await waitFor(() => {
+ // expect(screen.getByText('Count: 1')).toBeOnTheScreen();
+ // expect(screen.getByText('Count: 1')).toHaveTextContent('Count: 1');
+ // });
+});
+```
+
+## Using `wrapper` as the variable name {#using-wrapper-as-the-variable-name}
+
+Importance: low
+
+This is not really a "mistake" per se, but it's a common pattern that can be improved. When you use the `wrapper` option in `render`, you might be tempted to name your wrapper component `Wrapper`:
+
+```tsx
+import { View } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+test('renders with wrapper', async () => {
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
+ {children}
+ );
+
+ await render(Content, {
+ wrapper: Wrapper,
+ });
+
+ expect(screen.getByTestId('content')).toBeOnTheScreen();
+});
+```
+
+This works fine, but it's more conventional to name it something more descriptive like `ThemeProvider` or `AllTheProviders` (if you're wrapping with multiple providers). This makes it clearer what the wrapper is doing.
+
+## Summary
+
+The key principles to remember:
+
+1. **Use the right query** - Prefer `getByRole` as your first choice, use `findBy*` for async elements, and `queryBy*` only for checking non-existence
+2. **Use proper assertions** - Use RNTL's built-in matchers (`toBeOnTheScreen()`, `toBeDisabled()`, etc.) instead of asserting on props directly
+3. **Handle async operations correctly** - Always `await` `render()`, `renderHook`, `fireEvent`,and `userEvent` methods
+4. **Use `waitFor` correctly** - Avoid side-effects in callbacks, use `findBy*` instead when possible, and keep callbacks focused
+5. **Follow accessibility best practices** - Prefer ARIA attributes (`role`, `aria-label`) over `accessibility*` props
+6. **Organize code well** - Use `screen` over destructuring, prefer `userEvent` over `fireEvent`, and don't use `cleanup()`
+
+By following these principles, your tests will be more maintainable, accessible, and reliable.
diff --git a/website/docs/14.x/docs/guides/llm-guidelines.mdx b/website/docs/14.x/docs/guides/llm-guidelines.mdx
new file mode 100644
index 000000000..35c9942a9
--- /dev/null
+++ b/website/docs/14.x/docs/guides/llm-guidelines.mdx
@@ -0,0 +1,229 @@
+# LLM Guidelines for React Native Testing Library
+
+Actionable guidelines for writing tests with React Native Testing Library (RNTL) v14.
+
+## Core APIs
+
+### render
+
+```tsx
+const result = await render(, options?);
+```
+
+| Option | Description |
+| ---------------- | ---------------------------------------------------------------- |
+| `wrapper` | React component to wrap the rendered component (e.g., providers) |
+| `createNodeMock` | Function to create mock refs |
+
+| Return | Description |
+| --------------------- | ------------------------------------------------ |
+| `rerender(component)` | Re-render with a new component (async) |
+| `unmount()` | Unmount the rendered component (async) |
+| `toJSON()` | Get JSON representation for snapshots |
+| `debug(options?)` | Print the component tree to console |
+| `container` | Root host element of the rendered tree |
+| `root` | First child host element (your component's root) |
+
+### screen
+
+**Prefer `screen`** over destructuring from `render()`. Provides all query methods after `render()` is called.
+
+```tsx
+await render();
+screen.getByRole('button'); // Access queries via screen
+```
+
+### renderHook
+
+```tsx
+const { result, rerender, unmount } = await renderHook(() => useMyHook(), options?);
+```
+
+| Option | Description |
+| -------------- | -------------------------------------------------- |
+| `initialProps` | Initial props passed to the hook |
+| `wrapper` | React component to wrap the hook (e.g., providers) |
+
+| Return | Description |
+| ------------------ | ------------------------------------- |
+| `result.current` | Current return value of the hook |
+| `rerender(props?)` | Re-render hook with new props (async) |
+| `unmount()` | Unmount the hook (async) |
+
+## Query Selection
+
+- **Prefer `getByRole`** as first choice for querying elements
+- **Query priority**: `getByRole` → `getByLabelText` → `getByPlaceholderText` → `getByText` → `getByDisplayValue` → `getByTestId` (last resort)
+- **Use `findBy*`** for elements that appear asynchronously (after API calls, timeouts, state updates)
+- **Use `queryBy*` ONLY** for checking non-existence (with `.not.toBeOnTheScreen()`)
+- **Never use `getBy*`** for non-existence checks
+- **Avoid `container.queryAll()`** - use `screen` queries instead
+- **Query by visible text**, not `testID` when text is available
+
+## Assertions
+
+- **Use RNTL matchers** - prefer semantic matchers over prop assertions
+- **Combine queries with matchers**: `expect(screen.getByText('Hello')).toBeOnTheScreen()`
+- **No redundant null checks** - `getBy*` already throws if not found
+
+## Jest Matchers Reference
+
+| Matcher | Description |
+| --------------------------------- | ------------------------------------------------------------------------------------------- |
+| `toBeOnTheScreen()` | Element is present in the element tree |
+| `toBeVisible()` | Element is visible (checks style, `aria-hidden`, `accessibilityElementsHidden`, ancestors) |
+| `toBeEmptyElement()` | Element has no children or text content |
+| `toContainElement(element)` | Element contains another element |
+| `toBeEnabled()` | Element is not disabled (checks `aria-disabled`, `accessibilityState`, ancestors) |
+| `toBeDisabled()` | Element has `aria-disabled` or `accessibilityState={{ disabled: true }}` (checks ancestors) |
+| `toBeBusy()` | Element has `aria-busy` or `accessibilityState={{ busy: true }}` |
+| `toBeChecked()` | Element has `aria-checked` or `accessibilityState={{ checked: true }}` |
+| `toBePartiallyChecked()` | Element has `aria-checked="mixed"` or `accessibilityState={{ checked: 'mixed' }}` |
+| `toBeSelected()` | Element has `aria-selected` or `accessibilityState={{ selected: true }}` |
+| `toBeExpanded()` | Element has `aria-expanded` or `accessibilityState={{ expanded: true }}` |
+| `toBeCollapsed()` | Element has `aria-expanded={false}` or `accessibilityState={{ expanded: false }}` |
+| `toHaveTextContent(text)` | Element has matching text content |
+| `toHaveDisplayValue(value)` | TextInput has matching display value |
+| `toHaveAccessibleName(name?)` | Element has matching `aria-label`, `accessibilityLabel`, or text content |
+| `toHaveAccessibilityValue(value)` | Element has matching `aria-value*` or `accessibilityValue` |
+| `toHaveStyle(style)` | Element has matching style |
+| `toHaveProp(name, value?)` | Element has prop (use semantic matchers when possible) |
+
+## User Interactions
+
+**Prefer `userEvent`** over `fireEvent` for realistic user interaction simulation. `userEvent` triggers the complete event sequence that real users would produce.
+
+### userEvent (Preferred)
+
+```tsx
+const user = userEvent.setup();
+```
+
+| Method | Description |
+| ------------------------------------ | ----------------------------------------------------------------------------------- |
+| `user.press(element)` | Press an element (triggers `pressIn`, `pressOut`, `press`) |
+| `user.longPress(element, options?)` | Long press with optional `{ duration }` |
+| `user.type(element, text, options?)` | Type into TextInput (triggers `focus`, `keyPress`, `change`, `changeText` per char) |
+| `user.clear(element)` | Clear TextInput (select all + backspace) |
+| `user.paste(element, text)` | Paste text into TextInput |
+| `user.scrollTo(element, options)` | Scroll a ScrollView with `{ y }` or `{ x }` offset |
+
+### fireEvent (Low-level)
+
+Use only when `userEvent` doesn't support the event or when you need direct control.
+
+| Method | Description |
+| ---------------------------------------- | --------------------------------------------- |
+| `fireEvent(element, eventName, ...data)` | Fire any event by name |
+| `fireEvent.press(element)` | Fire `onPress` only (no `pressIn`/`pressOut`) |
+| `fireEvent.changeText(element, text)` | Fire `onChangeText` directly |
+| `fireEvent.scroll(element, eventData)` | Fire `onScroll` with event data |
+
+## Async/Await (v14)
+
+- **Always `await`**: `render()`, `fireEvent.*`, `renderHook()`, `userEvent.*`
+- **Make test functions `async`**: `test('name', async () => { ... })`
+- **Don't wrap in `act()`** - `render` and `fireEvent` handle it internally
+
+## waitFor Usage
+
+- **Use `findBy*`** instead of `waitFor` + `getBy*` when waiting for elements
+- **Never perform side-effects** (like `fireEvent.press()`) inside `waitFor` callbacks
+- **One assertion per `waitFor`** callback
+- **Never pass empty callbacks** - always include a meaningful assertion
+- **Place side-effects before `waitFor`** - perform actions, then wait for result
+
+## Code Organization
+
+- **Use `screen`** instead of destructuring from `render()`: `screen.getByText()` not `const { getByText } = render()`
+- **Prefer `userEvent`** over `fireEvent` for realistic interactions
+- **Don't use `cleanup()`** - handled automatically
+- **Name wrappers descriptively**: `ThemeProvider` not `Wrapper`
+- **Install ESLint plugin**: `eslint-plugin-testing-library`
+
+## Quick Checklist
+
+- ✅ Using `getByRole` as first choice?
+- ✅ Using `await` for all async operations?
+- ✅ Using `findBy*` for async elements (not `waitFor` + `getBy*`)?
+- ✅ Using `queryBy*` only for non-existence?
+- ✅ Using RNTL matchers (`toBeOnTheScreen()`, `toBeDisabled()`, etc.)?
+- ✅ Using `screen` not destructuring from `render()`?
+- ✅ Avoiding side-effects in `waitFor`?
+- ✅ Using `userEvent` when appropriate?
+
+## Example: Good Pattern
+
+```tsx
+import { render, screen } from '@testing-library/react-native';
+import userEvent from '@testing-library/react-native';
+import { Pressable, Text, TextInput, View } from 'react-native';
+
+test('user can submit form', async () => {
+ const user = userEvent.setup();
+
+ const Component = () => {
+ const [name, setName] = React.useState('');
+ const [submitted, setSubmitted] = React.useState(false);
+
+ return (
+
+
+ setSubmitted(true)}>
+ Submit
+
+ {submitted && Form submitted!}
+
+ );
+ };
+
+ await render();
+
+ // ✅ getByRole as first choice
+ const input = screen.getByRole('textbox', { name: 'Name' });
+ const button = screen.getByRole('button', { name: 'Submit' });
+
+ // ✅ userEvent for realistic interactions
+ await user.type(input, 'John Doe');
+ await user.press(button);
+
+ // ✅ findBy* for async elements
+ const successMessage = await screen.findByRole('alert');
+
+ // ✅ RNTL matchers
+ expect(successMessage).toBeOnTheScreen();
+ expect(successMessage).toHaveTextContent('Form submitted!');
+});
+```
+
+## Example: Anti-Patterns
+
+```tsx
+// ❌ Missing await
+test('bad', () => {
+ render();
+ fireEvent.press(screen.getByText('Submit'));
+});
+
+// ❌ getBy* for non-existence
+expect(screen.getByText('Error')).not.toBeOnTheScreen();
+
+// ❌ waitFor + getBy* instead of findBy*
+await waitFor(() => {
+ expect(screen.getByText('Loaded')).toBeOnTheScreen();
+});
+
+// ❌ Side-effect in waitFor
+await waitFor(async () => {
+ await fireEvent.press(button);
+ expect(screen.getByText('Result')).toBeOnTheScreen();
+});
+
+// ❌ accessibility* props instead of ARIA
+;
+
+// ❌ Destructuring from render
+const { getByText } = await render();
+```
+
+By following these guidelines, your tests will be more maintainable, accessible, and reliable.
diff --git a/website/rspress.config.ts b/website/rspress.config.ts
index 05bf15d68..295377938 100644
--- a/website/rspress.config.ts
+++ b/website/rspress.config.ts
@@ -96,99 +96,6 @@ const sidebar12x = {
],
};
-const sidebar13x = {
- '/13.x/docs/': [
- {
- text: 'Getting started',
- items: [
- { text: 'Introduction', link: '/13.x/docs/start/intro' },
- { text: 'Quick Start', link: '/13.x/docs/start/quick-start' },
- ],
- },
- {
- text: 'API reference',
- items: [
- { text: 'Render function', link: '/13.x/docs/api/render' },
- { text: 'Screen object', link: '/13.x/docs/api/screen' },
- { text: 'Queries', link: '/13.x/docs/api/queries' },
- { text: 'Jest Matchers', link: '/13.x/docs/api/jest-matchers' },
- {
- text: 'Triggering events',
- items: [
- { text: 'Fire Event', link: '/13.x/docs/api/events/fire-event' },
- { text: 'User Event', link: '/13.x/docs/api/events/user-event' },
- ],
- },
- {
- text: 'Miscellaneous',
- items: [
- { text: 'Accessibility', link: '/13.x/docs/api/misc/accessibility' },
- { text: 'Async utilities', link: '/13.x/docs/api/misc/async' },
- { text: 'Config', link: '/13.x/docs/api/misc/config' },
- { text: 'Other', link: '/13.x/docs/api/misc/other' },
- { text: 'Render Hook', link: '/13.x/docs/api/misc/render-hook' },
- ],
- },
- ],
- },
- {
- text: 'Guides',
- items: [
- { text: 'How to Query', link: '/13.x/docs/guides/how-to-query' },
- { text: 'React 19', link: '/13.x/docs/guides/react-19' },
- { text: 'Troubleshooting', link: '/13.x/docs/guides/troubleshooting' },
- { text: 'FAQ', link: '/13.x/docs/guides/faq' },
- { text: 'Community Resources', link: '/13.x/docs/guides/community-resources' },
- ],
- },
- {
- text: 'Advanced Guides',
- items: [
- { text: 'Testing Environment', link: '/13.x/docs/advanced/testing-env' },
- { text: 'Third-party Integration', link: '/13.x/docs/advanced/third-party-integration' },
- { text: 'Understanding Act', link: '/13.x/docs/advanced/understanding-act' },
- ],
- },
- {
- text: 'Migration Guides',
- collapsed: true,
- items: [
- { text: 'v13 Migration', link: '/13.x/docs/migration/v13' },
- { text: 'Jest Matchers', link: '/13.x/docs/migration/jest-matchers' },
- {
- text: 'Previous versions',
- collapsed: true,
- items: [
- { text: 'v12', link: '/13.x/docs/migration/previous/v12' },
- { text: 'v11', link: '/13.x/docs/migration/previous/v11' },
- { text: 'v9', link: '/13.x/docs/migration/previous/v9' },
- { text: 'v7', link: '/13.x/docs/migration/previous/v7' },
- { text: 'v2', link: '/13.x/docs/migration/previous/v2' },
- ],
- },
- ],
- },
- ],
- '/13.x/cookbook/': [
- { text: 'Cookbook', link: '/13.x/cookbook/' },
- {
- text: 'Basic Recipes',
- items: [
- { text: 'Async Tests', link: '/13.x/cookbook/basics/async-tests' },
- { text: 'Custom Render', link: '/13.x/cookbook/basics/custom-render' },
- ],
- },
- {
- text: 'Advanced Recipes',
- items: [{ text: 'Network Requests', link: '/13.x/cookbook/advanced/network-requests' }],
- },
- {
- text: 'State Management Recipes',
- items: [{ text: 'Jotai', link: '/13.x/cookbook/state-management/jotai' }],
- },
- ],
-};
-
const sidebar14x = {
'/14.x/docs/': [
{
@@ -229,6 +136,8 @@ const sidebar14x = {
text: 'Guides',
items: [
{ text: 'How to Query', link: '/14.x/docs/guides/how-to-query' },
+ { text: 'Common Mistakes', link: '/14.x/docs/guides/common-mistakes' },
+ { text: 'LLM Guidelines', link: '/14.x/docs/guides/llm-guidelines' },
{ text: 'Troubleshooting', link: '/14.x/docs/guides/troubleshooting' },
{ text: 'FAQ', link: '/14.x/docs/guides/faq' },
{ text: 'Community Resources', link: '/14.x/docs/guides/community-resources' },
@@ -303,6 +212,8 @@ const sidebarDefault = {
text: 'Guides',
items: [
{ text: 'How to Query', link: '/docs/guides/how-to-query' },
+ { text: 'Common Mistakes', link: '/docs/guides/common-mistakes' },
+ { text: 'LLM Guidelines', link: '/docs/guides/llm-guidelines' },
{ text: 'React 19', link: '/docs/guides/react-19' },
{ text: 'Troubleshooting', link: '/docs/guides/troubleshooting' },
{ text: 'FAQ', link: '/docs/guides/faq' },
@@ -409,7 +320,6 @@ export default defineConfig({
sidebar: {
...sidebarDefault,
...sidebar12x,
- ...sidebar13x,
...sidebar14x,
},
},