Engineering Diary Day 3: 126 Tests, Image Deduplication & Automated Health Monitoring
Day 3: The Testing Ramp
We started the day at 91 tests. We ended at 126. That number does not look dramatic until you understand what it took to get there: custom mocking strategies for framer-motion, a monkey-patch to Node's module resolution system, and a thorough audit of image duplication across the entire platform.
This is Day 3 of VOLO's production hardening phase. The site has been live since February 14th. Phase A through D (marketing site, booking platform, Agent API, dual-mode UI) shipped before launch. Now we are in Phase E: making everything bulletproof.
Part I: Component Testing SmartBookingInput
SmartBookingInput is the heart of our booking experience — a 1,380-line component (recently refactored into 6 modules) that handles both an AI natural-language mode and a traditional form mode. Testing it meant solving a problem that trips up many React projects: how do you test components that depend heavily on framer-motion?
The framer-motion Mock Problem
framer-motion's motion.div and AnimatePresence are not simple wrappers. They inject layout measurement, gesture handling, and animation orchestration into the React tree. In a jsdom test environment, none of this works — and worse, framer-motion passes custom props (variants, initial, animate, exit, layout, custom) that are not valid DOM attributes, causing React to throw warnings.
Our solution: a comprehensive mock that replaces motion.div and motion.span with plain HTML elements, strips animation-specific props via a filterDomProps helper, and makes AnimatePresence a simple pass-through wrapper. The filter function explicitly removes 15 animation props while forwarding everything else — className, style, onClick, children, and all standard attributes.
What We Test
12 tests cover the critical paths:
- Rendering in default AI mode with the correct heading and placeholder
- Tab switching between AI and Form modes
- Textarea input in AI mode with real user event simulation
- Flight parsing chip display (e.g., typing "NYC to LA tomorrow" and seeing parsed chips)
- Form submission callback firing correctly
- Compact mode prop propagation
- Voice input hook integration (mocked to disabled state)
The rule we follow: mock the boundaries, test the behavior. We do not test that framer-motion animates correctly — that is framer-motion's job. We test that our component renders the right content and responds to user actions.
Part II: Airport Search Engine — 23 Tests and a Module Hack
Our airport search engine is powered by Fuse.js, indexing 7,900+ airports worldwide with weighted fuzzy matching across IATA codes, ICAO codes, city names (English and Chinese), and airport names. It is the autocomplete backbone for every "From" and "To" field in the booking flow.
The require() Alias Problem
The search engine loads its data with require("@/content/data/airports-global.json") — a deliberate choice for server-side-only data loading. The @/ prefix is a path alias that Next.js and Vite resolve automatically for ESM import statements. But require() is a Node.js built-in that bypasses Vite's resolution entirely.
We tried three approaches:
- Vite alias config — already present in
vitest.config.ts, works for imports but not forrequire() server.deps.fallbackCJS: true— a Vitest option that is supposed to handle CJS interop; did not interceptrequire()- Explicit alias for the JSON file path — added a direct alias mapping; still ignored by Node's
require()
The solution that worked: patching Module._resolveFilename at the top of the test file. This hooks into Node's internal module resolution to intercept any require() call starting with @/ and remap it to the absolute file path under src/. It is a 10-line monkey-patch that makes the entire airport search module testable without modifying production code.
Test Coverage
23 tests exercise every code path in the search engine:
- Exact match: IATA "JFK" returns John F. Kennedy first; ICAO "KJFK" also resolves
- Case insensitivity: "jfk", "Jfk", "JFK" all return the same result
- City prefix: "shangh" matches Shanghai, "lond" matches London airports
- Fuzzy search: "tokyo" matches Tokyo Narita and Haneda via Fuse.js
- Chinese search: querying with Chinese characters like the Chinese name for Shanghai returns correct results
- Edge cases: empty string, single character, whitespace-only, nonexistent code "ZZZ"
- Limit parameter: requesting 3 results returns at most 3
- Result shape: every result has the expected interface fields (iata, icao, name, city, country, lat, lng)
Testing a search engine is not about verifying that Fuse.js works. It is about verifying that your configuration — weights, thresholds, match strategies, priority logic — produces the results your users expect.
Part III: Image Deduplication Across the Platform
A comprehensive audit revealed that several stock images were being reused across multiple pages. For a luxury brand, visual monotony is a quiet credibility killer — a visitor who sees the same hero image on the About page and the Dining page subconsciously registers it as "template-grade."
What We Found
Four duplicate image groups across the site:
- Pexels 3745234 (scenic coastal shot): used on homepage, about page, dining page, and concierge page — 4 appearances
- Pexels 30547618 (modern architecture): used on homepage and concierge page — 2 appearances
- Unsplash 1534088568 (aircraft): used on charter and empty-legs pages — 2 appearances
- Pexels 20562278 (cabin interior): used 3 times on the cabin experience page
Replacement Strategy
Each replacement was selected to match the page's thematic context:
- About page: replaced with a panoramic landscape (Pexels 2373201) — appropriate for a company story section
- Dining page: replaced with fine dining plating (Pexels 30507463) — directly relevant to the in-flight cuisine narrative
- Concierge page: replaced with a luxury service interior (Pexels 912050) — matches the white-glove concierge theme
- Empty-legs hero: replaced with an aircraft on approach (Pexels 46148) — better communicates the empty-leg opportunity
- Cabin page break: replaced with an aerial wing shot (Pexels 62623) — adds visual rhythm between cabin interior sections
All 8 replacement URLs were verified via HTTP HEAD request (status 200) before committing.
Part IV: Automated Image Health Monitoring
In the previous engineering diary, we wrote about resurrecting 70 dead Unsplash URLs and promised an automated health check. Today we delivered it.
GitHub Actions Cron Workflow
A new workflow at .github/workflows/image-health.yml runs every Monday at 9:00 AM UTC. It installs the project, executes our existing check:images script (which sends HEAD requests to every image URL in the codebase), and if any URLs return non-200 status codes, automatically creates a GitHub issue titled "Broken Unsplash URLs detected" with instructions for the team.
The workflow also supports manual triggering via workflow_dispatch, so anyone can run it on demand from the GitHub Actions tab.
The best monitoring is the kind you set up once and forget about. If an image CDN removes a photo, we will know within a week — and the issue will already be in our backlog before anyone sees a broken image on the live site.
By the Numbers
| Metric | Before Today | After Today |
|---|---|---|
| Total passing tests | 91 | 126 (+35) |
| Test files | 4 | 6 (+2) |
| ESLint errors | 0 | 0 |
| Duplicate image groups | 4 | 0 |
| Pages with replaced images | — | 5 |
| CI workflows | 1 (build) | 2 (+image health cron) |
| Static pages generated | 557 | 557 |
Technical Decisions We Made
Why Patch Module._resolveFilename Instead of Refactoring to import()?
We could have changed require("@/content/data/airports-global.json") to a dynamic import() in production code. We chose not to for three reasons: (1) the require() is intentionally synchronous for server-side data loading, (2) changing it would alter the module's lazy initialization behavior, and (3) the test-only monkey-patch is isolated to a single file and does not affect production. Rule of thumb: do not change production code to satisfy test infrastructure when a test-level workaround exists.
Why Not Mock the Airport Data?
We run tests against the real 7,900-airport dataset, not a 5-airport mock. This catches real issues: encoding problems in city names, sorting edge cases with identical IATA/ICAO prefixes, and performance regressions in the fuzzy search. The dataset is 1.2 MB — Vitest loads it in under 100ms. The cost is negligible; the confidence gain is not.
Tomorrow
The test foundation is solid. Next priorities: expanding coverage to the AI concierge flow, adding integration tests for the booking API endpoints, and beginning the pre-commit hook setup with Husky so that every commit is automatically linted and formatted before it reaches the repository.
Day 3 was about depth, not breadth. We did not ship any new features. We made the existing features provably correct and visually distinctive. That is the quiet work that separates a demo from a product.
Stay Informed
Empty leg deals, new routes, and aviation insights — delivered to your inbox.