diff --git a/src/components/app/IEDeprecationDialog.js b/src/components/app/IEDeprecationDialog.js
deleted file mode 100644
index fe8a1d0eca..0000000000
--- a/src/components/app/IEDeprecationDialog.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import PropTypes from 'prop-types'
-import { useSelector } from 'react-redux'
-
-import { AttentionFilled } from '@kyper/icon/AttentionFilled'
-import { Close } from '@kyper/icon/Close'
-import { Text } from '@mxenabled/mxui'
-import { useTokens } from '@kyper/tokenprovider'
-import { Button, Link } from '@mui/material'
-
-import { __ } from 'src/utilities/Intl'
-import { isIE } from 'src/utilities/Browser'
-import { PageviewInfo } from 'src/const/Analytics'
-
-export const IEDeprecationDialog = (props) => {
- const [showDialog, setShowDialog] = useState(true)
- const widgetProfile = useSelector((state) => state.profiles.widgetProfile)
- const tokens = useTokens()
- const styles = getStyles(tokens)
-
- useEffect(() => {
- if (isIE() && widgetProfile?.enable_ie_11_deprecation && showDialog) {
- props.onAnalyticPageview(PageviewInfo.CONNECT_IE_11_DEPRECATION[1])
- }
- }, [isIE(), widgetProfile?.enable_ie_11_deprecation && showDialog])
-
- return isIE() && widgetProfile?.enable_ie_11_deprecation && showDialog ? (
-
-
- setShowDialog(false)}
- style={styles.closeButton}
- variant="text"
- >
-
-
-
-
-
- {__('This browser is not supported')}
-
-
- {
- // --TR: Full String: "We no longer support Internet Explorer. You can continue, or switch to a supported browser, like Edge, Chrome, or Firefox, for a better experience."
- __(
- 'We no longer support Internet Explorer. You can continue, or switch to a supported browser, like ',
- )
- }
-
- {__('Edge')}
-
- {', '}
-
- {__('Chrome')}
-
- {', or '}
-
- {__('Firefox')}
-
- {', '}
- {__(' for a better experience.')}
-
-
setShowDialog(false)}
- style={styles.continueButton}
- variant="contained"
- >
- Continue
-
-
- {__(
- 'Clicking the links to supported browsers will take you to an external website with a different privacy policy, security measures, and terms and conditions.',
- )}
-
-
- ) : null
-}
-
-const getStyles = (tokens) => ({
- container: {
- background: tokens.BackgroundColor.Modal,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- padding: `0 ${tokens.Spacing.ContainerSidePadding}px`,
- position: 'fixed',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- maxWidth: '352px', // Our max content width (does not include side margin)
- minWidth: '270px', // Our min content width (does not include side margin)
- margin: '0 auto',
- },
- header: {
- position: 'absolute',
- display: 'flex',
- justifyContent: 'flex-end',
- width: '100%',
- },
- closeButton: {
- marginTop: tokens.Spacing.XSmall,
- },
- title: {
- textAlign: 'center',
- marginBottom: tokens.Spacing.Tiny,
- },
- paragraph: {
- textAlign: 'center',
- },
- continueButton: {
- marginTop: tokens.Spacing.XLarge,
- marginBottom: tokens.Spacing.Medium,
- },
- icon: {
- marginBottom: tokens.Spacing.Large,
- marginTop: tokens.Spacing.Jumbo,
- paddingTop: tokens.Spacing.Tiny,
- },
-})
-
-IEDeprecationDialog.propTypes = {
- onAnalyticPageview: PropTypes.func.isRequired,
-}
diff --git a/src/context/ApiContext-test.tsx b/src/context/ApiContext-test.tsx
new file mode 100644
index 0000000000..06a4dba027
--- /dev/null
+++ b/src/context/ApiContext-test.tsx
@@ -0,0 +1,104 @@
+import React from 'react'
+import { describe, it, expect, vi } from 'vitest'
+import { render, screen, waitFor } from 'src/utilities/testingLibrary'
+import { initialState } from 'src/services/mockedData'
+import { ApiProvider, useApi } from 'src/context/ApiContext'
+import { CreateMemberForm } from 'src/views/credentials/CreateMemberForm'
+import { apiValue as apiValueMock } from 'src/const/apiProviderMock'
+
+describe('ApiContext', () => {
+ const preloadedState = {
+ ...initialState,
+ connect: {
+ ...initialState.connect,
+ current_institution_guid: 'INS-123',
+ selectedInstitution: {
+ guid: 'INS-123',
+ code: 'mxbank',
+ name: 'MX Bank',
+ },
+ institutions: [
+ {
+ guid: 'INS-123',
+ code: 'mxbank',
+ name: 'MX Bank',
+ },
+ ],
+ },
+ }
+
+ const defaultProps = {
+ onError: () => {},
+ onSuccess: () => {},
+ }
+
+ it('provides API to child components', async () => {
+ const mockGetInstitutionCredentials = vi.fn().mockResolvedValue([
+ {
+ guid: 'CRD-1',
+ label: 'Username',
+ field_name: 'username',
+ field_type: 'TEXT',
+ },
+ ])
+
+ render(
+
+
+ ,
+ { preloadedState },
+ )
+
+ await waitFor(() => {
+ expect(mockGetInstitutionCredentials).toHaveBeenCalledWith('INS-123')
+ })
+
+ expect(screen.getByText('Username')).toBeInTheDocument()
+ })
+
+ it('allows custom API values to be provided', async () => {
+ const customGetInstitutionCredentials = vi.fn().mockResolvedValue([
+ {
+ guid: 'CRD-2',
+ label: 'Password',
+ field_name: 'password',
+ field_type: 'PASSWORD',
+ },
+ ])
+
+ render(
+
+
+ ,
+ { preloadedState },
+ )
+
+ await waitFor(() => {
+ expect(customGetInstitutionCredentials).toHaveBeenCalledWith('INS-123')
+ })
+
+ expect(screen.getByText('Password')).toBeInTheDocument()
+ })
+
+ it('provides default API values when used outside provider', () => {
+ const TestComponent = () => {
+ const { api } = useApi()
+ return (
+
+
{typeof api.loadMembers === 'function' ? 'yes' : 'no'}
+
+ )
+ }
+
+ render( , { preloadedState })
+
+ expect(screen.getByTestId('has-api')).toHaveTextContent('yes')
+ })
+})
diff --git a/src/context/ApiContext.tsx b/src/context/ApiContext.tsx
index 0be06eccb8..540f8affc0 100644
--- a/src/context/ApiContext.tsx
+++ b/src/context/ApiContext.tsx
@@ -141,9 +141,6 @@ const ApiProvider = ({ apiValue, children }: ApiProviderTypes) => {
const useApi = () => {
const context = React.useContext(ApiContext)
- if (context === undefined) {
- throw new Error('useApi must be used within a ApiProvider')
- }
return { api: context }
}
diff --git a/src/context/WebSocketContext-test.tsx b/src/context/WebSocketContext-test.tsx
new file mode 100644
index 0000000000..79c791d5fc
--- /dev/null
+++ b/src/context/WebSocketContext-test.tsx
@@ -0,0 +1,74 @@
+import React from 'react'
+import { renderHook } from '@testing-library/react'
+import { of } from 'rxjs'
+import { WebSocketProvider, useWebSocket, WebSocketConnection } from 'src/context/WebSocketContext'
+
+describe('WebSocketContext', () => {
+ it('should return undefined when no WebSocket connection is provided', () => {
+ const { result } = renderHook(() => useWebSocket(), {
+ wrapper: ({ children }) => {children} ,
+ })
+
+ expect(result.current).toBeUndefined()
+ })
+
+ it('should return the WebSocket connection when provided', () => {
+ const mockConnection: WebSocketConnection = {
+ isConnected: () => true,
+ webSocketMessages$: of({ type: 'test' }),
+ }
+
+ const { result } = renderHook(() => useWebSocket(), {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ })
+
+ expect(result.current).toBe(mockConnection)
+ expect(result.current?.isConnected()).toBe(true)
+ })
+
+ it('should allow accessing webSocketMessages$ observable', () => {
+ const mockConnection: WebSocketConnection = {
+ isConnected: () => false,
+ webSocketMessages$: of({ event: 'test', payload: { id: 123 } }),
+ }
+
+ const { result } = renderHook(() => useWebSocket(), {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ })
+
+ expect(result.current?.webSocketMessages$).toBeDefined()
+
+ let receivedMessage: unknown
+ result.current?.webSocketMessages$.subscribe((msg) => {
+ receivedMessage = msg
+ })
+
+ expect(receivedMessage).toEqual({ event: 'test', payload: { id: 123 } })
+ })
+
+ it('should provide the same connection to multiple consumers', () => {
+ const mockConnection: WebSocketConnection = {
+ isConnected: vi.fn(() => true),
+ webSocketMessages$: of({}),
+ }
+
+ const { result: result1 } = renderHook(() => useWebSocket(), {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ })
+
+ const { result: result2 } = renderHook(() => useWebSocket(), {
+ wrapper: ({ children }) => (
+ {children}
+ ),
+ })
+
+ expect(result1.current).toBe(mockConnection)
+ expect(result2.current).toBe(mockConnection)
+ })
+})
diff --git a/src/privacy/withProtection-test.tsx b/src/privacy/withProtection-test.tsx
new file mode 100644
index 0000000000..9dd2963d46
--- /dev/null
+++ b/src/privacy/withProtection-test.tsx
@@ -0,0 +1,62 @@
+import React from 'react'
+import { screen } from '@testing-library/react'
+import { describe, it, expect } from 'vitest'
+import { maskInputFn, withProtection } from 'src/privacy/withProtection'
+import { render } from 'src/utilities/testingLibrary'
+
+describe('maskInputFn', () => {
+ it('should mask input text by default', () => {
+ const result = maskInputFn('password123')
+ expect(result).toBe('***********')
+ })
+
+ it('should return original text when element has data-ph-unmask="true"', () => {
+ const element = document.createElement('input')
+ element.setAttribute('data-ph-unmask', 'true')
+ const result = maskInputFn('plainText123', element)
+ expect(result).toBe('plainText123')
+ })
+})
+
+describe('withProtection', () => {
+ it('should wrap component with ph-no-capture class by default', () => {
+ const TestComponent = ({ 'data-test': dataTest }: { 'data-test': string }) => (
+ Sensitive Content
+ )
+ const ProtectedComponent = withProtection(TestComponent)
+
+ render( )
+
+ const wrapper = document.querySelector('.ph-no-capture')
+ expect(wrapper).toBeTruthy()
+ expect(screen.getByTestId('test-component')).toHaveTextContent('Sensitive Content')
+ })
+
+ it('should not wrap component when allowCapture is true', () => {
+ const TestComponent = ({ 'data-test': dataTest }: { 'data-test': string }) => (
+ Public Content
+ )
+ const ProtectedComponent = withProtection(TestComponent)
+
+ render( )
+
+ const wrapper = document.querySelector('.ph-no-capture')
+ expect(wrapper).toBeNull()
+ expect(screen.getByTestId('test-component')).toHaveTextContent('Public Content')
+ })
+
+ it('should add data-ph-unmask attribute when allowCapture is true', () => {
+ const TestComponent = React.forwardRef<
+ HTMLInputElement,
+ { 'data-test': string; 'data-ph-unmask'?: boolean }
+ >((props, ref) => )
+ TestComponent.displayName = 'TestComponent'
+
+ const ProtectedComponent = withProtection(TestComponent)
+
+ render( )
+
+ const input = screen.getByTestId('test-input')
+ expect(input.getAttribute('data-ph-unmask')).toBe('true')
+ })
+})