diff --git a/package.json b/package.json index dcc7e4f..96208a8 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,9 @@ }, "jest": { "moduleNameMapper": { - "^@/(.*)$": "/src/$1" + "^@/(.*)$": "/src/$1", + "^@wso2/oxygen-ui$": "/src/__mocks__/@wso2/oxygen-ui.js", + "^@wso2/oxygen-ui-icons-react$": "/node_modules/@wso2/oxygen-ui-icons-react/dist/index.cjs" } } } diff --git a/src/__mocks__/@wso2/oxygen-ui.js b/src/__mocks__/@wso2/oxygen-ui.js new file mode 100644 index 0000000..c10f2a1 --- /dev/null +++ b/src/__mocks__/@wso2/oxygen-ui.js @@ -0,0 +1,30 @@ +/* + Copyright (c) 2026 WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + + 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. +*/ + +// @wso2/oxygen-ui ships an ESM-only bundle that Jest (CJS) can't parse. +// Proxy to @mui/material which oxygen-ui wraps and which has a CJS build. +const mui = require('@mui/material'); +const muiStyles = require('@mui/material/styles'); + +module.exports = { + ...mui, + ...muiStyles, + extendTheme: muiStyles.extendTheme || (() => ({})), + OxygenUIThemeProvider: mui.ThemeProvider, + useColorScheme: muiStyles.useColorScheme || (() => ({ mode: 'light', setMode: () => {} })), +}; diff --git a/src/components/ConnectorCard/ConnectorCard.test.tsx b/src/components/ConnectorCard/ConnectorCard.test.tsx index bc3d478..cf14503 100644 --- a/src/components/ConnectorCard/ConnectorCard.test.tsx +++ b/src/components/ConnectorCard/ConnectorCard.test.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import ConnectorCard from './ConnectorCard'; import { BallerinaPackage } from '@/types/connector'; @@ -34,24 +35,31 @@ const createMockConnector = (overrides: Partial = {}): Balleri ...overrides, }); +const renderCard = (props: Parameters[0]) => + render( + + + + ); + describe('ConnectorCard', () => { it('should render connector name correctly', () => { const connector = createMockConnector({ name: 'github' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); - expect(screen.getByText('github')).toBeInTheDocument(); + expect(screen.getByText('GitHub')).toBeInTheDocument(); }); it('should render connector version', () => { const connector = createMockConnector({ version: '2.5.1' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('2.5.1')).toBeInTheDocument(); }); it('should render connector summary', () => { const connector = createMockConnector({ summary: 'Test summary description' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('Test summary description')).toBeInTheDocument(); }); @@ -60,7 +68,7 @@ describe('ConnectorCard', () => { const connector = createMockConnector({ keywords: ['Area/Finance', 'Vendor/Stripe', 'Type/API'], }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('API')).toBeInTheDocument(); }); @@ -69,16 +77,16 @@ describe('ConnectorCard', () => { const connector = createMockConnector({ keywords: ['Area/Finance', 'Vendor/Stripe', 'Type/Connector'], }); - render(); + renderCard({ connector, effectiveMode: 'light' }); - expect(screen.getByText('Stripe')).toBeInTheDocument(); + expect(screen.getAllByText('Stripe').length).toBeGreaterThan(0); }); it('should not render vendor chip when "Other"', () => { const connector = createMockConnector({ keywords: ['Area/Finance', 'Vendor/Other', 'Type/Connector'], }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('Connector')).toBeInTheDocument(); expect(screen.getByText('Finance')).toBeInTheDocument(); @@ -88,21 +96,21 @@ describe('ConnectorCard', () => { const connector = createMockConnector({ keywords: ['Area/Communication', 'Vendor/Twilio', 'Type/Connector'], }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('Communication')).toBeInTheDocument(); }); it('should format pull count correctly', () => { const connector = createMockConnector({ totalPullCount: 1500000 }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('1.5M')).toBeInTheDocument(); }); it('should show "0" when pullCount is undefined', () => { const connector = createMockConnector({ totalPullCount: undefined }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('0')).toBeInTheDocument(); }); @@ -117,40 +125,40 @@ describe('ConnectorCard', () => { const yesterday = new Date(fixedNow); yesterday.setDate(yesterday.getDate() - 1); const connector = createMockConnector({ createdDate: yesterday.toISOString() }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('1 day ago')).toBeInTheDocument(); jest.useRealTimers(); }); - it('should have a link to connector URL', () => { + it('should have a link to connector detail page', () => { const connector = createMockConnector({ URL: 'packages/ballerinax/test/1.0.0' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); - const link = screen.getByRole('link', { name: /view/i }); - expect(link).toHaveAttribute('href', 'packages/ballerinax/test/1.0.0'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/connector/ballerinax/test/latest'); }); - it('should open link in new tab', () => { + it('should navigate via internal router link', () => { const connector = createMockConnector(); - render(); + renderCard({ connector, effectiveMode: 'light' }); - const link = screen.getByRole('link', { name: /view/i }); - expect(link).toHaveAttribute('target', '_blank'); + const link = screen.getByRole('link'); + expect(link).toBeInTheDocument(); }); it('should render avatar with first letter fallback', () => { const connector = createMockConnector({ name: 'testconnector', icon: '' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); expect(screen.getByText('T')).toBeInTheDocument(); }); it('should handle dot-separated names correctly', () => { const connector = createMockConnector({ name: 'aws.s3' }); - render(); + renderCard({ connector, effectiveMode: 'light' }); - expect(screen.getByText('s3')).toBeInTheDocument(); + expect(screen.getByText('AWS S3')).toBeInTheDocument(); }); }); diff --git a/src/components/Pagination/Pagination.test.tsx b/src/components/Pagination/Pagination.test.tsx index ae4de7e..2aa7092 100644 --- a/src/components/Pagination/Pagination.test.tsx +++ b/src/components/Pagination/Pagination.test.tsx @@ -129,8 +129,8 @@ describe('Pagination', () => { const select = screen.getByRole('combobox'); fireEvent.mouseDown(select); - // Click on 50 items option - const option50 = screen.getByRole('option', { name: '50 items' }); + // Click on 50 Items option + const option50 = screen.getByRole('option', { name: '50 Items' }); fireEvent.click(option50); expect(onPageSizeChange).toHaveBeenCalledWith(50); @@ -142,9 +142,9 @@ describe('Pagination', () => { const select = screen.getByRole('combobox'); fireEvent.mouseDown(select); - expect(screen.getByRole('option', { name: '5 items' })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: '10 items' })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: '15 items' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: '5 Items' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: '10 Items' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: '15 Items' })).toBeInTheDocument(); }); it('should handle single page correctly', () => { @@ -162,6 +162,6 @@ describe('Pagination', () => { render(); // Should show correct range even with zero items - expect(screen.getByText(/Showing 1-0 of 0/)).toBeInTheDocument(); + expect(screen.getByText(/Showing 0-0 of 0/)).toBeInTheDocument(); }); }); diff --git a/src/components/SearchBar/SearchBar.test.tsx b/src/components/SearchBar/SearchBar.test.tsx index a9b0d4f..ee66a50 100644 --- a/src/components/SearchBar/SearchBar.test.tsx +++ b/src/components/SearchBar/SearchBar.test.tsx @@ -77,10 +77,9 @@ describe('SearchBar', () => { it('should render search icon', () => { const onChange = jest.fn(); - render(); + const { container } = render(); - // MUI renders the icon with a data-testid or we can check for svg - const searchIcon = document.querySelector('[data-testid="SearchIcon"]'); + const searchIcon = container.querySelector('svg'); expect(searchIcon).toBeInTheDocument(); }); }); diff --git a/src/setupTests.ts b/src/setupTests.ts index f513a0b..232220d 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -17,6 +17,14 @@ */ import '@testing-library/jest-dom'; +import { TextEncoder, TextDecoder } from 'util'; +Object.assign(global, { TextEncoder, TextDecoder }); + +global.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +}; // Mock localStorage const localStorageMock = {