import { customIcon, visibleProviders, type ProviderKey, type ProviderVisibility, } from "../social-connections"; // The component module imports view-layer deps (`@expo/vector-icons`, // `react-native-reanimated` via Tooltip/EntropyMark) that the unit-suite babel // transform does not process. The shared setup stubs `react-native` / // `nativewind`; stub the rest here so the pure helper under test loads without // a render runtime. Same pattern as the pagination test. jest.mock("@expo/vector-icons", () => ({ Ionicons: "Ionicons" })); // `react-native-svg` is not in the transform whitelist, so its TypeScript // source reaches jest untranspiled; EntropyMark pulls it in for the brand mark. jest.mock("react-native-svg", () => ({ __esModule: true, default: "Svg", Path: "Path", })); jest.mock("react-native-reanimated", () => ({ __esModule: true, default: { View: "Animated.View" }, useSharedValue: () => ({ value: 0 }), useAnimatedStyle: () => ({}), withTiming: (v: number) => v, })); const ALL: ProviderVisibility = { entropy: true, apple: true, google: true, microsoft: true, }; const keysOf = (shown: ProviderVisibility): ProviderKey[] => visibleProviders(shown).map(({ key }) => key); describe("visibleProviders", () => { it("renders every provider when all flags are set", () => { // The component's defaults are all `true`, so this is what an existing // caller that passes no `show*` prop still gets. expect(keysOf(ALL)).toEqual(["entropy", "apple", "google", "microsoft"]); }); it.each(["entropy", "apple", "google", "microsoft"])( "suppresses only %s when its flag is false", (key) => { const got = keysOf({ ...ALL, [key]: false }); expect(got).not.toContain(key); expect(got).toHaveLength(3); } ); it("renders one provider when only its flag is set", () => { // The case that motivated per-provider flags: a tenant with a Google // connector and nothing else drew Apple and Microsoft as dead buttons. expect( keysOf({ entropy: false, apple: false, google: true, microsoft: false }) ).toEqual(["google"]); }); it("returns nothing when every flag is false", () => { // A caller that hides all four gets an empty row, not a stray gap-spaced // container with one invisible child — the consumer is expected to skip // rendering the component at all, and this is what it can check against. expect( keysOf({ entropy: false, apple: false, google: false, microsoft: false, }) ).toEqual([]); }); it("keeps PROVIDERS order regardless of the flag object's key order", () => { // Order comes from the module's provider list, not from the caller, so the // row cannot reshuffle between consumers that build the map differently. const reversed: ProviderVisibility = { microsoft: true, google: true, apple: true, entropy: true, }; expect(keysOf(reversed)).toEqual(keysOf(ALL)); }); it("carries the brand mark opt-out only for entropy", () => { // `icon: null` is what tells the component to draw EntropyMark instead of // an Ionicons glyph; the other three must keep a glyph name. const byKey = new Map(visibleProviders(ALL).map((p) => [p.key, p])); expect(byKey.get("entropy")?.icon).toBeNull(); expect(byKey.get("apple")?.icon).toBe("logo-apple"); expect(byKey.get("google")?.icon).toBe("logo-google"); expect(byKey.get("microsoft")?.icon).toBe("logo-microsoft"); }); }); describe("customIcon", () => { it("uses the operator's URL when there is one", () => { expect(customIcon("https://cdn.example/okta.svg", false)).toEqual({ kind: "image", uri: "https://cdn.example/okta.svg", }); }); it("trims, because a pasted URL usually arrives with whitespace", () => { expect(customIcon(" https://cdn.example/k.png\n", false)).toEqual({ kind: "image", uri: "https://cdn.example/k.png", }); }); it.each([undefined, null, "", " "])( "falls back when the URL is %p", (value) => { // A blank string is the case that matters: clearing a form field leaves // "" far more often than null, and `` renders // an empty square rather than failing loudly. expect(customIcon(value, false)).toEqual({ kind: "fallback" }); } ); it("falls back once the image has failed, URL or not", () => { // An unreachable or malformed URL must still leave a pressable, labelled // button — a connector the operator can see and use. expect(customIcon("https://cdn.example/gone.png", true)).toEqual({ kind: "fallback", }); }); });