Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avatar migration to vitest #2589

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 20 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
"@mui/material": "^6.1.6",
"@mui/private-theming": "^6.1.6",
"@mui/system": "^6.1.6",
"@mui/x-charts": "^7.22.1",
"@mui/x-charts": "^7.22.2",
"@mui/x-data-grid": "^7.22.1",
"@mui/x-date-pickers": "^7.22.1",
"@mui/x-date-pickers": "^7.18.0",
"@pdfme/generator": "^5.2.3",
"@pdfme/schemas": "^5.1.6",
"chart.js": "^4.4.6",
"@pdfme/generator": "^5.1.7",
"@reduxjs/toolkit": "^2.3.0",
"@vitejs/plugin-react": "^4.3.2",
"@vitejs/plugin-react": "^4.3.3",
"babel-plugin-transform-import-meta": "^2.2.1",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.6",
"customize-cra": "^1.0.0",
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
Expand All @@ -40,6 +40,7 @@
"i18next-http-backend": "^2.6.1",
"inquirer": "^8.0.0",
"js-cookie": "^3.0.1",
"lcov-result-merger": "^5.0.1",
"markdown-toc": "^1.2.0",
"prettier": "^3.3.3",
"prop-types": "^15.8.1",
Expand All @@ -66,13 +67,17 @@
"typescript": "^5.6.3",
"vite": "^5.4.8",
"vite-plugin-environment": "^1.1.3",
"vite-tsconfig-paths": "^5.1.2",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-tsconfig-paths": "^5.1.3",
"web-vitals": "^4.2.4"
},
"scripts": {
"serve": "cross-env ESLINT_NO_DEV_ERRORS=true vite --config config/vite.config.ts",
"build": "tsc && vite build --config config/vite.config.ts",
"preview": "vite preview --config config/vite.config.ts",
"test:vitest": "vitest run",
"test:vitest:watch": "vitest",
"test:vitest:coverage": "vitest run --coverage",
"test": "cross-env NODE_ENV=test jest --env=./scripts/custom-test-env.js --watchAll --coverage",
"eject": "react-scripts eject",
"lint:check": "eslint \"**/*.{ts,tsx}\" --max-warnings=0 && python .github/workflows/eslint_disable_check.py",
Expand Down Expand Up @@ -108,32 +113,33 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.25.4",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.7",
"@babel/preset-typescript": "^7.26.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^12.1.10",
"@types/inquirer": "^9.0.7",
"@types/jest": "^26.0.24",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.5.4",
"@types/node": "^22.9.0",
"@types/node-fetch": "^2.6.10",
"@types/react": "^18.3.3",
"@types/react": "^18.3.12",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-chartjs-2": "^2.5.7",
"@types/react-bootstrap": "^0.32.37",
"@types/react-chartjs-2": "^2.5.7",
"@types/react-datepicker": "^7.0.0",
"@types/react-dom": "^18.3.1",
"@types/react-google-recaptcha": "^2.1.9",
"@types/react-router-dom": "^5.1.8",
"@types/sanitize-html": "^2.13.0",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"@typescript-eslint/parser": "^8.5.0",
"@vitest/coverage-istanbul": "^2.1.5",
"babel-jest": "^29.7.0",
"cross-env": "^7.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.8.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.1",
Expand All @@ -146,9 +152,10 @@
"jest-preview": "^0.3.1",
"lint-staged": "^15.2.8",
"postcss-modules": "^6.0.0",
"sass": "^1.80.6",
"sass": "^1.80.7",
"tsx": "^4.19.1",
"vite-plugin-svgr": "^4.2.0",
"vitest": "^2.1.5",
"whatwg-fetch": "^3.6.20"
},
"resolutions": {
Expand Down
166 changes: 166 additions & 0 deletions src/components/Avatar/Avatar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/react-testing';
import { I18nextProvider } from 'react-i18next';
import '@testing-library/jest-dom';
import { describe, test, expect, vi } from 'vitest';
import { store } from 'state/store';
import Avatar from './Avatar';
import i18nForTest from 'utils/i18nForTest';
import { StaticMockLink } from 'utils/StaticMockLink';

// Setup mock for StaticMockLink
const link = new StaticMockLink([], true);

// Mock store and i18nForTest
vi.mock('state/store', () => ({
store: {
getState: vi.fn(() => ({
auth: {
user: null,
loading: false,
},
})),
subscribe: vi.fn(),
dispatch: vi.fn(),
},
}));

vi.mock('utils/i18nForTest', () => ({
__esModule: true,
default: vi.fn(() => ({
t: (key: string) => key,
})),
}));

// Test suite for Avatar component
describe('Testing Avatar component', () => {
// Test for rendering with name and alt attribute
test('should render with name and alt attribute', () => {
const testName = 'John Doe';
const testAlt = 'Test Alt Text';
const testSize = 64;

const { getByAltText } = render(
<MockedProvider addTypename={false} link={link}>
<BrowserRouter>
<Provider store={store}>
<I18nextProvider i18n={i18nForTest}>
<Avatar name={testName} alt={testAlt} size={testSize} />
</I18nextProvider>
</Provider>
</BrowserRouter>
</MockedProvider>,
);

const avatarElement = getByAltText(testAlt);
expect(avatarElement).toBeInTheDocument();
expect(avatarElement.getAttribute('src')).toBeDefined();
});

// Test for custom style and data-testid
test('should render with custom style and data-testid', () => {
const testName = 'Jane Doe';
const testAlt = 'Dummy Avatar'; // Default alt text
const testStyle = 'custom-avatar-style';
const testDataTestId = 'custom-avatar-test-id';

const { getByAltText } = render(
<MockedProvider addTypename={false} link={link}>
<BrowserRouter>
<Provider store={store}>
<I18nextProvider i18n={i18nForTest}>
<Avatar
name={testName}
avatarStyle={testStyle}
dataTestId={testDataTestId}
/>
</I18nextProvider>
</Provider>
</BrowserRouter>
</MockedProvider>,
);

const avatarElement = getByAltText(testAlt); // Expect 'Dummy Avatar' instead of 'Jane Doe'
expect(avatarElement).toBeInTheDocument();
expect(avatarElement.getAttribute('src')).toBeDefined();
expect(avatarElement.getAttribute('class')).toContain(testStyle);
expect(avatarElement.getAttribute('data-testid')).toBe(testDataTestId);
});

// Helper function for rendering Avatar component with props
const renderAvatar = (props = {}) => {
return render(
<MockedProvider addTypename={false} link={link}>
<BrowserRouter>
<Provider store={store}>
<I18nextProvider i18n={i18nForTest}>
<Avatar {...props} />
</I18nextProvider>
</Provider>
</BrowserRouter>
</MockedProvider>,
);
};

// Error Handling for Undefined Name
test('handles undefined name gracefully', () => {
renderAvatar({ name: undefined });

const avatarElement = screen.getByAltText('Dummy Avatar');
expect(avatarElement).toBeInTheDocument();
expect(avatarElement.getAttribute('src')).toContain('data:image/svg+xml');
});

// Valid Sizes Test
const validSizes = [32, 64, 128];
validSizes.forEach((size) => {
test(`accepts valid size ${size}`, () => {
renderAvatar({ name: 'Test User', size });

const avatarElement = screen.getByAltText('Dummy Avatar');
expect(avatarElement).toHaveAttribute('width', size.toString());
expect(avatarElement).toHaveAttribute('height', size.toString());
});
});

// Invalid Sizes Test
const invalidSizes = [0, -1, 257, 'string'];
invalidSizes.forEach((size) => {
test(`falls back to default size when invalid size ${size} is provided`, () => {
renderAvatar({ name: 'Test User', size });

const avatarElement = screen.getByAltText('Dummy Avatar');
expect(avatarElement).toHaveAttribute('width'); // Expect the fallback size of 128
expect(avatarElement).toHaveAttribute('height'); // Expect the fallback size of 128
});
});

// Custom URL Test
test('uses custom URL when provided', () => {
const customUrl = 'https://example.com/custom-avatar.png';

renderAvatar({
name: 'John Doe',
customUrl,
});

const avatarElement = screen.getByAltText('Dummy Avatar');
expect(avatarElement.getAttribute('src')).toBe(customUrl);
});

// Fallback to generated avatar when custom URL is invalid
test('falls back to generated avatar when custom URL is invalid', () => {
renderAvatar({
name: 'John Doe',
customUrl: '',
});

const avatarElement = screen.getByAltText('Dummy Avatar');
expect(avatarElement.getAttribute('src')).toContain('data:image/svg+xml');
});



});
18 changes: 16 additions & 2 deletions src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { initials } from '@dicebear/collection';
import styles from 'components/Avatar/Avatar.module.css';

interface InterfaceAvatarProps {
name: string;
name?: string;
NishantSinghhhhh marked this conversation as resolved.
Show resolved Hide resolved
alt?: string;
size?: number;
containerStyle?: string;
avatarStyle?: string;
dataTestId?: string;
radius?: number;
customUrl?: string;
}

/**
Expand All @@ -27,16 +28,27 @@ interface InterfaceAvatarProps {
* @returns JSX.Element - The rendered avatar image component.
*/
const Avatar = ({
name,
name = 'Guest',
alt = 'Dummy Avatar',
size,
avatarStyle,
containerStyle,
dataTestId,
radius,
customUrl,
}: InterfaceAvatarProps): JSX.Element => {

// Memoize the avatar creation to avoid unnecessary recalculations
const avatar = useMemo(() => {
if (customUrl) {
try {
new URL(customUrl);
return customUrl;
} catch (e) {
console.warn('Invalid custom URL provided to Avatar component');
}
}

return createAvatar(initials, {
size: size || 128,
seed: name,
Expand All @@ -53,6 +65,8 @@ const Avatar = ({
alt={alt}
className={avatarStyle ? avatarStyle : ''}
data-testid={dataTestId ? dataTestId : ''}
height={size || 128}
width={size || 128}
/>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "src/App.tsx", "setup.ts"]
"include": ["src", "src/App.tsx", "setup.ts"],
"exclude": ["node_modules", "dist", "vitest.config.ts"]
}
35 changes: 35 additions & 0 deletions vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
plugins: [
react(),
nodePolyfills({
include: ['events'],
}),
tsconfigPaths(),
],
test: {
include: ['src/**/*.spec.{js,jsx,ts,tsx}'],
globals: true,
environment: 'jsdom',
coverage: {
enabled: true,
provider: 'istanbul',
reportsDirectory: './coverage/vitest',
exclude: [
'node_modules',
'dist',
'**/*.{spec,test}.{js,jsx,ts,tsx}',
'coverage/**',
'**/index.{js,ts}',
'**/*.d.ts',
'src/test/**',
'vitest.config.ts',
],
reporter: ['text', 'html', 'text-summary', 'lcov'],
},
},
});
Loading