Skip to content

Commit ac38e8e

Browse files
test: add validation tests for platform-specific locale formats (#1585)
* docs: add changeset requirements to CLAUDE.md and create changeset * test: add validation tests for platform-specific locale formats Add comprehensive tests for localeCodeSchema validation to ensure support for different platform-specific locale code formats: - Standard BCP 47 format (en-US, zh-Hans-CN) - Underscore format used by Java/Android (en_US, zh_Hans_CN) - Android resource folder format with r-prefix (en-rUS, fr-rCA) These tests verify that the normalizeLocale() function correctly handles all common locale code variations before validation. * fix: prettier formatting in provider.spec.tsx
1 parent 3d5e776 commit ac38e8e

File tree

4 files changed

+72
-3
lines changed

4 files changed

+72
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lingo.dev/_spec": minor
3+
---
4+
5+
Allow any valid ISO locale code in validation instead of hardcoded list. Validation now accepts any locale conforming to ISO 639-1, ISO 15924, ISO 3166-1, and UN M.49 standards.

.changeset/honest-melons-judge.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Add comprehensive tests for platform-specific locale code formats (underscore and Android r-prefix)

packages/react/src/client/provider.spec.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ describe("client/provider", () => {
5353

5454
describe("LingoProviderWrapper", () => {
5555
it("renders nothing while loading by default, then shows children", async () => {
56-
const deferred = createDeferred<{ locale: string; files: Record<string, unknown> }>();
56+
const deferred = createDeferred<{
57+
locale: string;
58+
files: Record<string, unknown>;
59+
}>();
5760
const loadDictionary = vi.fn(() => deferred.promise);
5861

5962
const Child = () => <div data-testid="child">ok</div>;
@@ -97,7 +100,9 @@ describe("client/provider", () => {
97100
it("propagates load errors to the nearest error boundary", async () => {
98101
const loadDictionary = vi.fn().mockRejectedValue(new Error("boom"));
99102
const onError = vi.fn();
100-
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
103+
const consoleSpy = vi
104+
.spyOn(console, "error")
105+
.mockImplementation(() => {});
101106

102107
render(
103108
<TestErrorBoundary onError={onError}>
@@ -110,7 +115,7 @@ describe("client/provider", () => {
110115
await waitFor(() => expect(onError).toHaveBeenCalled());
111116
expect(onError.mock.calls[0][0]).toBeInstanceOf(Error);
112117
expect(onError.mock.calls[0][0].message).toBe("boom");
113-
118+
114119
const errorBoundary = await screen.findByTestId("boundary-error");
115120
expect(errorBoundary).not.toBeNull();
116121
expect(errorBoundary.textContent).toBe("error");

packages/spec/src/locales.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, it, expect } from "vitest";
22
import {
33
getLocaleCodeDelimiter,
4+
localeCodeSchema,
45
normalizeLocale,
56
resolveLocaleCode,
67
resolveOverriddenLocale,
@@ -88,3 +89,57 @@ describe("resolveOverridenLocale", () => {
8889
expect(resolveOverriddenLocale("frFR", "-")).toEqual("frFR");
8990
});
9091
});
92+
93+
describe("localeCodeSchema validation", () => {
94+
describe("standard BCP 47 format (hyphen)", () => {
95+
it("should accept language-region codes", () => {
96+
expect(localeCodeSchema.safeParse("en-US").success).toBe(true);
97+
expect(localeCodeSchema.safeParse("fr-CA").success).toBe(true);
98+
expect(localeCodeSchema.safeParse("es-MX").success).toBe(true);
99+
});
100+
101+
it("should accept language-script-region codes", () => {
102+
expect(localeCodeSchema.safeParse("zh-Hans-CN").success).toBe(true);
103+
expect(localeCodeSchema.safeParse("sr-Latn-RS").success).toBe(true);
104+
expect(localeCodeSchema.safeParse("sr-Cyrl-RS").success).toBe(true);
105+
});
106+
});
107+
108+
describe("underscore format (Java/Android)", () => {
109+
it("should accept language_region codes", () => {
110+
expect(localeCodeSchema.safeParse("en_US").success).toBe(true);
111+
expect(localeCodeSchema.safeParse("pt_BR").success).toBe(true);
112+
expect(localeCodeSchema.safeParse("de_DE").success).toBe(true);
113+
});
114+
115+
it("should accept language_script_region codes", () => {
116+
expect(localeCodeSchema.safeParse("zh_Hans_CN").success).toBe(true);
117+
expect(localeCodeSchema.safeParse("sr_Cyrl_RS").success).toBe(true);
118+
});
119+
});
120+
121+
describe("Android r-prefix format", () => {
122+
it("should accept language-rRegion codes", () => {
123+
expect(localeCodeSchema.safeParse("en-rUS").success).toBe(true);
124+
expect(localeCodeSchema.safeParse("fr-rCA").success).toBe(true);
125+
expect(localeCodeSchema.safeParse("es-rMX").success).toBe(true);
126+
expect(localeCodeSchema.safeParse("zh-rCN").success).toBe(true);
127+
});
128+
});
129+
130+
describe("invalid locale codes", () => {
131+
it("should reject invalid language codes", () => {
132+
expect(localeCodeSchema.safeParse("invalid-US").success).toBe(false);
133+
expect(localeCodeSchema.safeParse("xx-US").success).toBe(false);
134+
});
135+
136+
it("should reject invalid region codes", () => {
137+
expect(localeCodeSchema.safeParse("en-FAKE").success).toBe(false);
138+
expect(localeCodeSchema.safeParse("en-ZZ").success).toBe(false);
139+
});
140+
141+
it("should reject invalid script codes", () => {
142+
expect(localeCodeSchema.safeParse("zh-Fake-CN").success).toBe(false);
143+
});
144+
});
145+
});

0 commit comments

Comments
 (0)