| 1 | import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 2 | |
| 3 | const mocks = vi.hoisted(() => ({ |
| 4 | os: "android", |
| 5 | version: 36, |
| 6 | openSettings: vi.fn(), |
| 7 | native: null as { |
| 8 | configure?: ReturnType<typeof vi.fn>; |
| 9 | clear?: ReturnType<typeof vi.fn>; |
| 10 | openLiveUpdateSettings?: ReturnType<typeof vi.fn>; |
| 11 | } | null, |
| 12 | config: { scheme: ["t3code-preview"], extra: { iosPersonalTeamBuild: false } }, |
| 13 | requireModule: vi.fn(), |
| 14 | })); |
| 15 | |
| 16 | vi.mock("expo", () => ({ requireOptionalNativeModule: mocks.requireModule })); |
| 17 | vi.mock("expo-constants", () => ({ default: { expoConfig: mocks.config } })); |
| 18 | vi.mock("react-native", () => ({ |
| 19 | Linking: { openSettings: mocks.openSettings }, |
| 20 | Platform: { |
| 21 | get Version() { |
| 22 | return mocks.version; |
| 23 | }, |
| 24 | get OS() { |
| 25 | return mocks.os; |
| 26 | }, |
| 27 | }, |
| 28 | })); |
| 29 | |
| 30 | beforeEach(() => { |
| 31 | vi.resetModules(); |
| 32 | mocks.os = "android"; |
| 33 | mocks.version = 36; |
| 34 | mocks.openSettings.mockReset().mockResolvedValue(undefined); |
| 35 | mocks.native = { configure: vi.fn(), clear: vi.fn() }; |
| 36 | mocks.config.extra.iosPersonalTeamBuild = false; |
| 37 | mocks.requireModule.mockReset().mockImplementation(() => mocks.native); |
| 38 | }); |
| 39 | |
| 40 | describe("Android native notification capability", () => { |
| 41 | it("opens the Live Update controls on supported Android builds", async () => { |
| 42 | mocks.native!.openLiveUpdateSettings = vi.fn(() => true); |
| 43 | const { openAndroidLiveUpdateSettings, supportsAndroidLiveUpdateSettings } = |
| 44 | await import("./androidNotifications"); |
| 45 | expect(supportsAndroidLiveUpdateSettings()).toBe(true); |
| 46 | await openAndroidLiveUpdateSettings(); |
| 47 | expect(mocks.native!.openLiveUpdateSettings).toHaveBeenCalledOnce(); |
| 48 | expect(mocks.openSettings).not.toHaveBeenCalled(); |
| 49 | mocks.version = 35; |
| 50 | expect(supportsAndroidLiveUpdateSettings()).toBe(false); |
| 51 | mocks.os = "ios"; |
| 52 | mocks.version = 36; |
| 53 | expect(supportsAndroidLiveUpdateSettings()).toBe(false); |
| 54 | }); |
| 55 | |
| 56 | it.each([undefined, vi.fn(() => false)])( |
| 57 | "falls back to app settings for older binaries or missing system activities (%j)", |
| 58 | async (openLiveUpdateSettings) => { |
| 59 | if (openLiveUpdateSettings) mocks.native!.openLiveUpdateSettings = openLiveUpdateSettings; |
| 60 | const { openAndroidLiveUpdateSettings } = await import("./androidNotifications"); |
| 61 | await openAndroidLiveUpdateSettings(); |
| 62 | expect(mocks.openSettings).toHaveBeenCalledOnce(); |
| 63 | }, |
| 64 | ); |
| 65 | |
| 66 | it("uses the installed module and the build variant's deep-link scheme", async () => { |
| 67 | const { configureAndroidAgentNotifications, clearAndroidAgentNotifications } = |
| 68 | await import("./androidNotifications"); |
| 69 | const { supportsAgentAwarenessPush } = await import("./capabilities"); |
| 70 | // An iOS-only signing restriction must not disable Android notifications. |
| 71 | mocks.config.extra.iosPersonalTeamBuild = true; |
| 72 | expect(supportsAgentAwarenessPush()).toBe(true); |
| 73 | configureAndroidAgentNotifications("device", "user", false); |
| 74 | expect(mocks.native?.configure).toHaveBeenCalledWith("device", "user", "t3code-preview", false); |
| 75 | clearAndroidAgentNotifications(); |
| 76 | expect(mocks.native?.clear).toHaveBeenCalledOnce(); |
| 77 | }); |
| 78 | |
| 79 | it.each([null, { clear: vi.fn() }, { configure: vi.fn() }])( |
| 80 | "disables push when the native binary is missing required methods (%j)", |
| 81 | async (native) => { |
| 82 | mocks.native = native; |
| 83 | const { configureAndroidAgentNotifications, clearAndroidAgentNotifications } = |
| 84 | await import("./androidNotifications"); |
| 85 | const { supportsAgentAwarenessPush } = await import("./capabilities"); |
| 86 | expect(supportsAgentAwarenessPush()).toBe(false); |
| 87 | expect(() => configureAndroidAgentNotifications("device", "user", true)).not.toThrow(); |
| 88 | expect(() => clearAndroidAgentNotifications()).not.toThrow(); |
| 89 | }, |
| 90 | ); |
| 91 | |
| 92 | it("preserves the iOS personal-team restriction without loading Android code", async () => { |
| 93 | mocks.os = "ios"; |
| 94 | const { supportsAgentAwarenessPush } = await import("./capabilities"); |
| 95 | expect(supportsAgentAwarenessPush()).toBe(true); |
| 96 | mocks.config.extra.iosPersonalTeamBuild = true; |
| 97 | expect(supportsAgentAwarenessPush()).toBe(false); |
| 98 | expect(mocks.requireModule).not.toHaveBeenCalled(); |
| 99 | }); |
| 100 | }); |