From d2018b50e2e573c16cd50eeccbcab8dc45dfb1dd Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Sun, 16 Aug 2026 00:14:27 +0300 Subject: [PATCH 1/4] chore(deps-dev): add @react-native/jest-preset jest-expo 56 declares @react-native/jest-preset as a non-optional peer and resolves setupFiles and testEnvironment through it. Nothing provided it, so the suite quietly ran on the copy react-native ships in-tree. react-native 0.86 deleted that copy. Its jest-preset.js is now a shim that throws unless @react-native/jest-preset is installed, which takes the suite down in jest.config.cjs before a single test loads. Declaring the peer keeps the config loadable across the whole peer range. Verified on react-native 0.81.4 that the preset changes nothing: 1048 passed, 3 failed, 21 skipped, 1072 total both with and without it, where the 3 failures are pre-existing Windows path mismatches in the babel-plugin-tester suites. --- package.json | 1 + yarn.lock | 444 ++++++++++++++++++++++++++------------------------- 2 files changed, 229 insertions(+), 216 deletions(-) diff --git a/package.json b/package.json index 29c04232..ba85254f 100644 --- a/package.json +++ b/package.json @@ -215,6 +215,7 @@ "@eslint/js": "^10.0.1", "@expo/metro-config": "~54.0.5", "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@react-native/jest-preset": "^0.85.0", "@release-it/conventional-changelog": "10.0.1", "@tailwindcss/postcss": "^4.1.12", "@testing-library/react-native": "^13.3.3", diff --git a/yarn.lock b/yarn.lock index 8fe7596e..9ada786c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,7 +102,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.29.0, @babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -163,6 +163,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.1, @babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" + dependencies: + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 + languageName: node + linkType: hard + "@babel/generator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/generator@npm:7.29.7" @@ -194,7 +207,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": +"@babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: @@ -605,6 +618,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" + dependencies: + "@babel/types": "npm:^7.29.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/acc890c5e6a6dd40863a47b50bac111d7185ee6fbbe163ebe11d5214854ca2adb901462ad4d718a65090ef84bd2230e9e8ab45a2e0caccc685f1f57ab0bb1e28 + languageName: node + linkType: hard + "@babel/parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" @@ -1018,7 +1042,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.0.0-0, @babel/plugin-transform-arrow-functions@npm:^7.24.7": +"@babel/plugin-transform-arrow-functions@npm:^7.0.0-0": version: 7.27.1 resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" dependencies: @@ -1029,7 +1053,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.29.7": +"@babel/plugin-transform-arrow-functions@npm:^7.24.7, @babel/plugin-transform-arrow-functions@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-arrow-functions@npm:7.29.7" dependencies: @@ -1205,19 +1229,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-computed-properties@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/template": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e09a12f8c8ae0e6a6144c102956947b4ec05f6c844169121d0ec4529c2d30ad1dc59fee67736193b87a402f44552c888a519a680a31853bdb4d34788c28af3b0 - languageName: node - linkType: hard - -"@babel/plugin-transform-computed-properties@npm:^7.29.7": +"@babel/plugin-transform-computed-properties@npm:^7.24.7, @babel/plugin-transform-computed-properties@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.29.7" dependencies: @@ -1392,20 +1404,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-function-name@npm:7.27.1" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.27.1" - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5abdc7b5945fbd807269dcc6e76e52b69235056023b0b35d311e8f5dfd6c09d9f225839798998fc3b663f50cf701457ddb76517025a0d7a5474f3fe56e567a4c - languageName: node - linkType: hard - -"@babel/plugin-transform-function-name@npm:^7.29.7": +"@babel/plugin-transform-function-name@npm:^7.25.1, @babel/plugin-transform-function-name@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-function-name@npm:7.29.7" dependencies: @@ -1429,18 +1428,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.27.1 - resolution: "@babel/plugin-transform-literals@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c40dc3eb2f45a92ee476412314a40e471af51a0f51a24e91b85cef5fc59f4fe06758088f541643f07f949d2c67ee7bdce10e11c5ec56791ae09b15c3b451eeca - languageName: node - linkType: hard - -"@babel/plugin-transform-literals@npm:^7.29.7": +"@babel/plugin-transform-literals@npm:^7.25.2, @babel/plugin-transform-literals@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-literals@npm:7.29.7" dependencies: @@ -1451,18 +1439,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5b0abc7c0d09d562bf555c646dce63a30288e5db46fd2ce809a61d064415da6efc3b2b3c59b8e4fe98accd072c89a2f7c3765b400e4bf488651735d314d9feeb - languageName: node - linkType: hard - -"@babel/plugin-transform-logical-assignment-operators@npm:^7.29.7": +"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7, @babel/plugin-transform-logical-assignment-operators@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.29.7" dependencies: @@ -1603,18 +1580,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b72cbebbfe46fcf319504edc1cf59f3f41c992dd6840db766367f6a1d232cd2c52143c5eaf57e0316710bee251cae94be97c6d646b5022fcd9274ccb131b470c - languageName: node - linkType: hard - -"@babel/plugin-transform-numeric-separator@npm:^7.29.7": +"@babel/plugin-transform-numeric-separator@npm:^7.24.7, @babel/plugin-transform-numeric-separator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-numeric-separator@npm:7.29.7" dependencies: @@ -1977,7 +1943,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.0.0-0, @babel/plugin-transform-shorthand-properties@npm:^7.24.7": +"@babel/plugin-transform-shorthand-properties@npm:^7.0.0-0": version: 7.27.1 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" dependencies: @@ -1988,7 +1954,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.29.7": +"@babel/plugin-transform-shorthand-properties@npm:^7.24.7, @babel/plugin-transform-shorthand-properties@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.29.7" dependencies: @@ -2000,14 +1966,14 @@ __metadata: linkType: hard "@babel/plugin-transform-spread@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-spread@npm:7.27.1" + version: 7.29.8 + resolution: "@babel/plugin-transform-spread@npm:7.29.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.29.7" + "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.29.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/b34fc58b33bd35b47d67416655c2cbc8578fbb3948b4592bc15eb6d8b4046986e25c06e3b9929460fa4ab08e9653582415e7ef8b87d265e1239251bdf5a4c162 + checksum: 10c0/3af864918afb3389989b5ba5b196a376bb58d1c59657a978d12213766cf052bcf12e66165da6676e5aa08fa64f0c3ec78a124c38ce6b41b88810bb88ba9d0a89 languageName: node linkType: hard @@ -2023,18 +1989,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5698df2d924f0b1b7bdb7ef370e83f99ed3f0964eb3b9c27d774d021bee7f6d45f9a73e2be369d90b4aff1603ce29827f8743f091789960e7669daf9c3cda850 - languageName: node - linkType: hard - -"@babel/plugin-transform-sticky-regex@npm:^7.29.7": +"@babel/plugin-transform-sticky-regex@npm:^7.24.7, @babel/plugin-transform-sticky-regex@npm:^7.29.7": version: 7.29.7 resolution: "@babel/plugin-transform-sticky-regex@npm:7.29.7" dependencies: @@ -2341,7 +2296,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.25.0, @babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.25.0, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" dependencies: @@ -2352,7 +2307,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.29.7": +"@babel/template@npm:^7.28.6, @babel/template@npm:^7.29.7": version: 7.29.7 resolution: "@babel/template@npm:7.29.7" dependencies: @@ -2378,6 +2333,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.29.0": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.8" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + debug: "npm:^4.3.1" + checksum: 10c0/87a28989c434add26d787776ac6d30f749b89cb030f2a605c89f671a516a6fa165ac0476f07e2b69feed70128b2734cae1cbbf41dfe95ccf22081bd8f8b91923 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.29.7": version: 7.29.7 resolution: "@babel/traverse@npm:7.29.7" @@ -2403,6 +2373,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 + languageName: node + linkType: hard + "@babel/types@npm:^7.29.7": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" @@ -4363,6 +4343,21 @@ __metadata: languageName: node linkType: hard +"@react-native/jest-preset@npm:^0.85.0": + version: 0.85.3 + resolution: "@react-native/jest-preset@npm:0.85.3" + dependencies: + "@jest/create-cache-key-function": "npm:^29.7.0" + "@react-native/js-polyfills": "npm:0.85.3" + babel-jest: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + regenerator-runtime: "npm:^0.13.2" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/7c18a87432978d8e05bb66401d6591a99e1aaf575a206afd0a120611359c8addf7259ecf9f1ccb162fd2bd23412eaf8d7791168718bbe180e397a219806a302f + languageName: node + linkType: hard + "@react-native/js-polyfills@npm:0.81.4": version: 0.81.4 resolution: "@react-native/js-polyfills@npm:0.81.4" @@ -4370,6 +4365,13 @@ __metadata: languageName: node linkType: hard +"@react-native/js-polyfills@npm:0.85.3": + version: 0.85.3 + resolution: "@react-native/js-polyfills@npm:0.85.3" + checksum: 10c0/67c4ed8234cbeb6d5250b06901f352ddd5348f9aad88aac8a06dc884f42e0da22ff2e1ca8e06c7d03e1e659486bdb9d7682f34858ebf9398d7ae199ce02b3749 + languageName: node + linkType: hard + "@react-native/normalize-colors@npm:0.81.4": version: 0.81.4 resolution: "@react-native/normalize-colors@npm:0.81.4" @@ -5111,6 +5113,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef + languageName: node + linkType: hard + "acorn-globals@npm:^7.0.0": version: 7.0.1 resolution: "acorn-globals@npm:7.0.1" @@ -8392,13 +8404,6 @@ __metadata: languageName: node linkType: hard -"hermes-estree@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-estree@npm:0.32.0" - checksum: 10c0/3b67d1fe44336240ef7f9c40ecbf363279ba263d51efe120570c3862cc109e652fc09aebddfe6b73d0f0246610bee130e4064c359f1f4cbf002bdb1d99717ef2 - languageName: node - linkType: hard - "hermes-estree@npm:0.34.0": version: 0.34.0 resolution: "hermes-estree@npm:0.34.0" @@ -8406,6 +8411,13 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-estree@npm:0.35.0" + checksum: 10c0/a88c9dc63b8b3679b1aeb43e72e977597096c1bd7d59978c952f1d6df6d1a517c4a817c70b1b701854996b485adfa66c2fc7f80871029a7f0c04306f6717b59a + languageName: node + linkType: hard + "hermes-parser@npm:0.29.1, hermes-parser@npm:^0.29.1": version: 0.29.1 resolution: "hermes-parser@npm:0.29.1" @@ -8415,15 +8427,6 @@ __metadata: languageName: node linkType: hard -"hermes-parser@npm:0.32.0": - version: 0.32.0 - resolution: "hermes-parser@npm:0.32.0" - dependencies: - hermes-estree: "npm:0.32.0" - checksum: 10c0/5902d2c5d347c0629fba07a47eaad5569590ac69bc8bfb2e454e08d2dfbe1ebd989d88518dca2cba64061689b5eac5960ae6bd15a4a66600bbf377498a3234b7 - languageName: node - linkType: hard - "hermes-parser@npm:0.34.0": version: 0.34.0 resolution: "hermes-parser@npm:0.34.0" @@ -8433,6 +8436,15 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-parser@npm:0.35.0" + dependencies: + hermes-estree: "npm:0.35.0" + checksum: 10c0/49d98093a2094758db5b536627c6cf5146b140f66e63143acf471c62f1d3fd8bd6ae10a33f2372f72e3653deda5d4615c6dae89d01248849440916209901fc4a + languageName: node + linkType: hard + "hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -10444,15 +10456,16 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.2": - version: 0.83.2 - resolution: "metro-babel-transformer@npm:0.83.2" +"metro-babel-transformer@npm:0.83.7": + version: 0.83.7 + resolution: "metro-babel-transformer@npm:0.83.7" dependencies: "@babel/core": "npm:^7.25.2" flow-enums-runtime: "npm:^0.0.6" - hermes-parser: "npm:0.32.0" + hermes-parser: "npm:0.35.0" + metro-cache-key: "npm:0.83.7" nullthrows: "npm:^1.1.1" - checksum: 10c0/8f3005c6534eb62816fa85a321c891b1dd64f4ac92d7dc7eedbfe06b4fffd2e3f629d0641ffd87373c6d28152c701faf36c7f7a4b0ed6624aa2b3c922d6026ae + checksum: 10c0/c50c2fe9fa3d3fbe382d8a482ebcc77d7dba0ef4673cee4904a55b9ea28dea4b86d3b80e9b9ac0cd2b094b9ecd0368c953bba0f1df1cfd36c3a27c666352fd86 languageName: node linkType: hard @@ -10465,12 +10478,12 @@ __metadata: languageName: node linkType: hard -"metro-cache-key@npm:0.83.2": - version: 0.83.2 - resolution: "metro-cache-key@npm:0.83.2" +"metro-cache-key@npm:0.83.7": + version: 0.83.7 + resolution: "metro-cache-key@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/791517bcd997d2f8ecaba3aff99714408f1c80d938846c7b8d114e346ce93c963eb103317e4781b3c16ad187ed95b8e23397346bb6cb4b86ec8ff95dbda4681e + checksum: 10c0/a2f20b86b173cd2be710552a49e6389a2c9ef994ac35873616bd4e5c34588beb8c93279bcf525295ca73b6d206ab0d9942f35cbd4a76e642d359bdab48924d7a languageName: node linkType: hard @@ -10486,15 +10499,15 @@ __metadata: languageName: node linkType: hard -"metro-cache@npm:0.83.2": - version: 0.83.2 - resolution: "metro-cache@npm:0.83.2" +"metro-cache@npm:0.83.7": + version: 0.83.7 + resolution: "metro-cache@npm:0.83.7" dependencies: exponential-backoff: "npm:^3.1.1" flow-enums-runtime: "npm:^0.0.6" https-proxy-agent: "npm:^7.0.5" - metro-core: "npm:0.83.2" - checksum: 10c0/2c8d4004153431abb73265496ba2ede5e5fd09c2ca3769186ebe5b87645c97d74b871cc1bf83ab6bccdae596039341e330df3ebfab6e8469e1207e6a5d0e9ebc + metro-core: "npm:0.83.7" + checksum: 10c0/81ae4b2d0d389d25b816bb2d59eb8baed5e1f03a727aa9bcecadb5547e949525f90af888c956fbd5ff4a732cf4dc48b6d903bab648d23dc1830135d820040825 languageName: node linkType: hard @@ -10514,19 +10527,19 @@ __metadata: languageName: node linkType: hard -"metro-config@npm:0.83.2, metro-config@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-config@npm:0.83.2" +"metro-config@npm:0.83.7, metro-config@npm:^0.83.1": + version: 0.83.7 + resolution: "metro-config@npm:0.83.7" dependencies: connect: "npm:^3.6.5" flow-enums-runtime: "npm:^0.0.6" jest-validate: "npm:^29.7.0" - metro: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-core: "npm:0.83.2" - metro-runtime: "npm:0.83.2" + metro: "npm:0.83.7" + metro-cache: "npm:0.83.7" + metro-core: "npm:0.83.7" + metro-runtime: "npm:0.83.7" yaml: "npm:^2.6.1" - checksum: 10c0/224dff59b53f23ca4a0f39e8a82c297b93c779e9f92c1e097c1ac9fd66d86978b696476aef14a2eb3d2b4704adfb53e3d7ab76abf32924a06f3f6d0c586ac16f + checksum: 10c0/04f9ae008a4972d8399ed90dc03286afefabc2fc362fef8b8f362ec9c09f2edfe3f0f9e0e5aebf42de8b253f202629cc8e86ef0b07e205adf257cd609ac386fb languageName: node linkType: hard @@ -10541,14 +10554,14 @@ __metadata: languageName: node linkType: hard -"metro-core@npm:0.83.2, metro-core@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-core@npm:0.83.2" +"metro-core@npm:0.83.7, metro-core@npm:^0.83.1": + version: 0.83.7 + resolution: "metro-core@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" lodash.throttle: "npm:^4.1.1" - metro-resolver: "npm:0.83.2" - checksum: 10c0/3582bfb114fbda2c9bb5cdde1a185ba0c707a27f325a6e63c6a981c9a05e02965dcfcc8c98cc37f30ef8e103f34e4f7b544e67f61f1dcdfc1bf81d98b27507f8 + metro-resolver: "npm:0.83.7" + checksum: 10c0/cd267b2516ccce0cc152d12ff1f08073abec0a9b210779357a43f1a279acb3c3be0ff625de030da9a110aea45b70e36f14e13b846cb4fee8419fae67db2a53b4 languageName: node linkType: hard @@ -10569,9 +10582,9 @@ __metadata: languageName: node linkType: hard -"metro-file-map@npm:0.83.2": - version: 0.83.2 - resolution: "metro-file-map@npm:0.83.2" +"metro-file-map@npm:0.83.7": + version: 0.83.7 + resolution: "metro-file-map@npm:0.83.7" dependencies: debug: "npm:^4.4.0" fb-watchman: "npm:^2.0.0" @@ -10582,7 +10595,7 @@ __metadata: micromatch: "npm:^4.0.4" nullthrows: "npm:^1.1.1" walker: "npm:^1.0.7" - checksum: 10c0/0577c06c7c7f1325a9c9121a304de13c632f783e4a4fe149b1b532ae88d375f199803ea75ff56e979867d4969326a297336ac815ee494ba057eec600a129bb58 + checksum: 10c0/467729e9f607552504d9e1b9a8d81e5ea5a27f979f19a40360215773b38e1a3b563789047f06a5502cf5983aae922b3c52f5a7e0be8edc0b0bb747556e7d5af8 languageName: node linkType: hard @@ -10596,13 +10609,13 @@ __metadata: languageName: node linkType: hard -"metro-minify-terser@npm:0.83.2": - version: 0.83.2 - resolution: "metro-minify-terser@npm:0.83.2" +"metro-minify-terser@npm:0.83.7": + version: 0.83.7 + resolution: "metro-minify-terser@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" terser: "npm:^5.15.0" - checksum: 10c0/0bf1cb557d30c82b701c2cb8a89f92c93ebb54891bd88dc3a504783e2038825bbde77095b0c9ff74069e09b170742b8fe37f5f9233e421a41c997fe5b3303d66 + checksum: 10c0/bf59f9b5a51c3cfff313f64daab78673fa17d3c9d0ec34823d295cd454daa146daa23706f4cae36104bc41f10ad7b5fa2802246891a6f5eb4d0736ef891c9c7a languageName: node linkType: hard @@ -10615,12 +10628,12 @@ __metadata: languageName: node linkType: hard -"metro-resolver@npm:0.83.2": - version: 0.83.2 - resolution: "metro-resolver@npm:0.83.2" +"metro-resolver@npm:0.83.7": + version: 0.83.7 + resolution: "metro-resolver@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/1094ffa21f8f5273cd0f43d663806a62ed45841c7b4e51f1a823437e0659a98e13829ab18e419e2cfb3cbd097e0263c75e7937a92c5da69718458adec6c4ca51 + checksum: 10c0/be5ba43302362f3918ef4a1f91f96d63c04469cc9fed076a2846785f60f7f53193278a36d249b92a4678fcf95f7a6e31da87a13df82ddc384f010c80fd3b074d languageName: node linkType: hard @@ -10634,13 +10647,13 @@ __metadata: languageName: node linkType: hard -"metro-runtime@npm:0.83.2, metro-runtime@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-runtime@npm:0.83.2" +"metro-runtime@npm:0.83.7, metro-runtime@npm:^0.83.1": + version: 0.83.7 + resolution: "metro-runtime@npm:0.83.7" dependencies: "@babel/runtime": "npm:^7.25.0" flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/1eb13af44f47bc490ed663d6ad3024c95cfbc4117c281e60adbb5cd56e3813e3e9e4344b2e2b2e8db517411f3f7a18dadd71c96a7f79b7d019c6326221648564 + checksum: 10c0/b9feb9cffdf4df086cea91d803529d706c1a212d0c6b9d138ef4985905cb9bd22f9861a77061364a397917b772d2ef722e465a5b198d6405f574481dfa35a9a7 languageName: node linkType: hard @@ -10672,21 +10685,20 @@ __metadata: languageName: node linkType: hard -"metro-source-map@npm:0.83.2, metro-source-map@npm:^0.83.1": - version: 0.83.2 - resolution: "metro-source-map@npm:0.83.2" +"metro-source-map@npm:0.83.7, metro-source-map@npm:^0.83.1": + version: 0.83.7 + resolution: "metro-source-map@npm:0.83.7" dependencies: - "@babel/traverse": "npm:^7.25.3" - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-symbolicate: "npm:0.83.2" + metro-symbolicate: "npm:0.83.7" nullthrows: "npm:^1.1.1" - ob1: "npm:0.83.2" + ob1: "npm:0.83.7" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" - checksum: 10c0/1590e9e50389f607dff1283fa70a770dfce1be44299062e8a538f0dd75b63307ea8661495ca64ad9994898e3dd0d1dc995023ddd9b67dd304de3b259a105056c + checksum: 10c0/6c424f6258a2128bb4ae6e7591fb7ed1f79b042978397c24a1e35d8bdfcd8960655b3cdd0c4d8ff90f3055ce5b00eca5cb61b327720e7029b3f334fd8ae1776d languageName: node linkType: hard @@ -10706,19 +10718,19 @@ __metadata: languageName: node linkType: hard -"metro-symbolicate@npm:0.83.2": - version: 0.83.2 - resolution: "metro-symbolicate@npm:0.83.2" +"metro-symbolicate@npm:0.83.7": + version: 0.83.7 + resolution: "metro-symbolicate@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" invariant: "npm:^2.2.4" - metro-source-map: "npm:0.83.2" + metro-source-map: "npm:0.83.7" nullthrows: "npm:^1.1.1" source-map: "npm:^0.5.6" vlq: "npm:^1.0.0" bin: metro-symbolicate: src/index.js - checksum: 10c0/0c021d63520a9f8daaa70165d42c3050f46de31a02ed372aef19ca3027c5beddb14cf9623942142ff493680c094057d7314f2d712419231f3ff3dcf77f18f790 + checksum: 10c0/4968a89d9bdd040e0cb6328852f14a8555e912875a1661e639566879e548fbfb849134b8f55076f875c062d202c713be1c9f47b4e68c627a4f8b367d7e208213 languageName: node linkType: hard @@ -10736,17 +10748,17 @@ __metadata: languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.2": - version: 0.83.2 - resolution: "metro-transform-plugins@npm:0.83.2" +"metro-transform-plugins@npm:0.83.7": + version: 0.83.7 + resolution: "metro-transform-plugins@npm:0.83.7" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" + "@babel/generator": "npm:^7.29.1" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" nullthrows: "npm:^1.1.1" - checksum: 10c0/55925ace9b878721b478f0c0e95abdbd7d834c4738611a6b5c4a3e457a0f01c81b17356c3158fd70960c7f01b43ff641de2b2ad28888afaac21d73c541820ee1 + checksum: 10c0/2fce17ba1013e96278bd6800141ff050af0331a60c4c0dbc24f83c6b524806b6ff4089b36e3268c2315375211d16e875d644b7cbf2b23d43dc050047ec019556 languageName: node linkType: hard @@ -10771,24 +10783,24 @@ __metadata: languageName: node linkType: hard -"metro-transform-worker@npm:0.83.2": - version: 0.83.2 - resolution: "metro-transform-worker@npm:0.83.2" +"metro-transform-worker@npm:0.83.7": + version: 0.83.7 + resolution: "metro-transform-worker@npm:0.83.7" dependencies: "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" flow-enums-runtime: "npm:^0.0.6" - metro: "npm:0.83.2" - metro-babel-transformer: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-cache-key: "npm:0.83.2" - metro-minify-terser: "npm:0.83.2" - metro-source-map: "npm:0.83.2" - metro-transform-plugins: "npm:0.83.2" + metro: "npm:0.83.7" + metro-babel-transformer: "npm:0.83.7" + metro-cache: "npm:0.83.7" + metro-cache-key: "npm:0.83.7" + metro-minify-terser: "npm:0.83.7" + metro-source-map: "npm:0.83.7" + metro-transform-plugins: "npm:0.83.7" nullthrows: "npm:^1.1.1" - checksum: 10c0/ea8a0e6bdf24dc5719edb0f2aae0e5bf70f6801cebed8a952fd2d3a1eec95640ac1176d0132faa878897b524ff9828a07810ff22e8a82f007f235d470b027e93 + checksum: 10c0/a84e3bae6679826059d66bc9438f00292d93569bdefb4193bcbe4c9a72f5423d88d8d3da94389f95674674118af1ee233ac8a2b5b8279ff568c8becf4c609f1c languageName: node linkType: hard @@ -10842,44 +10854,43 @@ __metadata: languageName: node linkType: hard -"metro@npm:0.83.2, metro@npm:^0.83.1": - version: 0.83.2 - resolution: "metro@npm:0.83.2" +"metro@npm:0.83.7, metro@npm:^0.83.1": + version: 0.83.7 + resolution: "metro@npm:0.83.7" dependencies: - "@babel/code-frame": "npm:^7.24.7" + "@babel/code-frame": "npm:^7.29.0" "@babel/core": "npm:^7.25.2" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.3" - "@babel/types": "npm:^7.25.2" - accepts: "npm:^1.3.7" - chalk: "npm:^4.0.0" + "@babel/generator": "npm:^7.29.1" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + accepts: "npm:^2.0.0" ci-info: "npm:^2.0.0" connect: "npm:^3.6.5" debug: "npm:^4.4.0" error-stack-parser: "npm:^2.0.6" flow-enums-runtime: "npm:^0.0.6" graceful-fs: "npm:^4.2.4" - hermes-parser: "npm:0.32.0" + hermes-parser: "npm:0.35.0" image-size: "npm:^1.0.2" invariant: "npm:^2.2.4" jest-worker: "npm:^29.7.0" jsc-safe-url: "npm:^0.2.2" lodash.throttle: "npm:^4.1.1" - metro-babel-transformer: "npm:0.83.2" - metro-cache: "npm:0.83.2" - metro-cache-key: "npm:0.83.2" - metro-config: "npm:0.83.2" - metro-core: "npm:0.83.2" - metro-file-map: "npm:0.83.2" - metro-resolver: "npm:0.83.2" - metro-runtime: "npm:0.83.2" - metro-source-map: "npm:0.83.2" - metro-symbolicate: "npm:0.83.2" - metro-transform-plugins: "npm:0.83.2" - metro-transform-worker: "npm:0.83.2" - mime-types: "npm:^2.1.27" + metro-babel-transformer: "npm:0.83.7" + metro-cache: "npm:0.83.7" + metro-cache-key: "npm:0.83.7" + metro-config: "npm:0.83.7" + metro-core: "npm:0.83.7" + metro-file-map: "npm:0.83.7" + metro-resolver: "npm:0.83.7" + metro-runtime: "npm:0.83.7" + metro-source-map: "npm:0.83.7" + metro-symbolicate: "npm:0.83.7" + metro-transform-plugins: "npm:0.83.7" + metro-transform-worker: "npm:0.83.7" + mime-types: "npm:^3.0.1" nullthrows: "npm:^1.1.1" serialize-error: "npm:^2.1.0" source-map: "npm:^0.5.6" @@ -10888,7 +10899,7 @@ __metadata: yargs: "npm:^17.6.2" bin: metro: src/cli.js - checksum: 10c0/4c3cc7c2a455471d05757b567a0f2ca604a33f55ffbf2d838dbba8519396f2514b00d6a9214af288343be0277655e3397f2e7816506ec41aed16a9fa54585018 + checksum: 10c0/f759631778ce2e8cd7e1338ec886ffa0e3d4cd8456d5a506c57e5212206519435a345bcac6334dac22b3adf188b34854688007ddb1669c65afbadf0a0eef431a languageName: node linkType: hard @@ -10916,7 +10927,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:3.0.2": +"mime-types@npm:3.0.2, mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -11362,12 +11373,12 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.83.2": - version: 0.83.2 - resolution: "ob1@npm:0.83.2" +"ob1@npm:0.83.7": + version: 0.83.7 + resolution: "ob1@npm:0.83.7" dependencies: flow-enums-runtime: "npm:^0.0.6" - checksum: 10c0/fadcdb9e801458ebc66a09554d3535978e031e1c29e1d450f64f4d8da53ba1505b029726349a3211c5d388af49d1e8bb4f50304526ca237cb2b861177eeea327 + checksum: 10c0/b009d7a79b7ccf8db520acdc713a544a1ec379ae804438328080715c66944aa871551b8d7b1cf7cfb89cbe53bbab29f3345097c455259d7b7fe76b0891860187 languageName: node linkType: hard @@ -12188,9 +12199,9 @@ __metadata: linkType: hard "react-is@npm:^19.1.0": - version: 19.1.1 - resolution: "react-is@npm:19.1.1" - checksum: 10c0/3dba763fcd69835ae263dcd6727d7ffcc44c1d616f04b7329e67aefdc66a567af4f8dcecdd29454c7a707c968aa1eb85083a83fb616f01675ef25e71cf082f97 + version: 19.2.8 + resolution: "react-is@npm:19.2.8" + checksum: 10c0/ed5322c84efe035c8fc814b1614ff5ca7fb8c2872a7c045197daf99f9b1bd68f32a2c79ffcbcfcff2fc5d93924ff9f9447400898f5b1534e6302bb0736a257f0 languageName: node linkType: hard @@ -12266,6 +12277,7 @@ __metadata: "@eslint/js": "npm:^10.0.1" "@expo/metro-config": "npm:~54.0.5" "@ianvs/prettier-plugin-sort-imports": "npm:^4.4.2" + "@react-native/jest-preset": "npm:^0.85.0" "@release-it/conventional-changelog": "npm:10.0.1" "@tailwindcss/postcss": "npm:^4.1.12" "@testing-library/react-native": "npm:^13.3.3" From cc223c66e88b86b5bf6bb3970969dbe303340bd5 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Sun, 16 Aug 2026 00:14:48 +0300 Subject: [PATCH 2/4] fix: own the color scheme union instead of react-native's react-native's ColorSchemeName is not stable across this package's `react-native: >=0.81` peer range. Up to 0.85 it is "light" | "dark" | null | undefined; from 0.86 it is "light" | "dark" | "unspecified", and Appearance.setColorScheme rejects null. Six of the seven type errors the package produces on 0.86 come from that one inversion, and while the type is re-exported from react-native no single source can satisfy both ends of the range. ColorSchemeName is now the package's own union of every spelling, so colorScheme is one API across the range whichever version is installed. The one call the two versions declare incompatibly, the "follow the system" write, goes through setAppearanceColorScheme: "light" and "dark" keep the shared signature, and everything else is sent as "unspecified". That reaches the native module identically on both floors, because 0.85 and earlier map null to "unspecified" themselves before handing it down. The new suite pins both planes. The type aliases are read off the installed react-native rather than restating either spelling, so typecheck fails at whichever end of the range the checkout is on if coverage ever lapses; the runtime cases assert what the native module actually receives. --- src/__tests__/native/color-scheme.test.tsx | 105 +++++++++++++++++++++ src/color-scheme.ts | 35 +++++++ src/jest/index.ts | 5 +- src/native/reactivity.ts | 9 +- src/runtime.types.ts | 24 +++-- src/web/api.tsx | 3 +- 6 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 src/__tests__/native/color-scheme.test.tsx create mode 100644 src/color-scheme.ts diff --git a/src/__tests__/native/color-scheme.test.tsx b/src/__tests__/native/color-scheme.test.tsx new file mode 100644 index 00000000..0beced3f --- /dev/null +++ b/src/__tests__/native/color-scheme.test.tsx @@ -0,0 +1,105 @@ +import { + Appearance, + type ColorSchemeName as ReactNativeColorSchemeName, +} from "react-native"; + +import { act, render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS, testID } from "react-native-css/jest"; +import { colorScheme } from "react-native-css/runtime"; +import type { ColorSchemeName } from "react-native-css/runtime.types"; + +import { setAppearanceColorScheme } from "../../color-scheme"; + +const mockSetColorScheme = jest.fn(); + +// `Appearance` reads its native module lazily, so mocking the module is enough to make +// the writes observable. Without it `TurboModuleRegistry.get('Appearance')` is null +// under jest and every `setColorScheme` is a silent no-op. +jest.mock("react-native/Libraries/Utilities/NativeAppearance", () => ({ + __esModule: true, + default: { + setColorScheme: (scheme: string) => { + mockSetColorScheme(scheme); + }, + // `getColorScheme` feeds react-native's own post-write bookkeeping, which asserts + // the value is a resolved scheme or absent. + getColorScheme: () => undefined, + addListener: () => undefined, + removeListeners: () => undefined, + }, +})); + +/******************************* Compile plane ******************************/ + +type Assert = T; +type Covers = [Actual] extends [Accepted] ? true : false; + +type AppearanceChangeColorScheme = Parameters< + Parameters[0] +>[0]["colorScheme"]; + +/** + * `ColorSchemeName` is react-native-css's own union rather than react-native's, because + * react-native's changes shape inside this package's peer range: up to 0.85 "follow the + * system" is `null | undefined`, from 0.86 it is `"unspecified"`. + * + * These aliases are read off the installed react-native rather than restating either + * spelling, so `yarn typecheck` fails at whichever end of the range the checkout is on + * the moment the package stops covering a value react-native hands it. + */ +export type CoversReactNativeUnion = Assert< + Covers +>; +export type CoversAppearanceRead = Assert< + Covers, ColorSchemeName> +>; +export type CoversAppearanceEvent = Assert< + Covers +>; + +/******************************* Runtime plane ******************************/ + +beforeEach(() => { + mockSetColorScheme.mockClear(); +}); + +test.each([ + ["light", "light"], + ["dark", "dark"], + ["unspecified", "unspecified"], + [null, "unspecified"], + [undefined, "unspecified"], +] satisfies [ColorSchemeName, string][])( + "setAppearanceColorScheme(%p) reaches the native module as %p", + (value, expected) => { + setAppearanceColorScheme(value); + + expect(mockSetColorScheme).toHaveBeenCalledTimes(1); + expect(mockSetColorScheme).toHaveBeenCalledWith(expected); + }, +); + +test("prefers-color-scheme follows the scheme and releases on 'unspecified'", () => { + registerCSS(` +.my-class { color: blue; } + +@media (prefers-color-scheme: dark) { + .my-class { color: red; } +}`); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style).toStrictEqual({ color: "#00f" }); + + act(() => { + colorScheme.set("dark"); + }); + expect(component.props.style).toStrictEqual({ color: "#f00" }); + + act(() => { + colorScheme.set("unspecified"); + }); + expect(component.props.style).toStrictEqual({ color: "#00f" }); +}); diff --git a/src/color-scheme.ts b/src/color-scheme.ts new file mode 100644 index 00000000..8f8a0eb1 --- /dev/null +++ b/src/color-scheme.ts @@ -0,0 +1,35 @@ +import { Appearance } from "react-native"; + +import type { ColorSchemeName } from "./runtime.types"; + +/** + * `Appearance.setColorScheme` is not declared the same way across the supported + * `react-native` range. Up to 0.85 it takes `"light" | "dark" | null | undefined`; + * from 0.86 it takes `"light" | "dark" | "unspecified"` and rejects `null`. The two + * parameter types overlap only on `"light" | "dark"`, so no single call can name the + * "follow the system" value and type-check on both. + * + * The runtime is not split the same way: every version in the range hands + * `"unspecified"` to the native module for that state, because 0.85 and earlier map + * `null` to it on the way in. So only the declaration has to be restated, and only for + * the one value the versions disagree about. + */ +interface AppearanceColorSchemeReset { + setColorScheme: (colorScheme: "unspecified") => void; +} + +/** + * Writes a color scheme to `Appearance` in a way that compiles and runs on every + * `react-native` this package supports. + */ +export function setAppearanceColorScheme(value: ColorSchemeName): void { + if (value === "light" || value === "dark") { + // Declared identically by every version in the range. + Appearance.setColorScheme(value); + return; + } + + (Appearance as unknown as AppearanceColorSchemeReset).setColorScheme( + "unspecified", + ); +} diff --git a/src/jest/index.ts b/src/jest/index.ts index cc125390..1c335f71 100644 --- a/src/jest/index.ts +++ b/src/jest/index.ts @@ -1,10 +1,11 @@ -import { Appearance, Dimensions } from "react-native"; +import { Dimensions } from "react-native"; import { inspect } from "node:util"; import { compile, type CompilerOptions } from "react-native-css/compiler"; import { StyleCollection } from "react-native-css/native"; +import { setAppearanceColorScheme } from "../color-scheme"; import { colorScheme, dimensions } from "../native/reactivity"; declare global { @@ -21,7 +22,7 @@ export const testID = "react-native-css"; beforeEach(() => { StyleCollection.styles.clear(); dimensions.set(Dimensions.get("window")); - Appearance.setColorScheme(null); + setAppearanceColorScheme(null); colorScheme.set(null); }); diff --git a/src/native/reactivity.ts b/src/native/reactivity.ts index 0824edeb..67b43be2 100644 --- a/src/native/reactivity.ts +++ b/src/native/reactivity.ts @@ -1,14 +1,11 @@ /* eslint-disable */ import { createContext } from "react"; -import { - Appearance, - Dimensions, - type ColorSchemeName, - type LayoutRectangle, -} from "react-native"; +import { Appearance, Dimensions, type LayoutRectangle } from "react-native"; import type { StyleDescriptor } from "react-native-css/compiler"; +import type { ColorSchemeName } from "../runtime.types"; + export type Effect = { observers: Set; run(): void; diff --git a/src/runtime.types.ts b/src/runtime.types.ts index 6e8f9ad8..56e91e47 100644 --- a/src/runtime.types.ts +++ b/src/runtime.types.ts @@ -8,12 +8,7 @@ import type { FunctionComponent, ReactElement, } from "react"; -import type { - ColorSchemeName, - ImageStyle, - TextStyle, - ViewStyle, -} from "react-native"; +import type { ImageStyle, TextStyle, ViewStyle } from "react-native"; import type { DotNotation, ResolveDotPath } from "react-native-css/utilities"; @@ -144,6 +139,23 @@ export type RNStyle = ViewStyle & TextStyle & ImageStyle; /******************************** Globals ********************************/ +/** + * A color scheme, plus every spelling of "follow the system" in the supported + * `react-native` range. + * + * This deliberately does not reuse `react-native`'s own `ColorSchemeName`: that type + * is not stable across the `react-native` peer range. Up to 0.85 it is + * `"light" | "dark" | null | undefined`; from 0.86 it is + * `"light" | "dark" | "unspecified"`. Owning the union keeps `colorScheme` one API + * across the whole range, and accepts whichever spelling the installed version emits. + */ +export type ColorSchemeName = + | "light" + | "dark" + | "unspecified" + | null + | undefined; + export interface ColorScheme { get: () => ColorSchemeName; set: (value: ColorSchemeName) => void; diff --git a/src/web/api.tsx b/src/web/api.tsx index e43f800a..eb1c0e43 100644 --- a/src/web/api.tsx +++ b/src/web/api.tsx @@ -15,6 +15,7 @@ import type { StyledProps, } from "react-native-css"; +import { setAppearanceColorScheme } from "../color-scheme"; import type { ReactComponent } from "../runtime.types"; import { assignStyle } from "./assign-style"; @@ -73,7 +74,7 @@ export const colorScheme: ColorScheme = { return Appearance.getColorScheme(); }, set(name) { - Appearance.setColorScheme(name); + setAppearanceColorScheme(name); }, }; From fc224bed1c8ad6fe359d9947256d19ef9cd388f0 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Sun, 16 Aug 2026 00:15:10 +0300 Subject: [PATCH 3/4] fix: treat component instances as dot-notation leaves ScrollViewProps.scrollViewRef is a RefObject, and ScrollView holds ScrollViewProps again, so the prop graph DotNotation walks is cyclic. Bounded at MaxDepth 10 that single prop contributes 4,654 paths on react-native 0.81, and on 0.86, where the host instance surface is wider, building the union crosses TypeScript's 100,000 member limit: StyledConfiguration fails with TS2590 and takes src/components/FlatList.tsx with it. None of those paths can ever be a mapping target. They run through an object that does not exist until render. Treating a class component instance as a leaf cuts the cycle at its source, rather than lowering MaxDepth, which would only move the threshold to the next prop type that grows. Measured on react-native 0.81.4, where both the old and new definitions resolve: ViewProps is unchanged at 733 paths, ScrollViewProps drops from 7,482 to 2,094 and FlatListProps from 9,204 to 3,816. Every removed path traverses a ref, and nothing is added. --- src/__tests__/native/components.test.tsx | 21 +++++++++++++++++++++ src/utilities/dot-notation.types.ts | 15 +++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/__tests__/native/components.test.tsx b/src/__tests__/native/components.test.tsx index 3a596038..4f62ccbe 100644 --- a/src/__tests__/native/components.test.tsx +++ b/src/__tests__/native/components.test.tsx @@ -1,5 +1,6 @@ import { Button as RNButton, + ScrollView as RNScrollView, TextInput as RNTextInput, type ButtonProps, type TextInputProps, @@ -11,6 +12,7 @@ import { TextInput } from "react-native-css/components/TextInput"; import { registerCSS, testID } from "react-native-css/jest"; import { useCssElement } from "react-native-css/native"; import type { + ComponentPropsDotNotation, StyledConfiguration, StyledProps, } from "react-native-css/runtime.types"; @@ -114,3 +116,22 @@ test("nativeStyleMapping with boolean true on custom component", () => { expect(component.props.textAlign).toBe("right"); expect(component.props.style).not.toHaveProperty("textAlign"); }); + +test("a prop holding a component instance is a dot-notation leaf", () => { + // `ScrollViewProps.scrollViewRef` is a `RefObject`, and `ScrollView` + // carries `ScrollViewProps` again, so the prop graph is cyclic. Enumerating it + // produces paths that can never be a mapping target, and enough of them that + // `StyledConfiguration`, and every component built on it, stops + // compiling with TS2590 on react-native >=0.86. + const reachable: ComponentPropsDotNotation[] = [ + "scrollViewRef", + "scrollViewRef.current", + ]; + + // @ts-expect-error - the instance behind the ref is a leaf, so its props are not paths + const throughInstance: ComponentPropsDotNotation = + "scrollViewRef.current.props.style"; + + expect(reachable).toHaveLength(2); + expect(throughInstance).toBe("scrollViewRef.current.props.style"); +}); diff --git a/src/utilities/dot-notation.types.ts b/src/utilities/dot-notation.types.ts index 6700a21a..9746df38 100644 --- a/src/utilities/dot-notation.types.ts +++ b/src/utilities/dot-notation.types.ts @@ -1,4 +1,6 @@ /* eslint-disable */ +import type { Component } from "react"; + // ---------- Base Utilities ---------- type Falsy = undefined | null | false | ""; @@ -51,13 +53,22 @@ type ExtractStyleObject = RemoveRegisteredStyle< RemoveFalsy ? U : T>> >; -// Check if something is a non-array plain object +// Check if something is a non-array plain object. +// +// A class component instance is not one. It is reachable only through a ref, it holds +// its own props, and those props hold refs again, so the prop graph is cyclic. Walking +// it yields paths that can never be a mapping target, and the path set grows +// exponentially with depth: `ScrollViewProps["scrollViewRef"]` alone contributes 4,654 +// such paths, which is enough to overflow TypeScript's union limit on react-native +// >=0.86. Treating the instance as a leaf cuts the cycle where it starts. type IsPlainObject = T extends object ? T extends Function ? false : T extends readonly any[] ? false - : true + : T extends Component + ? false + : true : false; // ---------- Resolve Path Type Helpers ---------- From 8189bb65b17133105c68f5ce2079bbe44e6d559a Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Sun, 16 Aug 2026 00:32:17 +0300 Subject: [PATCH 4/4] docs: name the react-native version each break actually starts at Measured `yarn typecheck` on upstream/main against every minor in the peer range, one install each: 0.81.4 gives 0 errors, 0.82.0 gives 6, and 0.83.0 through 0.86.0 give 7. So the color scheme break starts at 0.82, where ColorSchemeName became "light" | "dark" | "unspecified" and setColorScheme stopped accepting null, and the TS2590 break starts at 0.83, where the wider host instance surface pushes the cyclic ref enumeration over TypeScript's union limit. 0.81 is the only minor in a `>=0.81` range that compiles. The runtime half moves with it: 0.82 and later pass the argument straight to the native module, so only 0.81 maps null to "unspecified" on the way in. That makes "unspecified" the value that is right everywhere, which is what the code already sends; only the comments named the wrong boundary. --- src/__tests__/native/components.test.tsx | 2 +- src/color-scheme.ts | 12 ++++++------ src/runtime.types.ts | 4 ++-- src/utilities/dot-notation.types.ts | 5 +++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/__tests__/native/components.test.tsx b/src/__tests__/native/components.test.tsx index 4f62ccbe..df89fc4e 100644 --- a/src/__tests__/native/components.test.tsx +++ b/src/__tests__/native/components.test.tsx @@ -122,7 +122,7 @@ test("a prop holding a component instance is a dot-notation leaf", () => { // carries `ScrollViewProps` again, so the prop graph is cyclic. Enumerating it // produces paths that can never be a mapping target, and enough of them that // `StyledConfiguration`, and every component built on it, stops - // compiling with TS2590 on react-native >=0.86. + // compiling with TS2590 on react-native >=0.83. const reachable: ComponentPropsDotNotation[] = [ "scrollViewRef", "scrollViewRef.current", diff --git a/src/color-scheme.ts b/src/color-scheme.ts index 8f8a0eb1..669a595f 100644 --- a/src/color-scheme.ts +++ b/src/color-scheme.ts @@ -4,15 +4,15 @@ import type { ColorSchemeName } from "./runtime.types"; /** * `Appearance.setColorScheme` is not declared the same way across the supported - * `react-native` range. Up to 0.85 it takes `"light" | "dark" | null | undefined`; - * from 0.86 it takes `"light" | "dark" | "unspecified"` and rejects `null`. The two + * `react-native` range. On 0.81 it takes `"light" | "dark" | null | undefined`; from + * 0.82 it takes `"light" | "dark" | "unspecified"` and rejects `null`. The two * parameter types overlap only on `"light" | "dark"`, so no single call can name the * "follow the system" value and type-check on both. * - * The runtime is not split the same way: every version in the range hands - * `"unspecified"` to the native module for that state, because 0.85 and earlier map - * `null` to it on the way in. So only the declaration has to be restated, and only for - * the one value the versions disagree about. + * `"unspecified"` is the value that is right at runtime on every version: 0.82 and + * later hand the argument straight to the native module, and 0.81 maps `null` to + * `"unspecified"` before doing the same. So only the declaration has to be restated, + * and only for the one value the versions disagree about. */ interface AppearanceColorSchemeReset { setColorScheme: (colorScheme: "unspecified") => void; diff --git a/src/runtime.types.ts b/src/runtime.types.ts index 56e91e47..531d0a13 100644 --- a/src/runtime.types.ts +++ b/src/runtime.types.ts @@ -144,8 +144,8 @@ export type RNStyle = ViewStyle & TextStyle & ImageStyle; * `react-native` range. * * This deliberately does not reuse `react-native`'s own `ColorSchemeName`: that type - * is not stable across the `react-native` peer range. Up to 0.85 it is - * `"light" | "dark" | null | undefined`; from 0.86 it is + * is not stable across the `react-native` peer range. On 0.81 it is + * `"light" | "dark" | null | undefined`; from 0.82 it is * `"light" | "dark" | "unspecified"`. Owning the union keeps `colorScheme` one API * across the whole range, and accepts whichever spelling the installed version emits. */ diff --git a/src/utilities/dot-notation.types.ts b/src/utilities/dot-notation.types.ts index 9746df38..51332ece 100644 --- a/src/utilities/dot-notation.types.ts +++ b/src/utilities/dot-notation.types.ts @@ -59,8 +59,9 @@ type ExtractStyleObject = RemoveRegisteredStyle< // its own props, and those props hold refs again, so the prop graph is cyclic. Walking // it yields paths that can never be a mapping target, and the path set grows // exponentially with depth: `ScrollViewProps["scrollViewRef"]` alone contributes 4,654 -// such paths, which is enough to overflow TypeScript's union limit on react-native -// >=0.86. Treating the instance as a leaf cuts the cycle where it starts. +// such paths on react-native 0.81, and from 0.83 the host instance surface is wide +// enough that building the union overflows TypeScript's limit. Treating the instance as +// a leaf cuts the cycle where it starts. type IsPlainObject = T extends object ? T extends Function ? false