@alepha/react 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/auth/index.browser.js +1488 -4
  2. package/dist/auth/index.browser.js.map +1 -1
  3. package/dist/auth/index.d.ts +2 -2
  4. package/dist/auth/index.js +1827 -4
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/core/index.d.ts +58 -937
  7. package/dist/core/index.d.ts.map +1 -1
  8. package/dist/core/index.js +139 -2014
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/form/index.d.ts.map +1 -1
  11. package/dist/form/index.js +6 -1
  12. package/dist/form/index.js.map +1 -1
  13. package/dist/head/index.browser.js +3 -1
  14. package/dist/head/index.browser.js.map +1 -1
  15. package/dist/head/index.d.ts +552 -8
  16. package/dist/head/index.d.ts.map +1 -1
  17. package/dist/head/index.js +17 -2
  18. package/dist/head/index.js.map +1 -1
  19. package/dist/{core → router}/index.browser.js +126 -516
  20. package/dist/router/index.browser.js.map +1 -0
  21. package/dist/router/index.d.ts +1334 -0
  22. package/dist/router/index.d.ts.map +1 -0
  23. package/dist/router/index.js +1939 -0
  24. package/dist/router/index.js.map +1 -0
  25. package/package.json +12 -6
  26. package/src/auth/__tests__/$auth.spec.ts +188 -0
  27. package/src/auth/index.ts +1 -1
  28. package/src/auth/services/ReactAuth.ts +1 -1
  29. package/src/core/__tests__/Router.spec.tsx +169 -0
  30. package/src/core/components/ClientOnly.tsx +14 -0
  31. package/src/core/components/ErrorBoundary.tsx +3 -2
  32. package/src/core/contexts/AlephaContext.ts +3 -0
  33. package/src/core/contexts/AlephaProvider.tsx +2 -1
  34. package/src/core/hooks/useAction.browser.spec.tsx +569 -0
  35. package/src/core/hooks/useAction.ts +11 -0
  36. package/src/core/index.ts +13 -102
  37. package/src/form/hooks/useForm.browser.spec.tsx +366 -0
  38. package/src/form/services/FormModel.ts +5 -0
  39. package/src/head/__tests__/expandSeo.spec.ts +203 -0
  40. package/src/head/__tests__/page-head.spec.ts +39 -0
  41. package/src/head/__tests__/seo-head.spec.ts +121 -0
  42. package/src/head/hooks/useHead.spec.tsx +288 -0
  43. package/src/head/index.ts +18 -8
  44. package/src/head/interfaces/Head.ts +3 -0
  45. package/src/head/providers/BrowserHeadProvider.browser.spec.ts +271 -0
  46. package/src/head/providers/HeadProvider.ts +6 -1
  47. package/src/head/providers/ServerHeadProvider.spec.ts +163 -0
  48. package/src/head/providers/ServerHeadProvider.ts +20 -0
  49. package/src/i18n/__tests__/integration.spec.tsx +239 -0
  50. package/src/i18n/components/Localize.spec.tsx +357 -0
  51. package/src/i18n/hooks/useI18n.browser.spec.tsx +438 -0
  52. package/src/i18n/providers/I18nProvider.spec.ts +389 -0
  53. package/src/{core → router}/components/ErrorViewer.tsx +2 -0
  54. package/src/router/components/Link.tsx +21 -0
  55. package/src/{core → router}/components/NestedView.tsx +3 -5
  56. package/src/router/components/NotFound.tsx +30 -0
  57. package/src/router/errors/Redirection.ts +28 -0
  58. package/src/{core → router}/hooks/useActive.ts +6 -2
  59. package/src/{core → router}/hooks/useQueryParams.ts +2 -2
  60. package/src/{core → router}/hooks/useRouter.ts +1 -1
  61. package/src/{core → router}/hooks/useRouterState.ts +1 -1
  62. package/src/{core → router}/index.browser.ts +14 -12
  63. package/src/{core/index.shared-router.ts → router/index.shared.ts} +6 -3
  64. package/src/router/index.ts +125 -0
  65. package/src/router/primitives/$page.browser.spec.tsx +702 -0
  66. package/src/router/primitives/$page.spec.tsx +702 -0
  67. package/src/{core → router}/primitives/$page.ts +1 -1
  68. package/src/{core → router}/providers/ReactBrowserProvider.ts +3 -13
  69. package/src/{core → router}/providers/ReactBrowserRendererProvider.ts +3 -0
  70. package/src/{core → router}/providers/ReactBrowserRouterProvider.ts +3 -0
  71. package/src/{core → router}/providers/ReactPageProvider.ts +5 -3
  72. package/src/router/providers/ReactServerProvider.spec.tsx +316 -0
  73. package/src/{core → router}/providers/ReactServerProvider.ts +12 -30
  74. package/src/{core → router}/services/ReactPageServerService.ts +3 -0
  75. package/src/{core → router}/services/ReactPageService.ts +5 -5
  76. package/src/{core → router}/services/ReactRouter.ts +26 -5
  77. package/dist/core/index.browser.js.map +0 -1
  78. package/dist/core/index.native.js +0 -403
  79. package/dist/core/index.native.js.map +0 -1
  80. package/src/core/components/Link.tsx +0 -18
  81. package/src/core/components/NotFound.tsx +0 -27
  82. package/src/core/errors/Redirection.ts +0 -13
  83. package/src/core/hooks/useSchema.ts +0 -88
  84. package/src/core/index.native.ts +0 -21
  85. package/src/core/index.shared.ts +0 -9
  86. /package/src/{core → router}/contexts/RouterLayerContext.ts +0 -0
@@ -0,0 +1,239 @@
1
+ import { Alepha } from "alepha";
2
+ import { describe, test } from "vitest";
3
+ import { $dictionary } from "../primitives/$dictionary.ts";
4
+ import { AlephaReactI18n } from "../index.ts";
5
+ import { I18nProvider } from "../providers/I18nProvider.ts";
6
+
7
+ describe("I18n Integration Tests", () => {
8
+ test("should lazy load dictionaries on server start", async ({ expect }) => {
9
+ let enLoaded = false;
10
+ let frLoaded = false;
11
+
12
+ class App {
13
+ en = $dictionary({
14
+ lazy: async () => {
15
+ enLoaded = true;
16
+ return { default: { hello: "Hello" } };
17
+ },
18
+ });
19
+
20
+ fr = $dictionary({
21
+ lazy: async () => {
22
+ frLoaded = true;
23
+ return { default: { hello: "Bonjour" } };
24
+ },
25
+ });
26
+ }
27
+
28
+ const alepha = Alepha.create().with(AlephaReactI18n);
29
+ const app = alepha.inject(App);
30
+
31
+ expect(enLoaded).toBe(false);
32
+ expect(frLoaded).toBe(false);
33
+
34
+ await alepha.start();
35
+
36
+ expect(enLoaded).toBe(true);
37
+ expect(frLoaded).toBe(true);
38
+ });
39
+
40
+ test("should share I18nProvider instance across application", async ({
41
+ expect,
42
+ }) => {
43
+ class App {
44
+ en = $dictionary({
45
+ lazy: async () => ({ default: { key: "value" } }),
46
+ });
47
+ }
48
+
49
+ const alepha = Alepha.create().with(AlephaReactI18n);
50
+ const app = alepha.inject(App);
51
+
52
+ const i18n1 = alepha.inject(I18nProvider);
53
+ const i18n2 = alepha.inject(I18nProvider);
54
+
55
+ expect(i18n1).toBe(i18n2);
56
+ expect(i18n1.registry).toBe(i18n2.registry);
57
+ });
58
+
59
+ test("should support multiple dictionary registrations", async ({
60
+ expect,
61
+ }) => {
62
+ class App {
63
+ en = $dictionary({
64
+ lazy: async () => ({
65
+ default: {
66
+ "app.title": "My Application",
67
+ "app.subtitle": "Built with Alepha",
68
+ "auth.login": "Log In",
69
+ "auth.logout": "Log Out",
70
+ },
71
+ }),
72
+ });
73
+
74
+ fr = $dictionary({
75
+ lazy: async () => ({
76
+ default: {
77
+ "app.title": "Mon Application",
78
+ "app.subtitle": "Construit avec Alepha",
79
+ "auth.login": "Se connecter",
80
+ "auth.logout": "Se déconnecter",
81
+ },
82
+ }),
83
+ });
84
+ }
85
+
86
+ const alepha = Alepha.create().with(AlephaReactI18n);
87
+ const app = alepha.inject(App);
88
+ const i18n = alepha.inject(I18nProvider);
89
+
90
+ await alepha.start();
91
+
92
+ // Test English
93
+ expect(i18n.tr("app.title")).toBe("My Application");
94
+ expect(i18n.tr("app.subtitle")).toBe("Built with Alepha");
95
+ expect(i18n.tr("auth.login")).toBe("Log In");
96
+ expect(i18n.tr("auth.logout")).toBe("Log Out");
97
+
98
+ // Switch to French
99
+ await i18n.setLang("fr");
100
+
101
+ expect(i18n.tr("app.title")).toBe("Mon Application");
102
+ expect(i18n.tr("app.subtitle")).toBe("Construit avec Alepha");
103
+ expect(i18n.tr("auth.login")).toBe("Se connecter");
104
+ expect(i18n.tr("auth.logout")).toBe("Se déconnecter");
105
+ });
106
+
107
+ test("should handle translation with complex interpolation across languages", async ({
108
+ expect,
109
+ }) => {
110
+ class App {
111
+ en = $dictionary({
112
+ lazy: async () => ({
113
+ default: {
114
+ notification: "$1 sent you $2 message(s) about $3",
115
+ },
116
+ }),
117
+ });
118
+
119
+ fr = $dictionary({
120
+ lazy: async () => ({
121
+ default: {
122
+ notification: "$1 vous a envoyé $2 message(s) à propos de $3",
123
+ },
124
+ }),
125
+ });
126
+
127
+ es = $dictionary({
128
+ lazy: async () => ({
129
+ default: {
130
+ notification: "$1 te envió $2 mensaje(s) sobre $3",
131
+ },
132
+ }),
133
+ });
134
+ }
135
+
136
+ const alepha = Alepha.create().with(AlephaReactI18n);
137
+ const app = alepha.inject(App);
138
+ const i18n = alepha.inject(I18nProvider);
139
+
140
+ await alepha.start();
141
+
142
+ const args = ["John", "3", "your project"];
143
+
144
+ expect(i18n.tr("notification", { args })).toBe(
145
+ "John sent you 3 message(s) about your project",
146
+ );
147
+
148
+ await i18n.setLang("fr");
149
+ expect(i18n.tr("notification", { args })).toBe(
150
+ "John vous a envoyé 3 message(s) à propos de your project",
151
+ );
152
+
153
+ await i18n.setLang("es");
154
+ expect(i18n.tr("notification", { args })).toBe(
155
+ "John te envió 3 mensaje(s) sobre your project",
156
+ );
157
+ });
158
+
159
+ test("should properly cleanup on stop", async ({ expect }) => {
160
+ class App {
161
+ en = $dictionary({
162
+ lazy: async () => ({ default: { test: "Test" } }),
163
+ });
164
+ }
165
+
166
+ const alepha = Alepha.create().with(AlephaReactI18n);
167
+ const app = alepha.inject(App);
168
+ const i18n = alepha.inject(I18nProvider);
169
+
170
+ await alepha.start();
171
+ expect(i18n.registry[0].translations).toEqual({ test: "Test" });
172
+
173
+ await alepha.stop();
174
+ // Registry should still exist after stop
175
+ expect(i18n.registry).toBeDefined();
176
+ });
177
+
178
+ test("should work with empty language fallback chain", async ({ expect }) => {
179
+ class App {
180
+ en = $dictionary({
181
+ lazy: async () => ({ default: { only_en: "Only in English" } }),
182
+ });
183
+
184
+ fr = $dictionary({
185
+ lazy: async () => ({ default: { only_fr: "Seulement en français" } }),
186
+ });
187
+ }
188
+
189
+ const alepha = Alepha.create().with(AlephaReactI18n);
190
+ const app = alepha.inject(App);
191
+ const i18n = alepha.inject(I18nProvider);
192
+
193
+ await alepha.start();
194
+
195
+ // In English, can't find French-only key
196
+ expect(i18n.tr("only_fr")).toBe("only_fr");
197
+ expect(i18n.tr("only_en")).toBe("Only in English");
198
+
199
+ // In French, falls back to English for English-only key
200
+ await i18n.setLang("fr");
201
+ expect(i18n.tr("only_fr")).toBe("Seulement en français");
202
+ expect(i18n.tr("only_en")).toBe("Only in English");
203
+ });
204
+
205
+ test("should handle rapid language switches", async ({ expect }) => {
206
+ class App {
207
+ en = $dictionary({
208
+ lazy: async () => ({ default: { status: "Active" } }),
209
+ });
210
+
211
+ fr = $dictionary({
212
+ lazy: async () => ({ default: { status: "Actif" } }),
213
+ });
214
+
215
+ de = $dictionary({
216
+ lazy: async () => ({ default: { status: "Aktiv" } }),
217
+ });
218
+ }
219
+
220
+ const alepha = Alepha.create().with(AlephaReactI18n);
221
+ const app = alepha.inject(App);
222
+ const i18n = alepha.inject(I18nProvider);
223
+
224
+ await alepha.start();
225
+
226
+ // Rapidly switch languages
227
+ await i18n.setLang("fr");
228
+ expect(i18n.tr("status")).toBe("Actif");
229
+
230
+ await i18n.setLang("de");
231
+ expect(i18n.tr("status")).toBe("Aktiv");
232
+
233
+ await i18n.setLang("en");
234
+ expect(i18n.tr("status")).toBe("Active");
235
+
236
+ await i18n.setLang("fr");
237
+ expect(i18n.tr("status")).toBe("Actif");
238
+ });
239
+ });
@@ -0,0 +1,357 @@
1
+ import { AlephaContext } from "@alepha/react";
2
+ import { I18nProvider, Localize } from "../index.ts";
3
+ import { Alepha, t } from "alepha";
4
+ import { DateTimeProvider } from "alepha/datetime";
5
+ import type { ReactNode } from "react";
6
+ import { renderToString } from "react-dom/server";
7
+ import { describe, expect, it } from "vitest";
8
+
9
+ describe("<Localize/>", () => {
10
+ const setup = () => {
11
+ const alepha = Alepha.create();
12
+ return {
13
+ i18n: alepha.inject(I18nProvider),
14
+ dateTime: alepha.inject(DateTimeProvider),
15
+ alepha,
16
+ render: (children: ReactNode) =>
17
+ renderToString(
18
+ <AlephaContext value={alepha}>{children}</AlephaContext>,
19
+ ),
20
+ };
21
+ };
22
+
23
+ it("should format date", () => {
24
+ const { render, i18n } = setup();
25
+ const date = new Date("2024-09-23T23:45:00Z");
26
+ expect(render(<Localize value={date} />)).toBe("9/24/2024");
27
+ i18n.setLang("fr");
28
+ expect(render(<Localize value={date} />)).toBe("24/09/2024");
29
+ });
30
+
31
+ it("should format number", () => {
32
+ const { render, i18n } = setup();
33
+ const number = 1234567.89;
34
+ expect(render(<Localize value={number} />)).toBe("1,234,567.89");
35
+ i18n.setLang("de");
36
+ expect(render(<Localize value={number} />)).toBe("1.234.567,89");
37
+ i18n.setLang("fr");
38
+ expect(render(<Localize value={number} />)).toBe("1\u202f234\u202f567,89");
39
+ });
40
+
41
+ // it("should format typebox error", async () => {
42
+ // const { render, i18n, alepha } = setup();
43
+ // const boom = async () => alepha.codec.decode(t.number(), "..");
44
+ // const error = await boom().catch((err) => err);
45
+ // expect(render(<Localize value={error} />)).toBe("must be number");
46
+ // await i18n.setLang("fr");
47
+ // expect(render(<Localize value={error} />)).toBe("doit être number");
48
+ // });
49
+
50
+ describe("number formatting options", () => {
51
+ it("should format currency", () => {
52
+ const { render } = setup();
53
+ const number = 1234.56;
54
+ expect(
55
+ render(
56
+ <Localize
57
+ value={number}
58
+ number={{ style: "currency", currency: "USD" }}
59
+ />,
60
+ ),
61
+ ).toBe("$1,234.56");
62
+ });
63
+
64
+ it("should format currency with locale", () => {
65
+ const { render, i18n } = setup();
66
+ const number = 1234.56;
67
+ i18n.setLang("fr");
68
+ expect(
69
+ render(
70
+ <Localize
71
+ value={number}
72
+ number={{ style: "currency", currency: "EUR" }}
73
+ />,
74
+ ),
75
+ ).toBe("1\u202f234,56\u00a0€");
76
+ });
77
+
78
+ it("should format percentage", () => {
79
+ const { render } = setup();
80
+ const number = 0.1234;
81
+ expect(
82
+ render(<Localize value={number} number={{ style: "percent" }} />),
83
+ ).toBe("12%");
84
+ });
85
+
86
+ it("should format with minimum fraction digits", () => {
87
+ const { render } = setup();
88
+ const number = 10;
89
+ expect(
90
+ render(
91
+ <Localize value={number} number={{ minimumFractionDigits: 2 }} />,
92
+ ),
93
+ ).toBe("10.00");
94
+ });
95
+
96
+ it("should format with maximum fraction digits", () => {
97
+ const { render } = setup();
98
+ const number = 10.123456;
99
+ expect(
100
+ render(
101
+ <Localize value={number} number={{ maximumFractionDigits: 2 }} />,
102
+ ),
103
+ ).toBe("10.12");
104
+ });
105
+
106
+ it("should format with notation", () => {
107
+ const { render } = setup();
108
+ const number = 1000000;
109
+ expect(
110
+ render(<Localize value={number} number={{ notation: "compact" }} />),
111
+ ).toBe("1M");
112
+ });
113
+ });
114
+
115
+ describe("date formatting options", () => {
116
+ it("should format with dayjs format string - LLL", () => {
117
+ const { render, i18n, dateTime } = setup();
118
+ const date = dateTime.utc("2024-09-24T00:45:00Z").toDate();
119
+ const formatted = render(<Localize value={date} date="LLL" />);
120
+ // Format depends on local timezone, so just check it contains the key parts
121
+ expect(formatted).toContain("September");
122
+ expect(formatted).toContain("2024");
123
+ i18n.setLang("fr");
124
+ const frFormatted = render(<Localize value={date} date="LLL" />);
125
+ expect(frFormatted).toContain("septembre");
126
+ expect(frFormatted).toContain("2024");
127
+ });
128
+
129
+ it("should format with dayjs format string - YYYY-MM-DD", () => {
130
+ const { render } = setup();
131
+ const date = new Date("2024-09-23T23:45:00Z");
132
+ expect(render(<Localize value={date} date="YYYY-MM-DD" />)).toBe(
133
+ "2024-09-24",
134
+ );
135
+ });
136
+
137
+ it("should format with dayjs format string - dddd, MMMM D YYYY", () => {
138
+ const { render } = setup();
139
+ const date = new Date("2024-09-23T23:45:00Z");
140
+ expect(render(<Localize value={date} date="dddd, MMMM D YYYY" />)).toBe(
141
+ "Tuesday, September 24 2024",
142
+ );
143
+ });
144
+
145
+ it("should format with fromNow for Date", () => {
146
+ const { render, dateTime } = setup();
147
+ dateTime.pause();
148
+ const date = dateTime.now().subtract(2, "hour").toDate();
149
+ const result = render(<Localize value={date} date="fromNow" />);
150
+ expect(result).toBe("2 hours ago");
151
+ });
152
+
153
+ it("should format with fromNow for DateTime", () => {
154
+ const { render, dateTime } = setup();
155
+ dateTime.pause();
156
+ const date = dateTime.now().subtract(3, "day");
157
+ const result = render(<Localize value={date} date="fromNow" />);
158
+ expect(result).toBe("3 days ago");
159
+ });
160
+
161
+ it("should format DateTime with dayjs format string", () => {
162
+ const { render, dateTime } = setup();
163
+ const date = dateTime.of("2024-09-23T23:45:00Z");
164
+ expect(render(<Localize value={date} date="YYYY-MM-DD" />)).toBe(
165
+ "2024-09-24",
166
+ );
167
+ });
168
+
169
+ it("should format with Intl.DateTimeFormatOptions", () => {
170
+ const { render } = setup();
171
+ const date = new Date("2024-09-23T23:45:00Z");
172
+ expect(
173
+ render(
174
+ <Localize
175
+ value={date}
176
+ date={{
177
+ weekday: "long",
178
+ year: "numeric",
179
+ month: "long",
180
+ day: "numeric",
181
+ }}
182
+ />,
183
+ ),
184
+ ).toBe("Tuesday, September 24, 2024");
185
+ });
186
+
187
+ it("should format with Intl.DateTimeFormatOptions and locale", () => {
188
+ const { render, i18n } = setup();
189
+ const date = new Date("2024-09-23T23:45:00Z");
190
+ i18n.setLang("fr");
191
+ expect(
192
+ render(
193
+ <Localize
194
+ value={date}
195
+ date={{
196
+ weekday: "long",
197
+ year: "numeric",
198
+ month: "long",
199
+ day: "numeric",
200
+ }}
201
+ />,
202
+ ),
203
+ ).toBe("mardi 24 septembre 2024");
204
+ });
205
+
206
+ it("should format DateTime with Intl.DateTimeFormatOptions", () => {
207
+ const { render, dateTime } = setup();
208
+ const date = dateTime.utc("2024-09-24T00:45:00Z");
209
+ const formatted = render(
210
+ <Localize
211
+ value={date}
212
+ date={{
213
+ hour: "2-digit",
214
+ minute: "2-digit",
215
+ hour12: true,
216
+ }}
217
+ />,
218
+ );
219
+ // Format depends on local timezone, so just verify it contains time parts
220
+ expect(formatted).toMatch(/\d{1,2}:\d{2}\s(AM|PM)/);
221
+ });
222
+ });
223
+
224
+ describe("string date parsing", () => {
225
+ it("should parse string date when dateOptions is provided", () => {
226
+ const { render } = setup();
227
+ const dateString = "2024-09-24T12:00:00Z";
228
+ expect(render(<Localize value={dateString} date="YYYY-MM-DD" />)).toBe(
229
+ "2024-09-24",
230
+ );
231
+ });
232
+
233
+ it("should parse string date with format options", () => {
234
+ const { render } = setup();
235
+ const dateString = "2024-09-24T12:00:00Z";
236
+ expect(render(<Localize value={dateString} date="LLL" />)).toContain(
237
+ "September",
238
+ );
239
+ });
240
+
241
+ it("should parse string date with timezone", () => {
242
+ const { render } = setup();
243
+ const dateString = "2024-09-24T12:00:00Z";
244
+ expect(
245
+ render(
246
+ <Localize
247
+ value={dateString}
248
+ timezone="America/New_York"
249
+ date="YYYY-MM-DD HH:mm"
250
+ />,
251
+ ),
252
+ ).toBe("2024-09-24 08:00");
253
+ });
254
+
255
+ it("should return string as-is when no dateOptions provided", () => {
256
+ const { render } = setup();
257
+ const dateString = "2024-09-24T12:00:00Z";
258
+ expect(render(<Localize value={dateString} />)).toBe(dateString);
259
+ });
260
+ });
261
+
262
+ describe("timezone formatting", () => {
263
+ it("should format Date with timezone using Intl", () => {
264
+ const { render } = setup();
265
+ const date = new Date("2024-09-24T12:00:00Z");
266
+ expect(
267
+ render(<Localize value={date} timezone="America/New_York" />),
268
+ ).toBe("9/24/2024");
269
+ });
270
+
271
+ it("should format Date with timezone and dateOptions", () => {
272
+ const { render } = setup();
273
+ const date = new Date("2024-09-24T12:00:00Z");
274
+ expect(
275
+ render(
276
+ <Localize
277
+ value={date}
278
+ timezone="America/New_York"
279
+ date={{
280
+ hour: "2-digit",
281
+ minute: "2-digit",
282
+ hour12: true,
283
+ timeZoneName: "short",
284
+ }}
285
+ />,
286
+ ),
287
+ ).toContain("EDT");
288
+ });
289
+
290
+ it("should format Date with timezone using dayjs format", () => {
291
+ const { render } = setup();
292
+ const date = new Date("2024-09-24T12:00:00Z");
293
+ const formatted = render(
294
+ <Localize
295
+ value={date}
296
+ timezone="America/New_York"
297
+ date="YYYY-MM-DD HH:mm"
298
+ />,
299
+ );
300
+ expect(formatted).toBe("2024-09-24 08:00");
301
+ });
302
+
303
+ it("should format DateTime with timezone using dayjs format", () => {
304
+ const { render, dateTime } = setup();
305
+ const date = dateTime.utc("2024-09-24T12:00:00Z");
306
+ const formatted = render(
307
+ <Localize
308
+ value={date}
309
+ timezone="Europe/Paris"
310
+ date="YYYY-MM-DD HH:mm"
311
+ />,
312
+ );
313
+ expect(formatted).toBe("2024-09-24 14:00");
314
+ });
315
+
316
+ it("should format DateTime with timezone and Intl options", () => {
317
+ const { render, dateTime } = setup();
318
+ const date = dateTime.utc("2024-09-24T12:00:00Z");
319
+ const formatted = render(
320
+ <Localize
321
+ value={date}
322
+ timezone="Asia/Tokyo"
323
+ date={{
324
+ hour: "2-digit",
325
+ minute: "2-digit",
326
+ hour12: false,
327
+ }}
328
+ />,
329
+ );
330
+ expect(formatted).toBe("21:00");
331
+ });
332
+
333
+ it("should format DateTime with timezone using LLL format", () => {
334
+ const { render, dateTime } = setup();
335
+ const date = dateTime.utc("2024-09-24T12:00:00Z");
336
+ const formatted = render(
337
+ <Localize value={date} timezone="America/Los_Angeles" date="LLL" />,
338
+ );
339
+ expect(formatted).toContain("September");
340
+ expect(formatted).toContain("2024");
341
+ expect(formatted).toContain("5:00 AM");
342
+ });
343
+
344
+ it("should respect locale and timezone together", () => {
345
+ const { render, i18n, dateTime } = setup();
346
+ const date = dateTime.utc("2024-09-24T12:00:00Z");
347
+ i18n.setLang("fr");
348
+ const formatted = render(
349
+ <Localize value={date} timezone="Europe/Paris" date="LLL" />,
350
+ );
351
+ console.log(formatted);
352
+ expect(formatted).toContain("septembre");
353
+ expect(formatted).toContain("2024");
354
+ expect(formatted).toContain("14:00");
355
+ });
356
+ });
357
+ });