evOo-local/apps/mobile/lib/ui-direction.test.ts
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

104 lines
3.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("expo", () => ({
reloadAppAsync: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("react-native", () => ({
I18nManager: {
allowRTL: vi.fn(),
forceRTL: vi.fn(),
isRTL: false,
},
Platform: { OS: "ios" },
}));
describe("mobile ui direction", () => {
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
vi.unstubAllGlobals();
vi.clearAllMocks();
});
it("resolves a locale tag from Intl", async () => {
const resolvedOptions = vi.fn().mockReturnValue({ locale: "he-IL" });
vi.stubGlobal("Intl", {
...Intl,
DateTimeFormat: vi.fn().mockImplementation(() => ({ resolvedOptions })),
});
const { resolveMobileUiLocale } = await import("./ui-direction");
expect(resolveMobileUiLocale()).toBe("he-IL");
});
it("forces rtl and reloads when the runtime is still ltr", async () => {
const { I18nManager } = await import("react-native");
const { reloadAppAsync } = await import("expo");
(I18nManager as { isRTL: boolean }).isRTL = false;
const { applyMobileUiDirection } = await import("./ui-direction");
expect(applyMobileUiDirection("he-IL")).toBe(true);
expect(I18nManager.allowRTL).toHaveBeenCalledWith(true);
expect(I18nManager.forceRTL).toHaveBeenCalledWith(true);
await Promise.resolve();
expect(reloadAppAsync).toHaveBeenCalledWith("ui-direction");
});
it("forces ltr and reloads when the runtime is still rtl", async () => {
const { I18nManager } = await import("react-native");
const { reloadAppAsync } = await import("expo");
(I18nManager as { isRTL: boolean }).isRTL = true;
const { applyMobileUiDirection } = await import("./ui-direction");
expect(applyMobileUiDirection("en-US")).toBe(false);
expect(I18nManager.allowRTL).toHaveBeenCalledWith(true);
expect(I18nManager.forceRTL).toHaveBeenCalledWith(false);
await Promise.resolve();
expect(reloadAppAsync).toHaveBeenCalledWith("ui-direction");
});
it("skips forceRTL and reload when layout already matches the locale", async () => {
const { I18nManager } = await import("react-native");
const { reloadAppAsync } = await import("expo");
(I18nManager as { isRTL: boolean }).isRTL = true;
const { applyMobileUiDirection } = await import("./ui-direction");
expect(applyMobileUiDirection("he-IL")).toBe(true);
expect(I18nManager.allowRTL).toHaveBeenCalledWith(true);
expect(I18nManager.forceRTL).not.toHaveBeenCalled();
await Promise.resolve();
expect(reloadAppAsync).not.toHaveBeenCalled();
});
it("defaults to LTR English so a no-arg call cannot fight zh/en/ru UI locale bootstrap", async () => {
const resolvedOptions = vi.fn().mockReturnValue({ locale: "ar-SA" });
vi.stubGlobal("Intl", {
...Intl,
DateTimeFormat: vi.fn().mockImplementation(() => ({ resolvedOptions })),
});
const { I18nManager } = await import("react-native");
const { reloadAppAsync } = await import("expo");
(I18nManager as { isRTL: boolean }).isRTL = true;
const { applyMobileUiDirection } = await import("./ui-direction");
expect(applyMobileUiDirection()).toBe(false);
expect(I18nManager.forceRTL).toHaveBeenCalledWith(false);
await Promise.resolve();
expect(reloadAppAsync).toHaveBeenCalledWith("ui-direction");
});
it("keeps root layout from applying device-locale direction before i18n bootstrap", async () => {
const { readFileSync } = await import("node:fs");
const { dirname, join } = await import("node:path");
const { fileURLToPath } = await import("node:url");
const layout = readFileSync(
join(dirname(fileURLToPath(import.meta.url)), "../app/_layout.tsx"),
"utf8",
);
expect(layout).not.toMatch(/applyMobileUiDirection\s*\(/);
});
});