Browse Source
feat(geo): integrate Flutter bridge get_location action for IP-based country and city detection
master
feat(geo): integrate Flutter bridge get_location action for IP-based country and city detection
master
3 changed files with 481 additions and 134 deletions
@ -0,0 +1,168 @@ |
|||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
||||
|
import { |
||||
|
getUserGeoRegion, |
||||
|
getStoredUserGeoRegion, |
||||
|
setStoredUserGeoRegion, |
||||
|
resetUserGeoRegionForTesting, |
||||
|
} from "./geo-region"; |
||||
|
import { http } from "./http"; |
||||
|
|
||||
|
vi.mock("./http", () => ({ |
||||
|
http: { |
||||
|
get: vi.fn(), |
||||
|
}, |
||||
|
})); |
||||
|
|
||||
|
describe("geo-region", () => { |
||||
|
const originalHabibApp = window.HabibApp; |
||||
|
const originalAddFlutterResponseListener = window.addFlutterResponseListener; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
vi.restoreAllMocks(); |
||||
|
localStorage.clear(); |
||||
|
resetUserGeoRegionForTesting(); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
window.HabibApp = originalHabibApp; |
||||
|
window.addFlutterResponseListener = originalAddFlutterResponseListener; |
||||
|
resetUserGeoRegionForTesting(); |
||||
|
}); |
||||
|
|
||||
|
describe("getStoredUserGeoRegion / setStoredUserGeoRegion", () => { |
||||
|
it("returns null when nothing is stored", () => { |
||||
|
expect(getStoredUserGeoRegion()).toBeNull(); |
||||
|
}); |
||||
|
|
||||
|
it("persists region and retrieves from memory and localStorage", () => { |
||||
|
const sample = { |
||||
|
ip: "1.2.3.4", |
||||
|
city: "Tehran", |
||||
|
country: "Iran", |
||||
|
countryCode: "IR", |
||||
|
phoneCode: "+98", |
||||
|
}; |
||||
|
setStoredUserGeoRegion(sample); |
||||
|
expect(getStoredUserGeoRegion()).toEqual(sample); |
||||
|
expect(localStorage.getItem("user_geo_region")).toBe( |
||||
|
JSON.stringify(sample), |
||||
|
); |
||||
|
expect(localStorage.getItem("geoIPPhoneCode")).toBe("+98"); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("getUserGeoRegion", () => { |
||||
|
it("returns stored region immediately if not forced", async () => { |
||||
|
const cached = { |
||||
|
city: "London", |
||||
|
country: "United Kingdom", |
||||
|
countryCode: "GB", |
||||
|
phoneCode: "+44", |
||||
|
}; |
||||
|
setStoredUserGeoRegion(cached); |
||||
|
|
||||
|
const result = await getUserGeoRegion(false); |
||||
|
expect(result).toEqual(cached); |
||||
|
expect(http.get).not.toHaveBeenCalled(); |
||||
|
}); |
||||
|
|
||||
|
it("fetches via Flutter bridge when in Flutter WebView", async () => { |
||||
|
let listenerCallback: ((event: any) => void) | undefined; |
||||
|
window.addFlutterResponseListener = vi.fn().mockImplementation((cb) => { |
||||
|
listenerCallback = cb; |
||||
|
return () => {}; |
||||
|
}); |
||||
|
|
||||
|
const mockPostMessage = vi.fn().mockImplementation(() => { |
||||
|
setTimeout(() => { |
||||
|
listenerCallback?.({ |
||||
|
action: "get_location", |
||||
|
success: true, |
||||
|
data: { |
||||
|
ip: "5.6.7.8", |
||||
|
city: "Isfahan", |
||||
|
country: "Iran", |
||||
|
country_code: "IR", |
||||
|
}, |
||||
|
}); |
||||
|
}, 10); |
||||
|
}); |
||||
|
|
||||
|
window.HabibApp = { postMessage: mockPostMessage }; |
||||
|
|
||||
|
const result = await getUserGeoRegion(true); |
||||
|
|
||||
|
expect(mockPostMessage).toHaveBeenCalledWith( |
||||
|
JSON.stringify({ action: "get_location" }), |
||||
|
); |
||||
|
expect(result).toEqual({ |
||||
|
ip: "5.6.7.8", |
||||
|
city: "Isfahan", |
||||
|
country: "Iran", |
||||
|
countryCode: "IR", |
||||
|
phoneCode: "+98", |
||||
|
}); |
||||
|
expect(getStoredUserGeoRegion()?.countryCode).toBe("IR"); |
||||
|
}); |
||||
|
|
||||
|
it("fetches from HTTP backend when in standard browser", async () => { |
||||
|
delete (window as any).HabibApp; |
||||
|
|
||||
|
(http.get as any).mockResolvedValueOnce({ |
||||
|
data: { |
||||
|
ip: "10.0.0.1", |
||||
|
city: "Shiraz", |
||||
|
country: "Iran", |
||||
|
country_code: "IR", |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const result = await getUserGeoRegion(true); |
||||
|
|
||||
|
expect(http.get).toHaveBeenCalledWith("/account/auth/user/region/", { |
||||
|
timeout: 4000, |
||||
|
}); |
||||
|
expect(result).toEqual({ |
||||
|
ip: "10.0.0.1", |
||||
|
city: "Shiraz", |
||||
|
country: "Iran", |
||||
|
countryCode: "IR", |
||||
|
phoneCode: "+98", |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it("falls back to HTTP backend if Flutter bridge returns failure", async () => { |
||||
|
let listenerCallback: ((event: any) => void) | undefined; |
||||
|
window.addFlutterResponseListener = vi.fn().mockImplementation((cb) => { |
||||
|
listenerCallback = cb; |
||||
|
return () => {}; |
||||
|
}); |
||||
|
|
||||
|
const mockPostMessage = vi.fn().mockImplementation(() => { |
||||
|
setTimeout(() => { |
||||
|
listenerCallback?.({ |
||||
|
action: "get_location", |
||||
|
success: false, |
||||
|
}); |
||||
|
}, 10); |
||||
|
}); |
||||
|
|
||||
|
window.HabibApp = { postMessage: mockPostMessage }; |
||||
|
|
||||
|
(http.get as any).mockResolvedValueOnce({ |
||||
|
data: { |
||||
|
ip: "192.168.1.1", |
||||
|
city: "Mashhad", |
||||
|
country: "Iran", |
||||
|
country_code: "IR", |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const result = await getUserGeoRegion(true); |
||||
|
expect(result.city).toBe("Mashhad"); |
||||
|
expect(result.countryCode).toBe("IR"); |
||||
|
expect(result.country).toBe("Iran"); |
||||
|
expect(result.phoneCode).toBe("+98"); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue