| 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 | 58 | |
| 59 | 59 | it("returns null when the view manager cannot be required", async () => { |
| 60 | 60 | setExpoViewConfigAvailable(); |
| 61 | const cause = new Error("boom"); | |
| 61 | 62 | expoMocks.requireNativeView.mockImplementation(() => { |
| 62 | throw new Error("boom"); | |
| 63 | throw cause; | |
| 63 | 64 | }); |
| 65 | const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); | |
| 64 | 66 | const { resolveNativeReviewDiffView } = await import("./nativeReviewDiffSurface"); |
| 67 | ||
| 68 | expect(resolveNativeReviewDiffView()).toBeNull(); | |
| 65 | 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 | 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; |
| 3 | 3 | import { requireNativeView } from "expo"; |
| 4 | 4 | |
| 5 | import { NativeViewResolutionError } from "../../native/nativeViewResolutionError"; | |
| 6 | ||
| 5 | 7 | const NATIVE_REVIEW_DIFF_MODULE_NAME = "T3ReviewDiffSurface"; |
| 6 | 8 | |
| 7 | 9 | interface ExpoGlobalWithViewConfig { |
| ... | ... | @@ -128,6 +130,7 @@ export interface NativeReviewDiffViewProps extends ViewProps { |
| 128 | 130 | } |
| 129 | 131 | |
| 130 | 132 | let cachedNativeReviewDiffView: ComponentType<NativeReviewDiffViewProps> | undefined; |
| 133 | let nativeReviewDiffViewResolutionFailed = false; | |
| 131 | 134 | |
| 132 | 135 | function getExpoViewConfig(moduleName: string) { |
| 133 | 136 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( |
| ... | ... | @@ -140,6 +143,10 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie |
| 140 | 143 | return cachedNativeReviewDiffView; |
| 141 | 144 | } |
| 142 | 145 | |
| 146 | if (nativeReviewDiffViewResolutionFailed) { | |
| 147 | return null; | |
| 148 | } | |
| 149 | ||
| 143 | 150 | if (getExpoViewConfig(NATIVE_REVIEW_DIFF_MODULE_NAME) == null) { |
| 144 | 151 | return null; |
| 145 | 152 | } |
| ... | ... | @@ -148,7 +155,14 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie |
| 148 | 155 | cachedNativeReviewDiffView = requireNativeView<NativeReviewDiffViewProps>( |
| 149 | 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 | 166 | return null; |
| 153 | 167 | } |
| 154 | 168 |
apps/mobile/src/features/terminal/nativeTerminalModule.test.ts+14-1| ... | ... | @@ -43,10 +43,23 @@ describe("resolveNativeTerminalSurfaceView", () => { |
| 43 | 43 | |
| 44 | 44 | it("returns null when the view manager cannot be required", async () => { |
| 45 | 45 | setExpoViewConfigAvailable(); |
| 46 | const cause = new Error("boom"); | |
| 46 | 47 | expoMocks.requireNativeView.mockImplementation(() => { |
| 47 | throw new Error("boom"); | |
| 48 | throw cause; | |
| 48 | 49 | }); |
| 50 | const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined); | |
| 49 | 51 | const { resolveNativeTerminalSurfaceView } = await import("./nativeTerminalModule"); |
| 52 | ||
| 53 | expect(resolveNativeTerminalSurfaceView()).toBeNull(); | |
| 50 | 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 | 2 | import type { NativeSyntheticEvent, ViewProps } from "react-native"; |
| 3 | 3 | import { requireNativeView } from "expo"; |
| 4 | 4 | |
| 5 | import { NativeViewResolutionError } from "../../native/nativeViewResolutionError"; | |
| 6 | ||
| 5 | 7 | const NATIVE_TERMINAL_MODULE_NAME = "T3TerminalSurface"; |
| 6 | 8 | |
| 7 | 9 | interface ExpoGlobalWithViewConfig { |
| ... | ... | @@ -33,6 +35,7 @@ export interface NativeTerminalSurfaceProps extends ViewProps { |
| 33 | 35 | } |
| 34 | 36 | |
| 35 | 37 | let cachedNativeTerminalSurfaceView: ComponentType<NativeTerminalSurfaceProps> | undefined; |
| 38 | let nativeTerminalSurfaceViewResolutionFailed = false; | |
| 36 | 39 | |
| 37 | 40 | function getExpoViewConfig(moduleName: string) { |
| 38 | 41 | return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.( |
| ... | ... | @@ -45,6 +48,10 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal |
| 45 | 48 | return cachedNativeTerminalSurfaceView; |
| 46 | 49 | } |
| 47 | 50 | |
| 51 | if (nativeTerminalSurfaceViewResolutionFailed) { | |
| 52 | return null; | |
| 53 | } | |
| 54 | ||
| 48 | 55 | if (getExpoViewConfig(NATIVE_TERMINAL_MODULE_NAME) == null) { |
| 49 | 56 | return null; |
| 50 | 57 | } |
| ... | ... | @@ -53,7 +60,14 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal |
| 53 | 60 | cachedNativeTerminalSurfaceView = requireNativeView<NativeTerminalSurfaceProps>( |
| 54 | 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 | 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 | } |