| author | |
| committer | |
| log | 779c2374876884992c7cdf059556acd255db824e |
| tree | 49f9f309df70cfb998afac8f800e1e3668dbdacb |
| parent | 350e229f7a5d5c4cdc9a29cf2d1f911ef27a9a48 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>5 files changed, 71 insertions(+), 4 deletions(-)
apps/mobile/src/features/diffs/nativeReviewDiffSurface.test.ts+14-1| ... | @@ -58,10 +58,23 @@ describe("resolveNativeReviewDiffView", () => { | ... | @@ -58,10 +58,23 @@ describe("resolveNativeReviewDiffView", () => { |
| 58 | 58 | ||
| 59 | it("returns null when the view manager cannot be required", async () => { | 59 | it("returns null when the view manager cannot be required", async () => { |
| 60 | setExpoViewConfigAvailable(); | 60 | setExpoViewConfigAvailable(); |
| 61 | const cause = new Error("boom"); | ||
| 61 | expoMocks.requireNativeView.mockImplementation(() => { | 62 | expoMocks.requireNativeView.mockImplementation(() => { |
| 62 | throw new Error("boom"); | 63 | throw cause; |
| 63 | }); | 64 | }); |
| 65 | const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); | ||
| 64 | const { resolveNativeReviewDiffView } = await import("./nativeReviewDiffSurface"); | 66 | const { resolveNativeReviewDiffView } = await import("./nativeReviewDiffSurface"); |
| 67 | |||
| 68 | expect(resolveNativeReviewDiffView()).toBeNull(); | ||
| 65 | expect(resolveNativeReviewDiffView()).toBeNull(); | 69 | expect(resolveNativeReviewDiffView()).toBeNull(); |
| 70 | expect(expoMocks.requireNativeView).toHaveBeenCalledTimes(1); | ||
| 71 | expect(consoleError).toHaveBeenCalledWith( | ||
| 72 | expect.objectContaining({ | ||
| 73 | _tag: "NativeViewResolutionError", | ||
| 74 | nativeModuleName: "T3ReviewDiffSurface", | ||
| 75 | cause, | ||
| 76 | }), | ||
| 77 | ); | ||
| 78 | expect(consoleError).toHaveBeenCalledTimes(1); | ||
| 66 | }); | 79 | }); |
| 67 | }); | 80 | }); |
apps/mobile/src/features/diffs/nativeReviewDiffSurface.ts+15-1| ... | @@ -2,6 +2,8 @@ import type { ComponentType } from "react"; | ... | @@ -2,6 +2,8 @@ import type { ComponentType } from "react"; |
| 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; | 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; |
| 3 | import { requireNativeView } from "expo"; | 3 | import { requireNativeView } from "expo"; |
| 4 | 4 | ||
| 5 | import { NativeViewResolutionError } from "../../native/nativeViewResolutionError"; | ||
| 6 | |||
| 5 | const NATIVE_REVIEW_DIFF_MODULE_NAME = "T3ReviewDiffSurface"; | 7 | const NATIVE_REVIEW_DIFF_MODULE_NAME = "T3ReviewDiffSurface"; |
| 6 | 8 | ||
| 7 | interface ExpoGlobalWithViewConfig { | 9 | interface ExpoGlobalWithViewConfig { |
| ... | @@ -128,6 +130,7 @@ export interface NativeReviewDiffViewProps extends ViewProps { | ... | @@ -128,6 +130,7 @@ export interface NativeReviewDiffViewProps extends ViewProps { |
| 128 | } | 130 | } |
| 129 | 131 | ||
| 130 | let cachedNativeReviewDiffView: ComponentType<NativeReviewDiffViewProps> | undefined; | 132 | let cachedNativeReviewDiffView: ComponentType<NativeReviewDiffViewProps> | undefined; |
| 133 | let nativeReviewDiffViewResolutionFailed = false; | ||
| 131 | 134 | ||
| 132 | function getExpoViewConfig(moduleName: string) { | 135 | function getExpoViewConfig(moduleName: string) { |
| 133 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( | 136 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( |
| ... | @@ -140,6 +143,10 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie | ... | @@ -140,6 +143,10 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie |
| 140 | return cachedNativeReviewDiffView; | 143 | return cachedNativeReviewDiffView; |
| 141 | } | 144 | } |
| 142 | 145 | ||
| 146 | if (nativeReviewDiffViewResolutionFailed) { | ||
| 147 | return null; | ||
| 148 | } | ||
| 149 | |||
| 143 | if (getExpoViewConfig(NATIVE_REVIEW_DIFF_MODULE_NAME) == null) { | 150 | if (getExpoViewConfig(NATIVE_REVIEW_DIFF_MODULE_NAME) == null) { |
| 144 | return null; | 151 | return null; |
| 145 | } | 152 | } |
| ... | @@ -148,7 +155,14 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie | ... | @@ -148,7 +155,14 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie |
| 148 | cachedNativeReviewDiffView = requireNativeView<NativeReviewDiffViewProps>( | 155 | cachedNativeReviewDiffView = requireNativeView<NativeReviewDiffViewProps>( |
| 149 | NATIVE_REVIEW_DIFF_MODULE_NAME, | 156 | NATIVE_REVIEW_DIFF_MODULE_NAME, |
| 150 | ); | 157 | ); |
| 151 | } catch { | 158 | } catch (cause) { |
| 159 | nativeReviewDiffViewResolutionFailed = true; | ||
| 160 | console.error( | ||
| 161 | new NativeViewResolutionError({ | ||
| 162 | nativeModuleName: NATIVE_REVIEW_DIFF_MODULE_NAME, | ||
| 163 | cause, | ||
| 164 | }), | ||
| 165 | ); | ||
| 152 | return null; | 166 | return null; |
| 153 | } | 167 | } |
| 154 | 168 |
apps/mobile/src/features/terminal/nativeTerminalModule.test.ts+14-1| ... | @@ -43,10 +43,23 @@ describe("resolveNativeTerminalSurfaceView", () => { | ... | @@ -43,10 +43,23 @@ describe("resolveNativeTerminalSurfaceView", () => { |
| 43 | 43 | ||
| 44 | it("returns null when the view manager cannot be required", async () => { | 44 | it("returns null when the view manager cannot be required", async () => { |
| 45 | setExpoViewConfigAvailable(); | 45 | setExpoViewConfigAvailable(); |
| 46 | const cause = new Error("boom"); | ||
| 46 | expoMocks.requireNativeView.mockImplementation(() => { | 47 | expoMocks.requireNativeView.mockImplementation(() => { |
| 47 | throw new Error("boom"); | 48 | throw cause; |
| 48 | }); | 49 | }); |
| 50 | const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); | ||
| 49 | const { resolveNativeTerminalSurfaceView } = await import("./nativeTerminalModule"); | 51 | const { resolveNativeTerminalSurfaceView } = await import("./nativeTerminalModule"); |
| 52 | |||
| 53 | expect(resolveNativeTerminalSurfaceView()).toBeNull(); | ||
| 50 | expect(resolveNativeTerminalSurfaceView()).toBeNull(); | 54 | expect(resolveNativeTerminalSurfaceView()).toBeNull(); |
| 55 | expect(expoMocks.requireNativeView).toHaveBeenCalledTimes(1); | ||
| 56 | expect(consoleError).toHaveBeenCalledWith( | ||
| 57 | expect.objectContaining({ | ||
| 58 | _tag: "NativeViewResolutionError", | ||
| 59 | nativeModuleName: "T3TerminalSurface", | ||
| 60 | cause, | ||
| 61 | }), | ||
| 62 | ); | ||
| 63 | expect(consoleError).toHaveBeenCalledTimes(1); | ||
| 51 | }); | 64 | }); |
| 52 | }); | 65 | }); |
apps/mobile/src/features/terminal/nativeTerminalModule.ts+15-1| ... | @@ -2,6 +2,8 @@ import type { ComponentType } from "react"; | ... | @@ -2,6 +2,8 @@ import type { ComponentType } from "react"; |
| 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; | 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; |
| 3 | import { requireNativeView } from "expo"; | 3 | import { requireNativeView } from "expo"; |
| 4 | 4 | ||
| 5 | import { NativeViewResolutionError } from "../../native/nativeViewResolutionError"; | ||
| 6 | |||
| 5 | const NATIVE_TERMINAL_MODULE_NAME = "T3TerminalSurface"; | 7 | const NATIVE_TERMINAL_MODULE_NAME = "T3TerminalSurface"; |
| 6 | 8 | ||
| 7 | interface ExpoGlobalWithViewConfig { | 9 | interface ExpoGlobalWithViewConfig { |
| ... | @@ -33,6 +35,7 @@ export interface NativeTerminalSurfaceProps extends ViewProps { | ... | @@ -33,6 +35,7 @@ export interface NativeTerminalSurfaceProps extends ViewProps { |
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | let cachedNativeTerminalSurfaceView: ComponentType<NativeTerminalSurfaceProps> | undefined; | 37 | let cachedNativeTerminalSurfaceView: ComponentType<NativeTerminalSurfaceProps> | undefined; |
| 38 | let nativeTerminalSurfaceViewResolutionFailed = false; | ||
| 36 | 39 | ||
| 37 | function getExpoViewConfig(moduleName: string) { | 40 | function getExpoViewConfig(moduleName: string) { |
| 38 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( | 41 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( |
| ... | @@ -45,6 +48,10 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal | ... | @@ -45,6 +48,10 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal |
| 45 | return cachedNativeTerminalSurfaceView; | 48 | return cachedNativeTerminalSurfaceView; |
| 46 | } | 49 | } |
| 47 | 50 | ||
| 51 | if (nativeTerminalSurfaceViewResolutionFailed) { | ||
| 52 | return null; | ||
| 53 | } | ||
| 54 | |||
| 48 | if (getExpoViewConfig(NATIVE_TERMINAL_MODULE_NAME) == null) { | 55 | if (getExpoViewConfig(NATIVE_TERMINAL_MODULE_NAME) == null) { |
| 49 | return null; | 56 | return null; |
| 50 | } | 57 | } |
| ... | @@ -53,7 +60,14 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal | ... | @@ -53,7 +60,14 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal |
| 53 | cachedNativeTerminalSurfaceView = requireNativeView<NativeTerminalSurfaceProps>( | 60 | cachedNativeTerminalSurfaceView = requireNativeView<NativeTerminalSurfaceProps>( |
| 54 | NATIVE_TERMINAL_MODULE_NAME, | 61 | NATIVE_TERMINAL_MODULE_NAME, |
| 55 | ); | 62 | ); |
| 56 | } catch { | 63 | } catch (cause) { |
| 64 | nativeTerminalSurfaceViewResolutionFailed = true; | ||
| 65 | console.error( | ||
| 66 | new NativeViewResolutionError({ | ||
| 67 | nativeModuleName: NATIVE_TERMINAL_MODULE_NAME, | ||
| 68 | cause, | ||
| 69 | }), | ||
| 70 | ); | ||
| 57 | return null; | 71 | return null; |
| 58 | } | 72 | } |
| 59 | 73 |
apps/mobile/src/native/nativeViewResolutionError.ts created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | import * as Schema from "effect/Schema"; | ||
| 2 | |||
| 3 | export class NativeViewResolutionError extends Schema.TaggedErrorClass<NativeViewResolutionError>()( | ||
| 4 | "NativeViewResolutionError", | ||
| 5 | { | ||
| 6 | nativeModuleName: Schema.String, | ||
| 7 | cause: Schema.Defect(), | ||
| 8 | }, | ||
| 9 | ) { | ||
| 10 | override get message(): string { | ||
| 11 | return `Failed to resolve native view ${this.nativeModuleName}.`; | ||
| 12 | } | ||
| 13 | } | ||