Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,5 @@ web-build/
# Dependency Graph Visualizations
app-dependency-graph.svg
app-dependency-graph.png
.env
.env
scratch.md
75 changes: 75 additions & 0 deletions app/mocks/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { ApiFeedResponse } from "../services/api/types"

export const mockFeedResponse: ApiFeedResponse = {
status: "ok",
feed: {
url: "https://feeds.simplecast.com/hEI_f9Dx",
title: "React Native Radio",
link: "https://www.reactnativeradio.com/",
author: "Infinite Red",
description: "Exploring React Native Together",
image: "https://placecats.com/200/200",
},
items: [
{
title: "RNR 287 - Expo Router and the Future of Navigation",
pubDate: "2024-06-12 00:00:00",
link: "https://www.reactnativeradio.com/episodes/rnr-287",
guid: "rnr-287-expo-router",
author: "Jamon Holmgren",
thumbnail: "https://placecats.com/300/300",
description:
"In this episode we dive deep into Expo Router, file-based routing, and what the future of navigation looks like in React Native.",
content:
"<p>In this episode we dive deep into Expo Router, file-based routing, and what the future of navigation looks like in React Native.</p>",
enclosure: {
link: "https://cdn.simplecast.com/audio/rnr-287.mp3",
type: "audio/mpeg",
length: 45000000,
duration: 2700,
rating: { scheme: "urn:itunes", value: "clean" },
},
categories: ["React Native", "Navigation", "Expo"],
},
{
title: "RNR 286 - React Native New Architecture Deep Dive",
pubDate: "2024-06-05 00:00:00",
link: "https://www.reactnativeradio.com/episodes/rnr-286",
guid: "rnr-286-new-architecture",
author: "Mazen Chami",
thumbnail: "https://placecats.com/300/300",
description:
"We explore the new architecture in React Native including Fabric, TurboModules, and the bridgeless mode.",
content:
"<p>We explore the new architecture in React Native including Fabric, TurboModules, and the bridgeless mode.</p>",
enclosure: {
link: "https://cdn.simplecast.com/audio/rnr-286.mp3",
type: "audio/mpeg",
length: 38000000,
duration: 2400,
rating: { scheme: "urn:itunes", value: "clean" },
},
categories: ["React Native", "Architecture", "Fabric"],
},
{
title: "RNR 285 - State Management Showdown",
pubDate: "2024-05-29 00:00:00",
link: "https://www.reactnativeradio.com/episodes/rnr-285",
guid: "rnr-285-state-management",
author: "Robin Heinze",
thumbnail: "https://placecats.com/300/300",
description:
"Comparing MobX-State-Tree, Zustand, Jotai, and Redux Toolkit for React Native apps.",
content:
"<p>Comparing MobX-State-Tree, Zustand, Jotai, and Redux Toolkit for React Native apps.</p>",
enclosure: {
link: "https://cdn.simplecast.com/audio/rnr-285.mp3",
type: "audio/mpeg",
length: 42000000,
duration: 2550,
rating: { scheme: "urn:itunes", value: "clean" },
},
categories: ["React Native", "State Management", "MobX"],
},
],
}
15 changes: 15 additions & 0 deletions app/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { http, passthrough, HttpResponse } from "msw"

import { mockFeedResponse } from "./data"

export const handlers = [
http.get("https://api.rss2json.com/v1/api.json", () => {
console.log("[MSW] Intercepted rss2json request, returning mock data")
return new HttpResponse(JSON.stringify(mockFeedResponse), {
headers: { "Content-Type": "application/json" },
})
}),

// Let Metro's internal requests pass through without warnings
http.post("*/symbolicate", () => passthrough()),
]
5 changes: 5 additions & 0 deletions app/mocks/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { setupServer } from "msw/native"

import { handlers } from "./handlers"

export const server = setupServer(...handlers)
2 changes: 2 additions & 0 deletions app/services/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class Api {
headers: {
Accept: "application/json",
},
// Use the fetch adapter so MSW can intercept requests reliably.
adapter: "fetch",
})
}

Expand Down
15 changes: 11 additions & 4 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { registerRootComponent } from "expo"

import { App } from "@/app"

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App)
async function enableMocking() {
if (!__DEV__) return

require("./msw.polyfills")
const { setupServer } = require("msw/native")
const { handlers } = require("./app/mocks/handlers")
const server = setupServer(...handlers)
server.listen()
}

enableMocking().then(() => registerRootComponent(App))
29 changes: 29 additions & 0 deletions msw.polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import "fast-text-encoding"
import "react-native-url-polyfill/auto"
import "web-streams-polyfill/dist/polyfill"

if (typeof globalThis.MessageEvent === "undefined") {
globalThis.MessageEvent = class MessageEvent {
constructor(type, init) {
this.type = type
this.data = init?.data ?? null
this.origin = init?.origin ?? ""
this.lastEventId = init?.lastEventId ?? ""
this.source = init?.source ?? null
this.ports = init?.ports ?? []
}
}
}

if (typeof globalThis.BroadcastChannel === "undefined") {
globalThis.BroadcastChannel = class BroadcastChannel {
constructor(_name) {}
postMessage() {}
close() {}
addEventListener() {}
removeEventListener() {}
dispatchEvent() {
return true
}
}
}
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"expo-localization": "~57.0.0",
"expo-splash-screen": "~57.0.2",
"expo-system-ui": "~57.0.0",
"fast-text-encoding": "^1.0.6",
"i18next": "^23.14.0",
"intl-pluralrules": "^2.0.1",
"react": "19.2.3",
Expand All @@ -61,9 +62,11 @@
"react-native-reanimated": "4.5.0",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.25.2",
"react-native-url-polyfill": "^3.0.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.10.0",
"setimmediate": "^1.0.5"
"setimmediate": "^1.0.5",
"web-streams-polyfill": "^4.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand All @@ -82,6 +85,7 @@
"eslint-plugin-reactotron": "^0.1.2",
"jest": "~29.7.0",
"jest-expo": "~57.0.1",
"msw": "^2.14.6",
"prettier": "^3.3.3",
"react-test-renderer": "19.2.0",
"reactotron-core-client": "^2.9.4",
Expand All @@ -94,5 +98,10 @@
},
"engines": {
"node": ">=20.0.0"
},
"pnpm": {
"patchedDependencies": {
"@mswjs/interceptors": "patches/@mswjs__interceptors.patch"
}
}
}
22 changes: 22 additions & 0 deletions patches/@mswjs__interceptors.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
diff --git a/lib/browser/fetch-5IMPqr9e.mjs b/lib/browser/fetch-5IMPqr9e.mjs
index ff7e15e2e4b6d91614f01fd4e5439f67ed72ea70..be355a7e1f1c6b14f2e7763bc0d580b7399f2231 100644
--- a/lib/browser/fetch-5IMPqr9e.mjs
+++ b/lib/browser/fetch-5IMPqr9e.mjs
@@ -110,7 +110,7 @@ function createDecompressionStream(contentEncoding) {
}, []));
}
function decompressResponse(response) {
- if (response.body === null) return null;
+ if (!response.body) return null;
const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || "");
if (!decompressionStream) return null;
response.body.pipeTo(decompressionStream.writable);
@@ -172,7 +172,7 @@ var FetchInterceptor = class FetchInterceptor extends Interceptor {
return;
}
this.logger.info("received mocked response!", { rawResponse });
- const response = new FetchResponse(decompressResponse(rawResponse) || rawResponse.body, {
+ const response = new FetchResponse(decompressResponse(rawResponse) || rawResponse._bodyInit || rawResponse.body, {
url: request.url,
status: rawResponse.status,
statusText: rawResponse.statusText,
Loading