-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(core): Country code behavior #1224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Ehesp
merged 6 commits into
@invertase/v7-development
from
@invertase/county-code-behavior
Oct 8, 2025
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
dc12f7b
feat(core): Country code behavior
Ehesp c73764a
Merge branch '@invertase/v7-development' of https://github.com/fireba…
Ehesp 26d872d
feat(core,react): counteyCodes behavior and CountrySelector rework
Ehesp c6c08df
chore: Formatting
Ehesp 27429e9
refactor: Rework number formatting logic
Ehesp 617cf51
chore: TS assertion
Ehesp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
import { describe, it, expect, vi } from "vitest"; | ||
import { countryCodesHandler, CountryCodesOptions } from "./country-codes"; | ||
import { countryData } from "../country-data"; | ||
|
||
describe("countryCodesHandler", () => { | ||
describe("default behavior", () => { | ||
it("should return all countries when no options provided", () => { | ||
const result = countryCodesHandler(); | ||
|
||
expect(result.allowedCountries).toEqual(countryData); | ||
expect(result.defaultCountry).toEqual(countryData.find((country) => country.code === "US")); | ||
}); | ||
|
||
it("should return all countries when empty options provided", () => { | ||
const result = countryCodesHandler({}); | ||
|
||
expect(result.allowedCountries).toEqual(countryData); | ||
expect(result.defaultCountry).toEqual(countryData.find((country) => country.code === "US")); | ||
}); | ||
}); | ||
|
||
describe("allowedCountries filtering", () => { | ||
it("should filter countries based on allowedCountries", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US", "GB", "CA"], | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.allowedCountries).toHaveLength(3); | ||
// Order is preserved from original countryData array, not from allowedCountries | ||
expect(result.allowedCountries.map((c) => c.code)).toEqual(["CA", "GB", "US"]); | ||
}); | ||
|
||
it("should handle single allowed country", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US"], | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.allowedCountries).toHaveLength(1); | ||
expect(result.allowedCountries[0]!.code).toBe("US"); | ||
}); | ||
|
||
it("should handle empty allowedCountries array", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: [], | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.allowedCountries).toEqual(countryData); | ||
}); | ||
}); | ||
|
||
describe("defaultCountry setting", () => { | ||
it("should set default country when provided", () => { | ||
const options: CountryCodesOptions = { | ||
defaultCountry: "GB", | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("GB"); | ||
expect(result.defaultCountry.name).toBe("United Kingdom"); | ||
}); | ||
|
||
it("should default to US when no defaultCountry provided", () => { | ||
const result = countryCodesHandler(); | ||
|
||
expect(result.defaultCountry.code).toBe("US"); | ||
}); | ||
|
||
it("should default to US when defaultCountry is undefined", () => { | ||
const options: CountryCodesOptions = { | ||
defaultCountry: undefined, | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("US"); | ||
}); | ||
}); | ||
|
||
describe("defaultCountry validation with allowedCountries", () => { | ||
it("should keep defaultCountry when it's in allowedCountries", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US", "GB", "CA"], | ||
defaultCountry: "GB", | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("GB"); | ||
expect(result.allowedCountries.map((c) => c.code)).toEqual(["CA", "GB", "US"]); | ||
}); | ||
|
||
it("should override defaultCountry when it's not in allowedCountries", () => { | ||
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); | ||
|
||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US", "GB", "CA"], | ||
defaultCountry: "FR", // France is not in allowed countries | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("CA"); // Should default to first allowed country (CA comes first in original array) | ||
expect(result.allowedCountries.map((c) => c.code)).toEqual(["CA", "GB", "US"]); | ||
expect(consoleSpy).toHaveBeenCalledWith( | ||
'The "defaultCountry" option is not in the "allowedCountries" list, the default country has been set to CA' | ||
); | ||
|
||
consoleSpy.mockRestore(); | ||
}); | ||
|
||
it("should override defaultCountry to first allowed country when not in list", () => { | ||
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); | ||
|
||
const options: CountryCodesOptions = { | ||
allowedCountries: ["GB", "CA", "AU"], // US is not in this list | ||
defaultCountry: "US", | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("AU"); // Should default to first allowed country (AU comes first in original array) | ||
expect(consoleSpy).toHaveBeenCalledWith( | ||
'The "defaultCountry" option is not in the "allowedCountries" list, the default country has been set to AU' | ||
); | ||
|
||
consoleSpy.mockRestore(); | ||
}); | ||
|
||
it("should not warn when defaultCountry is in allowedCountries", () => { | ||
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); | ||
|
||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US", "GB", "CA"], | ||
defaultCountry: "CA", | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.defaultCountry.code).toBe("CA"); | ||
expect(consoleSpy).not.toHaveBeenCalled(); | ||
|
||
consoleSpy.mockRestore(); | ||
}); | ||
}); | ||
|
||
describe("edge cases", () => { | ||
it("should handle invalid country codes gracefully", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: ["US", "INVALID", "GB"] as any, | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
// Should only include valid countries | ||
expect(result.allowedCountries).toHaveLength(2); | ||
expect(result.allowedCountries.map((c) => c.code)).toEqual(["GB", "US"]); | ||
}); | ||
|
||
it("should handle case sensitivity", () => { | ||
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); | ||
|
||
const options: CountryCodesOptions = { | ||
allowedCountries: ["us", "gb"] as any, // lowercase | ||
defaultCountry: "US", // This will trigger the validation logic | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
// Should fall back to all countries when no matches found | ||
expect(result.allowedCountries).toEqual(countryData); | ||
expect(consoleSpy).toHaveBeenCalledWith( | ||
'No countries matched the "allowedCountries" list, falling back to all countries' | ||
); | ||
|
||
consoleSpy.mockRestore(); | ||
}); | ||
|
||
it("should handle special country codes like Kosovo", () => { | ||
const options: CountryCodesOptions = { | ||
allowedCountries: ["XK", "US", "GB"], | ||
}; | ||
|
||
const result = countryCodesHandler(options); | ||
|
||
expect(result.allowedCountries.length).toBeGreaterThan(2); // Kosovo has multiple entries | ||
expect(result.allowedCountries.some((c) => c.code === "XK")).toBe(true); | ||
expect(result.allowedCountries.some((c) => c.code === "US")).toBe(true); | ||
expect(result.allowedCountries.some((c) => c.code === "GB")).toBe(true); | ||
}); | ||
}); | ||
|
||
describe("return type validation", () => { | ||
it("should return objects with correct structure", () => { | ||
const result = countryCodesHandler(); | ||
|
||
expect(result).toHaveProperty("allowedCountries"); | ||
expect(result).toHaveProperty("defaultCountry"); | ||
expect(Array.isArray(result.allowedCountries)).toBe(true); | ||
expect(typeof result.defaultCountry).toBe("object"); | ||
|
||
// Check structure of country objects | ||
result.allowedCountries.forEach((country) => { | ||
expect(country).toHaveProperty("name"); | ||
expect(country).toHaveProperty("dialCode"); | ||
expect(country).toHaveProperty("code"); | ||
expect(country).toHaveProperty("emoji"); | ||
}); | ||
|
||
expect(result.defaultCountry).toHaveProperty("name"); | ||
expect(result.defaultCountry).toHaveProperty("dialCode"); | ||
expect(result.defaultCountry).toHaveProperty("code"); | ||
expect(result.defaultCountry).toHaveProperty("emoji"); | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { CountryCode, countryData } from "../country-data"; | ||
|
||
export type CountryCodesOptions = { | ||
// The allowed countries are the countries that will be shown in the country selector | ||
// or `getCountries` is called. | ||
allowedCountries?: CountryCode[]; | ||
// The default country is the country that will be selected by default when | ||
// the country selector is rendered, or `getDefaultCountry` is called. | ||
defaultCountry?: CountryCode; | ||
}; | ||
|
||
export const countryCodesHandler = (options?: CountryCodesOptions) => { | ||
// Determine allowed countries | ||
let allowedCountries = options?.allowedCountries?.length | ||
? countryData.filter((country) => options.allowedCountries!.includes(country.code)) | ||
: countryData; | ||
|
||
// If no countries match, fall back to all countries | ||
if (options?.allowedCountries?.length && allowedCountries.length === 0) { | ||
console.warn(`No countries matched the "allowedCountries" list, falling back to all countries`); | ||
allowedCountries = countryData; | ||
} | ||
|
||
// Determine default country | ||
let defaultCountry = options?.defaultCountry | ||
? countryData.find((country) => country.code === options.defaultCountry)! | ||
: countryData.find((country) => country.code === "US")!; | ||
|
||
// If default country is not in allowed countries, use first allowed country | ||
if (!allowedCountries.some((country) => country.code === defaultCountry.code)) { | ||
defaultCountry = allowedCountries[0]!; | ||
console.warn( | ||
`The "defaultCountry" option is not in the "allowedCountries" list, the default country has been set to ${defaultCountry.code}` | ||
); | ||
} | ||
|
||
return { | ||
allowedCountries, | ||
defaultCountry, | ||
}; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.