Skip to content

fix: six small findings, plus a windows CI lane - #428

Open
YevheniiKotyrlo wants to merge 6 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/small-findings
Open

fix: six small findings, plus a windows CI lane#428
YevheniiKotyrlo wants to merge 6 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/small-findings

Conversation

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor

Six small independent findings, each separable — split any of them out if you would rather take them apart.

Fixes

  1. prefers-color-scheme resolved to nothing when the OS reported no preference. It now resolves to light, which is what the CSS spec says no-preference means for this feature. Without it a dark: rule and its unprefixed counterpart could both fail to apply.
  2. src/web/api.tsx called a method that does not exist. react-native-web@0.21.1's Appearance exports only getColorScheme and addChangeListener — there is no setColorScheme, so colorScheme.set(…) was a hard TypeError on web. It is invisible to tsc because the file imports Appearance from "react-native", so TypeScript resolves React Native's own .d.ts while the bundler substitutes react-native-web. It now reports that the scheme cannot be set on web rather than throwing.

Tests, and one CI lane

  1. ci: run the unit suite on windows. ci.yml was ubuntu-latest plus one macos-15, with no Windows runner — which is why a three-test failure sat in main unnoticed and why a previous cross-platform test of mine was inert on the only runner that existed. This is the smallest change here and probably the most valuable.
  2. prefers-color-scheme had no compiler test. media-query.test.ts had no case for it — its platform block is describe.skip, leaving only hover. The single existing assertion of that condition reached it via light-dark(), where the compiler synthesises the condition, so the @media parse path was unproven. The platform media queries are revived in the same commit.
  3. src/metro/ had no tests at all. metro/resolver.ts is the only import rewriter when globalClassNamePolyfill is false, and the babel plugin the only one when it is true — they are alternatives, not layers, so one of the two rewriting paths was entirely unexercised. This covers the gate.
  4. dynamicRootVariables so a :root test reaches the runtime rather than asserting only the compiled output.

Note

Item 2 is a behaviour change on web for anyone currently calling colorScheme.set there — today that call throws, so nothing can be depending on it working, but it is the one item here that is not purely additive. Happy to split it out if you would rather take it separately.

yarn typecheck and yarn lint exit 0. The 3 remaining failures are the known Windows babel-plugin-tester baseline, present on main.

`colorScheme.set()` called `Appearance.setColorScheme`, which does not exist.
The web plane imports `Appearance` from "react-native" and the bundler
substitutes react-native-web, whose Appearance reads through to
`matchMedia("(prefers-color-scheme: dark)")` and exposes only `getColorScheme`
and `addChangeListener`. Every call was a TypeError.

TypeScript cannot see this: it resolves react-native's `.d.ts` for that import
either way, so the substitution is invisible to `yarn typecheck`.

The browser owns the color scheme on web and evaluates
`@media (prefers-color-scheme)` itself, so there is no observable for an
override to drive the way there is on native. Throwing names that constraint;
returning silently would leave an in-app theme toggle broken with nothing to
find.

The test runs the web module against react-native-web, which is the first
coverage the web plane has had.
The compiler had no `@media (prefers-color-scheme: ...)` case. The one
assertion of that condition reaches it through `light-dark()`, where the
compiler synthesises the condition, so the parse path was unproven. The runtime
half had no direct coverage either — `testMediaQuery` was only ever reached
through a rendered component, which also exercises the collection and the
resolver, so a condition evaluated wrongly could still produce the right style.

The platform block was `describe.skip`. It is stale expectations, not a gap:
the conditions it asserts are produced correctly today, and the block fails
only on drift the compiler has since accumulated — `#ff0000` now shortens to
`#f00`, specificity is `[2, 1]`, and a `v` entry carries the inherited color.
The ios case also asserted one nesting level too few on `m`, which is an
authoring slip rather than drift. Corrected and unskipped.
`withReactNativeCSS` installs the `resolveRequest` that decides whether the
native and web resolvers run at all, and nothing exercised it. The resolvers
themselves are covered; the dispatch into them was not, so the gate could be
inverted, defaulted the other way, or dropped without a test noticing.

Covers both settings on both platforms, the metro-override short-circuit, and
the preference for a config's existing `resolveRequest` over the context's.
`inlineVariables` inlines a custom property that has exactly one declaration,
so `:root { --my-var: red }` compiles to a literal with no root variable entry
at all. A test written that way asserts the inliner and keeps passing with the
runtime variable registry deleted, which makes it silently worthless. Use count
does not save it: the pass counts declarations, so one declaration read from
ten rules is still inlined.

Rendering cannot tell the two apart either, because the inlined literal and the
resolved variable produce the same style. Only the compiled stylesheet shows
the difference.

`dynamicRootVariables` emits a second declaration behind a guard that never
matches, so the property stays dynamic. Both declarations carry the same value,
so the resolved value does not depend on the guard staying unmatched.

The tests pin the inliner's behaviour as well as the helper, so if it stops
inlining or starts keying on use count the helper can be retired.
Every job runs on ubuntu except one macos builder, so nothing in CI executes
this codebase on Windows. The babel plugin, the metro resolver and the compiler
all join and compare file paths, which is exactly the class of code a POSIX-only
matrix cannot vet.

That gap is not theoretical. At this commit, on a Windows host, three of the
repo's own tests fail: "7. import View from '../View/View'" in
src/__tests__/babel/react-native.test.ts, and "6. import View from '../View'"
and "17. const View = _interopRequireDefault(require('../View'))" in
src/__tests__/babel/react-native-web.test.ts. Relative imports are not
rewritten because the separator comparison assumes forward slashes.

Coverage stays on the ubuntu job; this one only needs to be able to fail.

Ordering: the fix for those three lives on fix/babel-windows-posix-paths. Merge
that branch first, or this job lands red.
… is set

`Appearance.getColorScheme()` answers null whenever the OS reports
`unspecified` or the native module is absent, so a null scheme is a reachable
production state rather than a test-harness artifact. Comparing the queried
value straight against it made `@media (prefers-color-scheme: light)` match
nothing in that state, so an explicit light rule silently never applied.

MQ5 resolves the absence of a preference to `light`, and the rest of the
library already assumes that. `light-dark(red, blue)` compiles to a light base
rule plus the dark value behind `["=", "prefers-color-scheme", "dark"]`;
`colorScheme.get()` on native ends in `?? "light"`; and react-native-web's
`getColorScheme()` reads the dark media query and answers "light" when it does
not match. Only this comparison disagreed, so a light rule behaved differently
on the two platforms.

Resolving the scheme before the comparison rather than branching on it keeps an
unrecognised value false, which MQ5 also requires.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant