evOo-local/apps/mobile/plugins/with-scene-lifecycle.test.js
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

115 lines
4.0 KiB
JavaScript

import { describe, expect, it } from "vitest";
import { applySceneLifecycleToAppDelegate, applySceneManifest } from "./with-scene-lifecycle.js";
// Matches expo@57.0.23's prebuilt AppDelegate template.
const TEMPLATE = `internal import Expo
import React
import ReactAppDependencyProvider
@main
class AppDelegate: ExpoAppDelegate {
var window: UIWindow?
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
public override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
#if os(iOS) || os(tvOS)
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "main",
in: window,
launchOptions: launchOptions)
#endif
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Linking API
public override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
}
}
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
override func bundleURL() -> URL? {
nil
}
}
`;
describe("applySceneLifecycleToAppDelegate", () => {
it("hands window creation to Expo's scene delegate", () => {
const result = applySceneLifecycleToAppDelegate(TEMPLATE);
// The app delegate only provides the factory; the scene manifest routes
// to Expo's EXExpoAppSceneDelegate, so no window code stays behind.
expect(result).toContain(
"class AppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider {",
);
expect(result).toContain("let factory = ExpoReactNativeFactory(delegate: delegate)");
expect(result).not.toContain("UIWindow(frame: UIScreen.main.bounds)");
expect(result).not.toContain("startReactNative(");
expect(result).not.toContain("class SceneDelegate");
// Pre-existing template behavior is untouched.
expect(result).toContain("RCTLinkingManager.application(app, open: url, options: options)");
expect(result).toContain("class ReactNativeDelegate: ExpoReactNativeFactoryDelegate");
});
it("is idempotent", () => {
const once = applySceneLifecycleToAppDelegate(TEMPLATE);
expect(applySceneLifecycleToAppDelegate(once)).toBe(once);
});
it("fails loudly when the Expo template drifts", () => {
expect(() => applySceneLifecycleToAppDelegate("class AppDelegate: ExpoAppDelegate {}")).toThrow(
/template changed/,
);
});
it("fails loudly when only one anchor survives template drift", () => {
const noLaunchingBlock = TEMPLATE.replace(/#if os\(iOS\)[\s\S]*?#endif\n\n/, "");
expect(() => applySceneLifecycleToAppDelegate(noLaunchingBlock)).toThrow(/template changed/);
});
it("refuses a partially migrated AppDelegate instead of skipping it", () => {
const partial = TEMPLATE.replace(
"class AppDelegate: ExpoAppDelegate {",
"class AppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider {",
);
expect(() => applySceneLifecycleToAppDelegate(partial)).toThrow(/refusing to guess/);
});
});
describe("applySceneManifest", () => {
it("declares a single-scene manifest pointing at Expo's scene delegate", () => {
const infoPlist = applySceneManifest({});
expect(infoPlist.UIApplicationSceneManifest).toEqual({
UIApplicationSupportsMultipleScenes: false,
UISceneConfigurations: {
UIWindowSceneSessionRoleApplication: [
{
UISceneConfigurationName: "main",
UISceneDelegateClassName: "EXExpoAppSceneDelegate",
},
],
},
});
});
});