Skip to content

Add mobile tests - #102

Merged
Jberma23 merged 9 commits into
mainfrom
add-mobile-tests
Aug 6, 2026
Merged

Add mobile tests#102
Jberma23 merged 9 commits into
mainfrom
add-mobile-tests

Conversation

@Jberma23

@Jberma23 Jberma23 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

PaulgSmith and others added 9 commits August 5, 2026 23:31
Adds a React Native app in mobile/ (Expo SDK 57, React 19, RN 0.86,
expo-router, TypeScript) and the Rails API it talks to.

Rails side:
  - Api::BaseController inherits ActionController::API, so the phone is
    not subject to CSRF, cookies or the allow_browser version check, and
    renders 404/400/422 as predictable JSON.
  - Api::V1::TasksController exposes CRUD at /api/v1/tasks. Task is a
    placeholder resource wired end to end, meant to be replaced.
  - rack-cors for the Expo web target. Native builds ignore CORS.
  - Development Host Authorization now accepts .local hostnames, which
    were 403ing requests from phones resolving the host over mDNS.

Mobile side:
  - src/lib/api.ts derives the API host from the Metro bundler address
    instead of hardcoding localhost, which resolves differently on the
    iOS simulator, the Android emulator (10.0.2.2) and real devices.
    EXPO_PUBLIC_API_URL overrides it and is required for release builds.
  - Tasks screen lists, creates, toggles and deletes through the API,
    updating optimistically and rolling back on failure.

Housekeeping: rubocop, brakeman and the Docker build context skip
mobile/, whose node_modules ship Ruby CocoaPods scripts that would
otherwise be linted and scanned as our own. WSL Zone.Identifier files
are now gitignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a6fb86f)
The SDK 57 rescaffold reset several things that had been customised on
the earlier add-expo-mobile branch. Carried them across:

  - app.json: name "Along With You", slug along-with-you, and the
    alongwithyou:// URL scheme. The scaffold defaults meant the app
    installed on a phone as "mobile" and claimed the mobile:// scheme,
    which matters as soon as OAuth callbacks or push notifications
    need a deep link.
  - package.json: name alongwithyou-mobile, plus the typecheck script.
  - engines.node, set to >=20.19.4 rather than the previous >=20.19.0,
    because React Native 0.86 will not build below that.
  - eslint, installed at the versions matching SDK 57 rather than the
    SDK 54 ones, and mobile/README.md rewritten for the src/ layout and
    the API client, which the old copy predates.

Two react-hooks/set-state-in-effect errors surfaced once linting ran.
Loading tasks no longer sets state before its first await, which was a
real cascading render. The remaining report on the mount effect, and one
in Expo's own use-color-scheme.web.ts, are suppressed inline: both want
Suspense or a data library, which this app does not have yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 27d15c0)
Two error paths escaped the JSON contract Api::BaseController sets up, and
both are ones a phone hits in practice:

- A truncated or malformed request body raised
  ActionDispatch::Http::Parameters::ParseError, which is handled by
  middleware and rendered as an HTML error page. The client's JSON.parse
  then threw a SyntaxError instead of surfacing the real problem.
- Any unmatched path under /api fell through to the HTML 404 page.

Also drop the new/edit routes, which only make sense for HTML forms and
would otherwise shadow the JSON 404 with a missing action error.

The newest-first index test could not fail before this: fixtures share a
single created_at, so the order was up to SQLite and the assertion held
either way. Fixtures now carry explicit timestamps and the test asserts
the ids it expects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Requests had no timeout. A network that drops packets rather than
  refusing the connection - a phone off the wifi, a laptop asleep behind a
  port forward - left fetch pending forever and the Tasks spinner with it.
- JSON.parse ran unguarded on the response body, so an HTML page from a
  proxy, a tunnel or a Rails error outside /api threw a bare SyntaxError
  instead of an ApiError, and the UI showed "Something went wrong."
- Rolling back a failed delete restored a snapshot of the whole list,
  undoing anything that changed while the request was in flight. Two
  deletes in a row could resurrect the first row. Restore by position.
- npm run reset-project deletes src/ and scripts/. That is fine in a fresh
  Expo template; here it deletes the app.
- The web tab bar introduced the app as "Expo Starter".

types/expo.d.ts holds the reference Expo generates into the gitignored
expo-env.d.ts, without which `npm run typecheck` fails on a fresh clone
(cannot find '@/global.css', '../animated-icon.module.css').

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14k lines of TypeScript arrived with nothing checking them: CI runs
rubocop, brakeman and the Ruby tests only, and dependabot watched bundler
and github-actions but not mobile/package.json, so 822 npm packages had no
update or advisory coverage at all.

The npm audit gate is set to high: Expo's build toolchain currently pulls
in uuid < 11.1.1 through @expo/config-plugins, which nothing here can
resolve, and 10 packages depend on it.

Also correct the test commands in the README (`npx tsc --noEmit` is
`npm run typecheck`, and `bin/ci` is Ruby-only), and record the follow-ups
this review turned up but did not change: rate limiting, app store
identifiers, the leftover Expo template screens, and the licensing
question `mobile/LICENSE` raises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A 12.8 MB core dump from React Native DevTools (mobile/core.9043) came
with this work and is dropped from the branch. mobile/.gitignore already
lists core.*; the root did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tasks.completed is NOT NULL with nothing above the database enforcing it,
and the column is permitted in task_params. PATCH with {"completed":null}
reached SQLite and raised ActiveRecord::NotNullViolation: a 500 with an
HTML body, from the controller whose whole job is predictable JSON.

Values that cast to a boolean still pass, so "1", "0", "true" and "false"
behave as before. Only nil - an explicit null, or "" - is rejected, and it
comes back as a field error the client can render.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jest-expo plus React Native Testing Library, wired into the mobile CI job.
Nineteen tests across the two pieces most worth covering first:

- src/lib/api.test.ts drives the client against a mocked fetch: how the API
  URL is resolved for a release build, the Android emulator, a LAN device
  and a bare localhost, and what each failure becomes - a 422 with field
  errors, an HTML body, an unreachable host, a server that never answers.
- src/__tests__/tasks-screen.test.tsx drives the screen against a mocked
  client: listing, adding, toggling, retrying after a failed load, and
  keeping a row that failed to delete.

Two things this setup has to work around, both documented where they bite:

- Expo Router turns every file under src/app/ into a route, and its context
  regex only skips +api, +html and +middleware. A test beside a screen would
  ship as a route, so screen tests live in src/__tests__/.
- TypeScript 6 does not pull @types/jest in automatically here, so each test
  file references it directly rather than putting test-only types in
  tsconfig.json.

Writing the tests turned up one thing worth knowing about the client: in
development it always resolves a URL, falling back to localhost when Metro
never reported a host, so the "No API URL configured" path only exists in a
release build. Both behaviours are now pinned by tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jberma23
Jberma23 merged commit bb4f575 into main Aug 6, 2026
6 checks passed
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.

2 participants