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/5] 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/5] 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/5] 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 }, }); From a0bf6369e2a4b2976f351c8a054fe375dd5accea Mon Sep 17 00:00:00 2001 From: Kavidu23 <10952412@students.plymouth.ac.uk> Date: Thu, 6 Nov 2025 18:36:26 +0530 Subject: [PATCH 4/5] style(react): fix indentation and formatting to comply with 2-space guideline --- .../react/src/__test__/useBranding.test.ts | 111 ++++--- .../react/src/__test__/useBrowserUrl.test.ts | 97 +++--- packages/react/src/__test__/useForm.test.ts | 301 +++++++++--------- .../react/src/__test__/useTranslation.test.ts | 219 +++++++------ 4 files changed, 362 insertions(+), 366 deletions(-) diff --git a/packages/react/src/__test__/useBranding.test.ts b/packages/react/src/__test__/useBranding.test.ts index 17006a7e..1f0a9dc0 100644 --- a/packages/react/src/__test__/useBranding.test.ts +++ b/packages/react/src/__test__/useBranding.test.ts @@ -15,79 +15,78 @@ * specific language governing permissions and limitations * under the License. */ -import { renderHook } from '@testing-library/react'; + +import {renderHook} from '@testing-library/react'; import useBranding from '../hooks/useBranding'; -import { vi, describe, it, expect, afterEach } from 'vitest'; +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() }; + 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'); + 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'); }); - it('returns default values when context is missing', async () => { - (useBrandingContext as unknown as ReturnType).mockImplementation(() => { - throw new Error('Provider missing'); - }); + const {result} = renderHook(() => useBranding()); - 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); - 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(); - await expect(result.current.fetchBranding()).resolves.toBeUndefined(); - await expect(result.current.refetch()).resolves.toBeUndefined(); + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('useBranding: BrandingProvider not available')); + }); - 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'); }); - 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 })); + const {result} = renderHook(() => useBranding({locale: 'en', autoFetch: true})); - expect(result.current.brandingPreference).toBeNull(); - expect(result.current.theme).toBeNull(); - expect(result.current.activeTheme).toBeNull(); - }); + 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 index 44192f7a..7390345f 100644 --- a/packages/react/src/__test__/useBrowserUrl.test.ts +++ b/packages/react/src/__test__/useBrowserUrl.test.ts @@ -15,63 +15,64 @@ * 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"; + +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(), +vi.mock('@asgardeo/browser', () => ({ + hasAuthParamsInUrl: vi.fn(), })); -describe("useBrowserUrl hook", () => { - const { hasAuthParams } = useBrowserUrl(); +describe('useBrowserUrl hook', () => { + const {hasAuthParams} = useBrowserUrl(); - beforeEach(() => { - vi.clearAllMocks(); - }); + 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 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 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 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); - }); + 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: 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 - }); + // 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 index 9c74132a..e043cfe5 100644 --- a/packages/react/src/__test__/useForm.test.ts +++ b/packages/react/src/__test__/useForm.test.ts @@ -15,170 +15,171 @@ * 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'; + +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; + 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); + 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); }); - 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(); - }); + expect(onSubmit).not.toHaveBeenCalled(); + expect(result.current.isSubmitted).toBe(true); + }); - it('bulk setValues', () => { - const { result } = renderHook(() => useForm(config)); + it('handleSubmit calls onSubmit if valid', async () => { + const {result} = renderHook(() => useForm(config)); + const onSubmit = vi.fn(); - act(() => result.current.setValues({ username: 'Bob', password: '123456' })); - expect(result.current.values.username).toBe('Bob'); - expect(result.current.values.password).toBe('123456'); + act(() => { + result.current.setValues({username: 'Alice', password: '123456', email: 'alice@example.com'}); }); - 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'); + await act(async () => { + await result.current.handleSubmit(onSubmit)(); }); - 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); - }); + expect(onSubmit).toHaveBeenCalledWith({username: 'Alice', password: '123456', email: 'alice@example.com'}); + }); - 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('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(); }); - 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); - }); + 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 index 791730bb..34369ce7 100644 --- a/packages/react/src/__test__/useTranslation.test.ts +++ b/packages/react/src/__test__/useTranslation.test.ts @@ -15,140 +15,135 @@ * 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"; + +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(), +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, - }, + 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, - }, + }, + 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 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", + 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 wrapper: React.FC<{children: React.ReactNode}> = ({children}) => + React.createElement(I18nContext.Provider, {value: contextValue}, children); const esContext: I18nContextValue = { - ...contextValue, - currentLanguage: "es", - t: createTFunction("es"), + ...contextValue, + currentLanguage: 'es', + t: createTFunction('es'), }; -const esWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => - React.createElement(I18nContext.Provider, { value: esContext }, children); +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/ - ); - }); +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/); + }); }); From 4be8ff8cf8963d36b39e966acfceec5345922a80 Mon Sep 17 00:00:00 2001 From: Kavidu23 <10952412@students.plymouth.ac.uk> Date: Thu, 6 Nov 2025 18:45:08 +0530 Subject: [PATCH 5/5] test(react): revert to Playwright provider and reinstall browser dependencies --- packages/react/package.json | 1 + ...hook-handles-relative-afterSignInUrl-1.png | Bin 0 -> 2170 bytes ...rage-setValue-and-validate-on-change-1.png | Bin 0 -> 2170 bytes packages/react/vitest.config.ts | 10 +- pnpm-lock.yaml | 276 +++++++++++++++++- 5 files changed, 283 insertions(+), 4 deletions(-) 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 diff --git a/packages/react/package.json b/packages/react/package.json index f38b0e88..316dc1f5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -50,6 +50,7 @@ "@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__/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/vitest.config.ts b/packages/react/vitest.config.ts index 9b886b2a..bfcf53bd 100644 --- a/packages/react/vitest.config.ts +++ b/packages/react/vitest.config.ts @@ -16,11 +16,15 @@ * under the License. */ -import { defineConfig } from 'vitest/config'; +import {defineConfig} from 'vitest/config'; export default defineConfig({ test: { - environment: 'jsdom', //use jsdom environment for React testing + browser: { + enabled: true, + headless: true, + instances: [{browser: 'chromium'}], + provider: 'playwright', + }, }, }); - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9138da8f..da1e223d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ 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: @@ -405,6 +408,9 @@ 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 @@ -954,6 +960,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -972,6 +984,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.9': resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} engines: {node: '>=18'} @@ -990,6 +1008,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.9': resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} engines: {node: '>=18'} @@ -1008,6 +1032,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.9': resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} engines: {node: '>=18'} @@ -1026,6 +1056,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.9': resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} engines: {node: '>=18'} @@ -1044,6 +1080,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.9': resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} engines: {node: '>=18'} @@ -1062,6 +1104,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.9': resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} engines: {node: '>=18'} @@ -1080,6 +1128,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.9': resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} engines: {node: '>=18'} @@ -1098,6 +1152,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.9': resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} engines: {node: '>=18'} @@ -1116,6 +1176,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.9': resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} engines: {node: '>=18'} @@ -1134,6 +1200,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.9': resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} engines: {node: '>=18'} @@ -1152,6 +1224,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.9': resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} engines: {node: '>=18'} @@ -1170,6 +1248,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.9': resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} engines: {node: '>=18'} @@ -1188,6 +1272,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.9': resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} engines: {node: '>=18'} @@ -1206,6 +1296,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.9': resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} engines: {node: '>=18'} @@ -1224,6 +1320,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + 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'} @@ -1242,6 +1344,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.9': resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} engines: {node: '>=18'} @@ -1260,6 +1368,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + 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'} @@ -1278,6 +1392,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.9': resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} engines: {node: '>=18'} @@ -1296,6 +1416,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.25.9': resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} engines: {node: '>=18'} @@ -1314,6 +1440,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.9': resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} engines: {node: '>=18'} @@ -1332,6 +1464,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.25.9': resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} engines: {node: '>=18'} @@ -1350,6 +1488,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.9': resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} engines: {node: '>=18'} @@ -1368,6 +1512,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.9': resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} engines: {node: '>=18'} @@ -1386,6 +1536,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.9': resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} engines: {node: '>=18'} @@ -1404,6 +1560,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.9': resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} engines: {node: '>=18'} @@ -3629,6 +3791,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} @@ -7270,6 +7437,9 @@ snapshots: '@esbuild/aix-ppc64@0.25.11': optional: true + '@esbuild/aix-ppc64@0.25.12': + optional: true + '@esbuild/aix-ppc64@0.25.9': optional: true @@ -7279,6 +7449,9 @@ snapshots: '@esbuild/android-arm64@0.25.11': optional: true + '@esbuild/android-arm64@0.25.12': + optional: true + '@esbuild/android-arm64@0.25.9': optional: true @@ -7288,6 +7461,9 @@ snapshots: '@esbuild/android-arm@0.25.11': optional: true + '@esbuild/android-arm@0.25.12': + optional: true + '@esbuild/android-arm@0.25.9': optional: true @@ -7297,6 +7473,9 @@ snapshots: '@esbuild/android-x64@0.25.11': optional: true + '@esbuild/android-x64@0.25.12': + optional: true + '@esbuild/android-x64@0.25.9': optional: true @@ -7306,6 +7485,9 @@ snapshots: '@esbuild/darwin-arm64@0.25.11': optional: true + '@esbuild/darwin-arm64@0.25.12': + optional: true + '@esbuild/darwin-arm64@0.25.9': optional: true @@ -7315,6 +7497,9 @@ snapshots: '@esbuild/darwin-x64@0.25.11': optional: true + '@esbuild/darwin-x64@0.25.12': + optional: true + '@esbuild/darwin-x64@0.25.9': optional: true @@ -7324,6 +7509,9 @@ snapshots: '@esbuild/freebsd-arm64@0.25.11': optional: true + '@esbuild/freebsd-arm64@0.25.12': + optional: true + '@esbuild/freebsd-arm64@0.25.9': optional: true @@ -7333,6 +7521,9 @@ snapshots: '@esbuild/freebsd-x64@0.25.11': optional: true + '@esbuild/freebsd-x64@0.25.12': + optional: true + '@esbuild/freebsd-x64@0.25.9': optional: true @@ -7342,6 +7533,9 @@ snapshots: '@esbuild/linux-arm64@0.25.11': optional: true + '@esbuild/linux-arm64@0.25.12': + optional: true + '@esbuild/linux-arm64@0.25.9': optional: true @@ -7351,6 +7545,9 @@ snapshots: '@esbuild/linux-arm@0.25.11': optional: true + '@esbuild/linux-arm@0.25.12': + optional: true + '@esbuild/linux-arm@0.25.9': optional: true @@ -7360,6 +7557,9 @@ snapshots: '@esbuild/linux-ia32@0.25.11': optional: true + '@esbuild/linux-ia32@0.25.12': + optional: true + '@esbuild/linux-ia32@0.25.9': optional: true @@ -7369,6 +7569,9 @@ snapshots: '@esbuild/linux-loong64@0.25.11': optional: true + '@esbuild/linux-loong64@0.25.12': + optional: true + '@esbuild/linux-loong64@0.25.9': optional: true @@ -7378,6 +7581,9 @@ snapshots: '@esbuild/linux-mips64el@0.25.11': optional: true + '@esbuild/linux-mips64el@0.25.12': + optional: true + '@esbuild/linux-mips64el@0.25.9': optional: true @@ -7387,6 +7593,9 @@ snapshots: '@esbuild/linux-ppc64@0.25.11': optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + '@esbuild/linux-ppc64@0.25.9': optional: true @@ -7396,6 +7605,9 @@ snapshots: '@esbuild/linux-riscv64@0.25.11': optional: true + '@esbuild/linux-riscv64@0.25.12': + optional: true + '@esbuild/linux-riscv64@0.25.9': optional: true @@ -7405,6 +7617,9 @@ snapshots: '@esbuild/linux-s390x@0.25.11': optional: true + '@esbuild/linux-s390x@0.25.12': + optional: true + '@esbuild/linux-s390x@0.25.9': optional: true @@ -7414,6 +7629,9 @@ snapshots: '@esbuild/linux-x64@0.25.11': optional: true + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/linux-x64@0.25.9': optional: true @@ -7423,6 +7641,9 @@ snapshots: '@esbuild/netbsd-arm64@0.25.11': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.25.9': optional: true @@ -7432,6 +7653,9 @@ snapshots: '@esbuild/netbsd-x64@0.25.11': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.25.9': optional: true @@ -7441,6 +7665,9 @@ snapshots: '@esbuild/openbsd-arm64@0.25.11': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.25.9': optional: true @@ -7450,6 +7677,9 @@ snapshots: '@esbuild/openbsd-x64@0.25.11': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.25.9': optional: true @@ -7459,6 +7689,9 @@ snapshots: '@esbuild/openharmony-arm64@0.25.11': optional: true + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.25.9': optional: true @@ -7468,6 +7701,9 @@ snapshots: '@esbuild/sunos-x64@0.25.11': optional: true + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.25.9': optional: true @@ -7477,6 +7713,9 @@ snapshots: '@esbuild/win32-arm64@0.25.11': optional: true + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.25.9': optional: true @@ -7486,6 +7725,9 @@ snapshots: '@esbuild/win32-ia32@0.25.11': optional: true + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-ia32@0.25.9': optional: true @@ -7495,6 +7737,9 @@ snapshots: '@esbuild/win32-x64@0.25.11': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.25.9': optional: true @@ -9955,7 +10200,7 @@ snapshots: esbuild-plugin-inline-worker@0.1.1: dependencies: - esbuild: 0.25.11 + esbuild: 0.25.12 find-cache-dir: 3.3.2 esbuild-plugin-polyfill-node@0.3.0(esbuild@0.25.9): @@ -10033,6 +10278,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.11 '@esbuild/win32-x64': 0.25.11 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9