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 ? ( -
-
- -
- - - {__('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.')} - - - - {__( - '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') + }) +})