From 6c4269fc6fa6b03fa66c6beceef8626e360f3802 Mon Sep 17 00:00:00 2001 From: Kavidu23 <10952412@students.plymouth.ac.uk> Date: Sun, 26 Oct 2025 18:03:41 +0530 Subject: [PATCH 1/3] test(hooks): add unit tests for @asgardeo/react hooks to cover edge cases --- packages/react/package.json | 3 +- ...pts-a-config-object-without-breaking-1.png | Bin 0 -> 2170 bytes ...fault-values-when-context-is-missing-1.png | Bin 0 -> 2170 bytes ...s-values-from-context-when-available-1.png | Bin 0 -> 2170 bytes ...hook-handles-relative-afterSignInUrl-1.png | Bin 0 -> 2170 bytes ...rage-setValue-and-validate-on-change-1.png | Bin 0 -> 2170 bytes .../react/src/__test__/useBranding.test.ts | 93 ++++++ .../react/src/__test__/useBrowserUrl.test.ts | 77 +++++ packages/react/src/__test__/useForm.test.ts | 184 +++++++++++ .../react/src/__test__/useTranslation.test.ts | 154 +++++++++ packages/react/tsconfig.spec.json | 6 +- pnpm-lock.yaml | 300 +++++++++++++++++- 12 files changed, 811 insertions(+), 6 deletions(-) create mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png create mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png create mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png create mode 100644 packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png create mode 100644 packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png create mode 100644 packages/react/src/__test__/useBranding.test.ts create mode 100644 packages/react/src/__test__/useBrowserUrl.test.ts create mode 100644 packages/react/src/__test__/useForm.test.ts create mode 100644 packages/react/src/__test__/useTranslation.test.ts diff --git a/packages/react/package.json b/packages/react/package.json index 474abde3..316dc1f5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", "@types/node": "^22.15.3", "@types/react": "^19.1.5", "@vitest/browser": "^3.1.3", @@ -62,8 +63,8 @@ "react": ">=16.8.0" }, "dependencies": { - "@asgardeo/i18n": "workspace:^", "@asgardeo/browser": "workspace:^", + "@asgardeo/i18n": "workspace:^", "@emotion/css": "^11.13.5", "@floating-ui/react": "^0.27.12", "@types/react-dom": "^19.1.5", diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d561bf03e76f31b6ebd40213a020dea1a733702d GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 literal 0 HcmV?d00001 diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d561bf03e76f31b6ebd40213a020dea1a733702d GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 literal 0 HcmV?d00001 diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d561bf03e76f31b6ebd40213a020dea1a733702d GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 literal 0 HcmV?d00001 diff --git a/packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png b/packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d561bf03e76f31b6ebd40213a020dea1a733702d GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 literal 0 HcmV?d00001 diff --git a/packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png b/packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d561bf03e76f31b6ebd40213a020dea1a733702d GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 literal 0 HcmV?d00001 diff --git a/packages/react/src/__test__/useBranding.test.ts b/packages/react/src/__test__/useBranding.test.ts new file mode 100644 index 00000000..17006a7e --- /dev/null +++ b/packages/react/src/__test__/useBranding.test.ts @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { renderHook } from '@testing-library/react'; +import useBranding from '../hooks/useBranding'; +import { vi, describe, it, expect, afterEach } from 'vitest'; + +// Mock the context and make default export a vi.fn() +vi.mock('../contexts/Branding/useBrandingContext', () => { + return { default: vi.fn() }; +}); + +// Import the mocked function +import useBrandingContext from '../contexts/Branding/useBrandingContext'; + +describe('useBranding hook', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it('returns values from context when available', () => { + const mockReturn = { + brandingPreference: { name: 'TestBrand' }, + theme: { colors: { primary: { main: '#000' } } }, + activeTheme: 'light', + isLoading: false, + error: null, + fetchBranding: vi.fn(), + refetch: vi.fn(), + }; + + (useBrandingContext as unknown as ReturnType).mockReturnValue(mockReturn); + + const { result } = renderHook(() => useBranding()); + + expect(result.current.brandingPreference).toEqual(mockReturn.brandingPreference); + expect(result.current.theme).toEqual(mockReturn.theme); + expect(result.current.activeTheme).toBe('light'); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(typeof result.current.fetchBranding).toBe('function'); + expect(typeof result.current.refetch).toBe('function'); + }); + + it('returns default values when context is missing', async () => { + (useBrandingContext as unknown as ReturnType).mockImplementation(() => { + throw new Error('Provider missing'); + }); + + const { result } = renderHook(() => useBranding()); + + expect(result.current.brandingPreference).toBeNull(); + expect(result.current.theme).toBeNull(); + expect(result.current.activeTheme).toBeNull(); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeInstanceOf(Error); + + await expect(result.current.fetchBranding()).resolves.toBeUndefined(); + await expect(result.current.refetch()).resolves.toBeUndefined(); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('useBranding: BrandingProvider not available') + ); + }); + + it('accepts a config object without breaking', () => { + (useBrandingContext as unknown as ReturnType).mockImplementation(() => { + throw new Error('Provider missing'); + }); + + const { result } = renderHook(() => useBranding({ locale: 'en', autoFetch: true })); + + expect(result.current.brandingPreference).toBeNull(); + expect(result.current.theme).toBeNull(); + expect(result.current.activeTheme).toBeNull(); + }); +}); diff --git a/packages/react/src/__test__/useBrowserUrl.test.ts b/packages/react/src/__test__/useBrowserUrl.test.ts new file mode 100644 index 00000000..44192f7a --- /dev/null +++ b/packages/react/src/__test__/useBrowserUrl.test.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import useBrowserUrl from "../hooks/useBrowserUrl"; +import { hasAuthParamsInUrl } from "@asgardeo/browser"; +import { vi, describe, it, expect, beforeEach } from "vitest"; + +// Mock the module +vi.mock("@asgardeo/browser", () => ({ + hasAuthParamsInUrl: vi.fn(), +})); + +describe("useBrowserUrl hook", () => { + const { hasAuthParams } = useBrowserUrl(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns true if hasAuthParamsInUrl returns true and URL matches afterSignInUrl", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(true); + const url = new URL("https://example.com/callback"); + const afterSignInUrl = "https://example.com/callback"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(true); + }); + + it("returns false if hasAuthParamsInUrl returns false and no error param exists", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(false); + const url = new URL("https://example.com/callback"); + const afterSignInUrl = "https://example.com/other"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(false); + }); + + it("returns true if URL contains error param", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(false); + const url = new URL("https://example.com/callback?error=access_denied"); + const afterSignInUrl = "https://example.com/other"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(true); + }); + + it("returns false if URL does not match afterSignInUrl and no error param and hasAuthParamsInUrl false", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(false); + const url = new URL("https://example.com/callback"); + const afterSignInUrl = "https://example.com/other"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(false); + }); + + // Edge case: trailing slash mismatch + it("normalizes URLs with trailing slashes", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(true); + const url = new URL("https://example.com/callback/"); + const afterSignInUrl = "https://example.com/callback"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(false); // still false due to exact match + }); + + // Edge case: relative afterSignInUrl + it("handles relative afterSignInUrl", () => { + (hasAuthParamsInUrl as unknown as ReturnType).mockReturnValue(true); + const url = new URL("https://example.com/callback"); + const afterSignInUrl = "/callback"; + expect(hasAuthParams(url, afterSignInUrl)).toBe(false); // relative URL fails exact match + }); +}); diff --git a/packages/react/src/__test__/useForm.test.ts b/packages/react/src/__test__/useForm.test.ts new file mode 100644 index 00000000..9c74132a --- /dev/null +++ b/packages/react/src/__test__/useForm.test.ts @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { renderHook, act } from '@testing-library/react'; +import { useForm, UseFormConfig } from '../hooks/useForm'; +import { vi, describe, it, expect } from 'vitest'; + +interface LoginForm extends Record { + username: string; + password: string; + email?: string; +} + +describe('useForm hook - full coverage', () => { + const initialValues: LoginForm = { username: '', password: '', email: '' }; + const fields = [ + { name: 'username', required: true }, + { name: 'password', required: true }, + { name: 'email', required: false, validator: (value: string) => value.includes('@') ? null : 'Invalid email' } + ]; + + const globalValidator = (values: LoginForm) => { + const errors: Record = {}; + if (values.password && values.password.length < 6) { + errors['password'] = 'Password too short'; + } + return errors; + }; + + const config: UseFormConfig = { initialValues, fields, validator: globalValidator }; + + it('initial state', () => { + const { result } = renderHook(() => useForm(config)); + expect(result.current.values).toEqual(initialValues); + expect(result.current.errors).toEqual({}); + expect(result.current.touched).toEqual({}); + expect(result.current.isValid).toBe(true); + expect(result.current.isSubmitted).toBe(false); + }); + + it('setValue and validate on change', () => { + const { result } = renderHook(() => useForm({ ...config, validateOnChange: true })); + + act(() => result.current.setValue('username', 'Alice')); + expect(result.current.values.username).toBe('Alice'); + + act(() => result.current.setValue('email', 'wrong-email')); + expect(result.current.errors['email']).toBe('Invalid email'); + + act(() => result.current.setValue('email', 'alice@example.com')); + expect(result.current.errors['email']).toBeUndefined(); + }); + + it('bulk setValues', () => { + const { result } = renderHook(() => useForm(config)); + + act(() => result.current.setValues({ username: 'Bob', password: '123456' })); + expect(result.current.values.username).toBe('Bob'); + expect(result.current.values.password).toBe('123456'); + }); + + it('setTouched and validate on blur', () => { + const { result } = renderHook(() => useForm(config)); + + act(() => result.current.setTouched('username')); + expect(result.current.touched['username']).toBe(true); + expect(result.current.errors['username']).toBe('This field is required'); + }); + + it('bulk setTouchedFields', () => { + const { result } = renderHook(() => useForm(config)); + act(() => result.current.setTouchedFields({ username: true, password: true })); + expect(result.current.touched['username']).toBe(true); + expect(result.current.touched['password']).toBe(true); + }); + + it('touchAllFields triggers validation', () => { + const { result } = renderHook(() => useForm(config)); + act(() => result.current.touchAllFields()); + expect(result.current.touched['username']).toBe(true); + expect(result.current.touched['password']).toBe(true); + expect(result.current.errors['username']).toBe('This field is required'); + }); + + it('setErrors and clearErrors', () => { + const { result } = renderHook(() => useForm(config)); + act(() => result.current.setErrors({ username: 'Custom error', password: 'Another error' })); + expect(result.current.errors['username']).toBe('Custom error'); + expect(result.current.errors['password']).toBe('Another error'); + + act(() => result.current.clearErrors()); + expect(result.current.errors).toEqual({}); + }); + + it('validateField returns correct errors', () => { + const { result } = renderHook(() => useForm(config)); + act(() => result.current.setValue('email', 'invalid')); + expect(result.current.validateField('email')).toBe('Invalid email'); + expect(result.current.validateField('username')).toBe('This field is required'); + }); + + it('validateForm returns correct ValidationResult', () => { + const { result } = renderHook(() => useForm(config)); + act(() => result.current.setValue('password', '123')); + const validation = result.current.validateForm(); + expect(validation.isValid).toBe(false); + expect(validation.errors['password']).toBe('Password too short'); + }); + + it('handleSubmit prevents submission if invalid', async () => { + const { result } = renderHook(() => useForm(config)); + const onSubmit = vi.fn(); + + await act(async () => { + await result.current.handleSubmit(onSubmit)({ preventDefault: vi.fn() } as any); + }); + + expect(onSubmit).not.toHaveBeenCalled(); + expect(result.current.isSubmitted).toBe(true); + }); + + it('handleSubmit calls onSubmit if valid', async () => { + const { result } = renderHook(() => useForm(config)); + const onSubmit = vi.fn(); + + act(() => { + result.current.setValues({ username: 'Alice', password: '123456', email: 'alice@example.com' }); + }); + + await act(async () => { + await result.current.handleSubmit(onSubmit)(); + }); + + expect(onSubmit).toHaveBeenCalledWith({ username: 'Alice', password: '123456', email: 'alice@example.com' }); + }); + + it('reset restores initial state', () => { + const { result } = renderHook(() => useForm(config)); + act(() => { + result.current.setValue('username', 'Changed'); + result.current.setTouched('username'); + result.current.setError('username', 'Error'); + result.current.reset(); + }); + + expect(result.current.values).toEqual(initialValues); + expect(result.current.touched).toEqual({}); + expect(result.current.errors).toEqual({}); + expect(result.current.isSubmitted).toBe(false); + }); + + it('getFieldProps works correctly', () => { + const { result } = renderHook(() => useForm(config)); + const props = result.current.getFieldProps('username'); + + expect(props.name).toBe('username'); + expect(props.required).toBe(true); + expect(props.value).toBe(''); + expect(props.touched).toBe(false); + expect(props.error).toBeUndefined(); + expect(typeof props.onBlur).toBe('function'); + expect(typeof props.onChange).toBe('function'); + + act(() => props.onChange('NewValue')); + expect(result.current.values.username).toBe('NewValue'); + + act(() => props.onBlur()); + expect(result.current.touched['username']).toBe(true); + }); +}); diff --git a/packages/react/src/__test__/useTranslation.test.ts b/packages/react/src/__test__/useTranslation.test.ts new file mode 100644 index 00000000..791730bb --- /dev/null +++ b/packages/react/src/__test__/useTranslation.test.ts @@ -0,0 +1,154 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { renderHook } from "@testing-library/react"; +import React from "react"; +import useTranslation from "../hooks/useTranslation"; +import I18nContext, { I18nContextValue } from "../contexts/I18n/I18nContext"; +import { I18nBundle, I18nTextDirection } from "@asgardeo/i18n"; +import { vi, describe, it, expect } from "vitest"; + +// --- MOCK @asgardeo/browser --- +vi.mock("@asgardeo/browser", () => ({ + deepMerge: vi.fn((a, b) => ({ ...a, ...b })), + I18nPreferences: vi.fn(), +})); + +// --- Mock Data --- +const globalBundles: Record = { + en: { + translations: { welcome: "Welcome", goodbye: "Goodbye", helloName: "Hello, {name}!" } as any, + metadata: { + localeCode: "en-US", + countryCode: "US", + languageCode: "en", + displayName: "English", + direction: "ltr" as I18nTextDirection, + }, + }, + es: { + translations: { welcome: "Bienvenido", goodbye: "Adiós" } as any, + metadata: { + localeCode: "es-ES", + countryCode: "ES", + languageCode: "es", + displayName: "Spanish", + direction: "ltr" as I18nTextDirection, + }, + }, +}; + +const mockSetLanguage = vi.fn(); + +// --- Context helper --- +const createTFunction = (lang: "en" | "es") => (key: string, params?: Record) => { + const translation = (globalBundles[lang].translations as any)[key] + || (globalBundles["en"].translations as any)[key] + || key; + if (!params) return translation; + return Object.keys(params).reduce( + (str, p) => str.replace(`{${p}}`, params[p]), + translation + ); +}; + +const contextValue: I18nContextValue = { + t: createTFunction("en"), + currentLanguage: "en", + setLanguage: mockSetLanguage, + bundles: globalBundles, + fallbackLanguage: "en", +}; + +const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => + React.createElement(I18nContext.Provider, { value: contextValue }, children); + +const esContext: I18nContextValue = { + ...contextValue, + currentLanguage: "es", + t: createTFunction("es"), +}; + +const esWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => + React.createElement(I18nContext.Provider, { value: esContext }, children); + +// --- Tests --- +describe("useTranslation hook", () => { + it("returns translations for current language", () => { + const { result } = renderHook(() => useTranslation(), { wrapper }); + const t = result.current.t; + + expect(t("welcome")).toBe("Welcome"); + expect(t("goodbye")).toBe("Goodbye"); + expect(t("nonexistent")).toBe("nonexistent"); + }); + + it("supports translation parameters", () => { + const { result } = renderHook(() => useTranslation(), { wrapper }); + const t = result.current.t; + + expect(t("helloName", { name: "Alice" })).toBe("Hello, Alice!"); + }); + + it("falls back to fallback language if key missing in current language", () => { + const { result } = renderHook(() => useTranslation(), { wrapper: esWrapper }); + const t = result.current.t; + + expect(t("helloName", { name: "Bob" })).toBe("Hello, Bob!"); + }); + + it("allows changing language via setLanguage", () => { + const { result } = renderHook(() => useTranslation(), { wrapper }); + + result.current.setLanguage("es"); + expect(mockSetLanguage).toHaveBeenCalledWith("es"); + }); + + it("merges component-level bundles correctly", () => { + const componentBundles = { + bundles: { + en: { + translations: { welcome: "Welcome Component" } as any, + metadata: { + localeCode: "en-US", + countryCode: "US", + languageCode: "en", + displayName: "English", + direction: "ltr" as I18nTextDirection, + }, + }, + }, + }; + + const { result } = renderHook(() => useTranslation(componentBundles), { wrapper }); + const t = result.current.t; + + expect(t("welcome")).toBe("Welcome Component"); + expect(t("goodbye")).toBe("Goodbye"); + }); + + it("returns availableLanguages correctly", () => { + const { result } = renderHook(() => useTranslation(), { wrapper }); + expect(result.current.availableLanguages).toEqual(["en", "es"]); + }); + + it("throws error if used outside of I18nProvider", () => { + expect(() => renderHook(() => useTranslation())).toThrow( + /useTranslation must be used within an I18nProvider/ + ); + }); +}); diff --git a/packages/react/tsconfig.spec.json b/packages/react/tsconfig.spec.json index 46a76e28..74d909dd 100644 --- a/packages/react/tsconfig.spec.json +++ b/packages/react/tsconfig.spec.json @@ -2,12 +2,12 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "dist", - "module": "commonjs", - "types": ["jest", "node"] + "module": "esnext", + "types": ["vitest", "node"] }, "include": [ "test-configs", - "jest.config.js", + "vitest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.test.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a741c5af..d08e4d0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -384,6 +384,9 @@ importers: '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/node': specifier: ^22.15.3 version: 22.15.30 @@ -951,6 +954,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -963,6 +972,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.9': resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} @@ -975,6 +990,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.9': resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} @@ -987,6 +1008,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.9': resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} @@ -999,6 +1026,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.9': resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} @@ -1011,6 +1044,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.9': resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} @@ -1023,6 +1062,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.9': resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} @@ -1035,6 +1080,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} @@ -1047,6 +1098,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.9': resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} @@ -1059,6 +1116,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.9': resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} @@ -1071,6 +1134,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.9': resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} @@ -1083,6 +1152,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.9': resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} @@ -1095,6 +1170,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.9': resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} @@ -1107,6 +1188,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.9': resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} @@ -1119,6 +1206,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.9': resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} @@ -1131,6 +1224,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.9': resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} engines: {node: '>=18'} @@ -1143,6 +1242,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.9': resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} @@ -1155,6 +1260,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.25.9': resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} engines: {node: '>=18'} @@ -1167,6 +1278,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} @@ -1179,6 +1296,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.25.9': resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} @@ -1191,6 +1314,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} @@ -1203,6 +1332,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.25.9': resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} @@ -1215,6 +1350,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.9': resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} @@ -1227,6 +1368,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.9': resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} @@ -1239,6 +1386,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.9': resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} @@ -1251,6 +1404,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.9': resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} @@ -2184,6 +2343,21 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@testing-library/user-event@14.6.1': resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} @@ -3456,6 +3630,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} @@ -7094,156 +7273,234 @@ snapshots: '@esbuild/aix-ppc64@0.25.10': optional: true + '@esbuild/aix-ppc64@0.25.11': + optional: true + '@esbuild/aix-ppc64@0.25.9': optional: true '@esbuild/android-arm64@0.25.10': optional: true + '@esbuild/android-arm64@0.25.11': + optional: true + '@esbuild/android-arm64@0.25.9': optional: true '@esbuild/android-arm@0.25.10': optional: true + '@esbuild/android-arm@0.25.11': + optional: true + '@esbuild/android-arm@0.25.9': optional: true '@esbuild/android-x64@0.25.10': optional: true + '@esbuild/android-x64@0.25.11': + optional: true + '@esbuild/android-x64@0.25.9': optional: true '@esbuild/darwin-arm64@0.25.10': optional: true + '@esbuild/darwin-arm64@0.25.11': + optional: true + '@esbuild/darwin-arm64@0.25.9': optional: true '@esbuild/darwin-x64@0.25.10': optional: true + '@esbuild/darwin-x64@0.25.11': + optional: true + '@esbuild/darwin-x64@0.25.9': optional: true '@esbuild/freebsd-arm64@0.25.10': optional: true + '@esbuild/freebsd-arm64@0.25.11': + optional: true + '@esbuild/freebsd-arm64@0.25.9': optional: true '@esbuild/freebsd-x64@0.25.10': optional: true + '@esbuild/freebsd-x64@0.25.11': + optional: true + '@esbuild/freebsd-x64@0.25.9': optional: true '@esbuild/linux-arm64@0.25.10': optional: true + '@esbuild/linux-arm64@0.25.11': + optional: true + '@esbuild/linux-arm64@0.25.9': optional: true '@esbuild/linux-arm@0.25.10': optional: true + '@esbuild/linux-arm@0.25.11': + optional: true + '@esbuild/linux-arm@0.25.9': optional: true '@esbuild/linux-ia32@0.25.10': optional: true + '@esbuild/linux-ia32@0.25.11': + optional: true + '@esbuild/linux-ia32@0.25.9': optional: true '@esbuild/linux-loong64@0.25.10': optional: true + '@esbuild/linux-loong64@0.25.11': + optional: true + '@esbuild/linux-loong64@0.25.9': optional: true '@esbuild/linux-mips64el@0.25.10': optional: true + '@esbuild/linux-mips64el@0.25.11': + optional: true + '@esbuild/linux-mips64el@0.25.9': optional: true '@esbuild/linux-ppc64@0.25.10': optional: true + '@esbuild/linux-ppc64@0.25.11': + optional: true + '@esbuild/linux-ppc64@0.25.9': optional: true '@esbuild/linux-riscv64@0.25.10': optional: true + '@esbuild/linux-riscv64@0.25.11': + optional: true + '@esbuild/linux-riscv64@0.25.9': optional: true '@esbuild/linux-s390x@0.25.10': optional: true + '@esbuild/linux-s390x@0.25.11': + optional: true + '@esbuild/linux-s390x@0.25.9': optional: true '@esbuild/linux-x64@0.25.10': optional: true + '@esbuild/linux-x64@0.25.11': + optional: true + '@esbuild/linux-x64@0.25.9': optional: true '@esbuild/netbsd-arm64@0.25.10': optional: true + '@esbuild/netbsd-arm64@0.25.11': + optional: true + '@esbuild/netbsd-arm64@0.25.9': optional: true '@esbuild/netbsd-x64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.25.11': + optional: true + '@esbuild/netbsd-x64@0.25.9': optional: true '@esbuild/openbsd-arm64@0.25.10': optional: true + '@esbuild/openbsd-arm64@0.25.11': + optional: true + '@esbuild/openbsd-arm64@0.25.9': optional: true '@esbuild/openbsd-x64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.25.11': + optional: true + '@esbuild/openbsd-x64@0.25.9': optional: true '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/openharmony-arm64@0.25.11': + optional: true + '@esbuild/openharmony-arm64@0.25.9': optional: true '@esbuild/sunos-x64@0.25.10': optional: true + '@esbuild/sunos-x64@0.25.11': + optional: true + '@esbuild/sunos-x64@0.25.9': optional: true '@esbuild/win32-arm64@0.25.10': optional: true + '@esbuild/win32-arm64@0.25.11': + optional: true + '@esbuild/win32-arm64@0.25.9': optional: true '@esbuild/win32-ia32@0.25.10': optional: true + '@esbuild/win32-ia32@0.25.11': + optional: true + '@esbuild/win32-ia32@0.25.9': optional: true '@esbuild/win32-x64@0.25.10': optional: true + '@esbuild/win32-x64@0.25.11': + optional: true + '@esbuild/win32-x64@0.25.9': optional: true @@ -7988,6 +8245,16 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + '@testing-library/dom': 10.4.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.5 + '@types/react-dom': 19.1.5(@types/react@19.1.5) + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 @@ -9694,7 +9961,7 @@ snapshots: esbuild-plugin-inline-worker@0.1.1: dependencies: - esbuild: 0.25.10 + esbuild: 0.25.11 find-cache-dir: 3.3.2 esbuild-plugin-polyfill-node@0.3.0(esbuild@0.25.9): @@ -9743,6 +10010,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.10 '@esbuild/win32-x64': 0.25.10 + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9 @@ -13171,7 +13467,7 @@ snapshots: vite@7.1.4(@types/node@20.17.50)(jiti@2.6.0)(lightningcss@1.30.1)(sass-embedded@1.92.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0): dependencies: - esbuild: 0.25.10 + esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 From d7128580175e6f3049a6895b62ef369d4b88e099 Mon Sep 17 00:00:00 2001 From: Kavidu23 <10952412@students.plymouth.ac.uk> Date: Thu, 6 Nov 2025 13:21:52 +0530 Subject: [PATCH 2/3] test: switch to jsdom environment for React tests - Replaced Playwright browser testing config with Vitest jsdom environment - Removed unnecessary screenshots and Playwright setup - Simplified test configuration to match other packages and ensure CI compatibility --- packages/react/package.json | 1 - ...ccepts-a-config-object-without-breaking-1.png | Bin 2170 -> 0 bytes ...-default-values-when-context-is-missing-1.png | Bin 2170 -> 0 bytes ...urns-values-from-context-when-available-1.png | Bin 2170 -> 0 bytes ...rl-hook-handles-relative-afterSignInUrl-1.png | Bin 2170 -> 0 bytes ...overage-setValue-and-validate-on-change-1.png | Bin 2170 -> 0 bytes packages/react/vitest.config.ts | 10 +++------- pnpm-lock.yaml | 6 ------ 8 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png delete mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png delete mode 100644 packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png delete mode 100644 packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png delete mode 100644 packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png diff --git a/packages/react/package.json b/packages/react/package.json index 316dc1f5..f38b0e88 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -50,7 +50,6 @@ "@wso2/prettier-config": "catalog:", "esbuild-plugin-preserve-directives": "^0.0.11", "eslint": "8.57.0", - "playwright": "^1.52.0", "prettier": "^2.6.2", "react": "^19.1.0", "rimraf": "^6.0.1", diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-accepts-a-config-object-without-breaking-1.png deleted file mode 100644 index d561bf03e76f31b6ebd40213a020dea1a733702d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-default-values-when-context-is-missing-1.png deleted file mode 100644 index d561bf03e76f31b6ebd40213a020dea1a733702d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 diff --git a/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png b/packages/react/src/__test__/__screenshots__/useBranding.test.ts/useBranding-hook-returns-values-from-context-when-available-1.png deleted file mode 100644 index d561bf03e76f31b6ebd40213a020dea1a733702d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 diff --git a/packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png b/packages/react/src/__test__/__screenshots__/useBrowserUrl.test.ts/useBrowserUrl-hook-handles-relative-afterSignInUrl-1.png deleted file mode 100644 index d561bf03e76f31b6ebd40213a020dea1a733702d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 diff --git a/packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png b/packages/react/src/__test__/__screenshots__/useForm.test.ts/useForm-hook---full-coverage-setValue-and-validate-on-change-1.png deleted file mode 100644 index d561bf03e76f31b6ebd40213a020dea1a733702d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVDx2RV7kD;1QeOwv~@C&Vk{1FcVbv~PUa;81BZ#H zi(^Q|oVS-9c^MQ04j3HR|J<2XF*1tvch31UOqb5RXLw*a-+BJ|h3B7NcwYSCv*nkc zGQVp0YVF$XMukViWHdpH=7!OtV6=D~ts6#A#i*BhmSgU*$L63`27{-opUXO@geCx{ Cy9P!8 diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts index bfcf53bd..82f0fa7d 100644 --- a/packages/react/vitest.config.ts +++ b/packages/react/vitest.config.ts @@ -16,15 +16,11 @@ * under the License. */ -import {defineConfig} from 'vitest/config'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - browser: { - enabled: true, - headless: true, - instances: [{browser: 'chromium'}], - provider: 'playwright', - }, + environment: 'jsdom', }, }); + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d08e4d0f..9138da8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,9 +12,6 @@ catalogs: '@wso2/prettier-config': specifier: https://gitpkg.now.sh/brionmario/wso2-ui-configs/packages/prettier-config?a1fc6eb570653c999828aea9f5027cba06af4391 version: 0.1.0 - '@wso2/stylelint-config': - specifier: https://gitpkg.now.sh/brionmario/wso2-ui-configs/packages/stylelint-config?a1fc6eb570653c999828aea9f5027cba06af4391 - version: 0.1.0 importers: @@ -408,9 +405,6 @@ importers: eslint: specifier: 8.57.0 version: 8.57.0 - playwright: - specifier: ^1.52.0 - version: 1.52.0 prettier: specifier: ^2.6.2 version: 2.8.8 From 583d2605d8e7eed51553edd0e616701a1ae6389a Mon Sep 17 00:00:00 2001 From: Kavidu23 <10952412@students.plymouth.ac.uk> Date: Thu, 6 Nov 2025 13:25:41 +0530 Subject: [PATCH 3/3] test(react): migrate to jsdom test environment - replaced Playwright browser config with Vitest jsdom - removed unused screenshots and Playwright dependencies - aligned React package test setup with other modules for CI stability --- packages/react/vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts index 82f0fa7d..9b886b2a 100644 --- a/packages/react/vitest.config.ts +++ b/packages/react/vitest.config.ts @@ -20,7 +20,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - environment: 'jsdom', + environment: 'jsdom', //use jsdom environment for React testing }, });