From 67f7863e9dcebfb6d5bfbb6c9c3e223a286975a3 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Thu, 7 Aug 2025 02:17:24 +0530 Subject: [PATCH 1/9] Added comprehensive unit tests for AnchorTitle Component --- .../unit/components/AnchorTitle.test.tsx | 408 ++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 frontend/__tests__/unit/components/AnchorTitle.test.tsx diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx new file mode 100644 index 0000000000..3547fe59e4 --- /dev/null +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -0,0 +1,408 @@ +import React from "react"; +import { screen, render, fireEvent, waitFor } from "@testing-library/react"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faLink } from "@fortawesome/free-solid-svg-icons"; +import AnchorTitle from "components/AnchorTitle"; + +library.add(faLink) + +jest.mock('utils/slugify', () => ({ + __esModule: true, + default: jest.fn((str: string) => str.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')) +})) + +describe('AnchorTitle Component', () => { + afterEach(() => { + jest.restoreAllMocks() + window.history.replaceState(null, '', window.location.pathname) + }) + + describe('Basic Rendering', () => { + it('renders without crashing', () => { + render() + expect(screen.getByText('Test Title')).toBeInTheDocument() + }) + + it('displays the correct title text', () => { + const testTitle = 'Sample Heading' + render() + expect(screen.getByText(testTitle)).toBeInTheDocument() + }) + + it('renders with correct HTML structure', () => { + render() + + const container = screen.getByText('Test').closest('div') + expect(container).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') + + const groupContainer = screen.getByText('Test').closest('.group') + expect(groupContainer).toHaveClass('group', 'relative', 'flex', 'items-center') + + const titleElement = screen.getByText('Test') + expect(titleElement).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') + expect(titleElement).toHaveAttribute('id', 'anchor-title') + }) + + it('renders FontAwesome link icon', () => { + render() + const link = screen.getByRole('link') + const icon = link.querySelector('svg') + expect(icon).toBeInTheDocument() + expect(link).toHaveClass('inherit-color', 'ml-2', 'opacity-0', 'transition-opacity', 'duration-200', 'group-hover:opacity-100') + }) + }) + + describe('Prop-Based Behaviour', () => { + it('generates correct ID and href from title using slugify', () => { + const testTitle = 'Test title with spaces' + render() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '#test-title-with-spaces') + + const element = document.getElementById('test-title-with-spaces') + expect(element).toBeInTheDocument() + }) + + it('handles different title formats correctly', () => { + const titles = [ + 'Simple Title', + 'Test with Numbers 123', + 'Title-with-hyphens', + 'Title_with_underscores' + ] + + titles.forEach(title => { + const { unmount } = render() + const link = screen.getByRole('link') + expect(link.getAttribute('href')).toMatch(/^#[a-z0-9-]+$/) + unmount() + }) + }) + + it('applies className prop when provided', () => { + const customClass = 'custom-class' + + render() + const container = screen.getByText('Test').closest('div') + expect(container).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') + }) + }) + + describe('Event Handling', () => { + let mockScrollTo: jest.SpyInstance + let mockPushState: jest.SpyInstance + let mockGetBoundingClientRect: jest.SpyInstance + let mockGetElementById: jest.SpyInstance + let mockRequestAnimationFrame: jest.SpyInstance + + beforeEach(() => { + mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() + mockRequestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { + cb(0) + return 0 + }) + + mockGetBoundingClientRect = jest.fn(() => ({ + top: 100, + height: 50, + width: 200, + bottom: 150, + left: 0, + right: 200, + x: 0, + y: 100, + toJSON: () => {} + })) + + const mockElement = { + getBoundingClientRect: mockGetBoundingClientRect, + querySelector: jest.fn(() => ({ + offsetHeight: 30 + })) + } + mockGetElementById = jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + }) + + it('prevents default behaviour on link click', () => { + render() + const link = screen.getByRole('link') + + const mockPreventDefault = jest.fn() + const clickEvent = new MouseEvent('click', { bubbles: true }) + Object.defineProperty(clickEvent, 'preventDefault', { value: mockPreventDefault }) + + fireEvent(link, clickEvent) + expect(mockPreventDefault).toHaveBeenCalled() + }) + + it('calls scrollTo with correct parameters on click', () => { + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + + expect(mockScrollTo).toHaveBeenCalledWith({ + "behavior": "smooth", + "top": 20, + }) + }) + + it('updates browser history on click', () => { + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + + expect(mockPushState).toHaveBeenCalledWith(null, '', '#history-test') + }) + + it('calculates scroll position correctly with heading offset', () => { + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + + expect(mockScrollTo).toHaveBeenCalledWith({ + top: 20, + behavior: 'smooth' + }) + }) + }) + + describe('useEffect Behaviour', () => { + let mockScrollTo: jest.SpyInstance + let mockGetElementById: jest.SpyInstance + let mockRequestAnimationFrame: jest.SpyInstance + + beforeEach(() => { + mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + mockRequestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { + cb(0) + return 0 + }) + + const mockElement = { + getBoundingClientRect: jest.fn(() => ({ + top: 100, + height: 50, + width: 200, + bottom: 150, + left: 0, + right: 200, + x: 0, + y: 100, + toJSON: () => {} + })), + querySelector: jest.fn(() => ({ + offsetHeight: 30 + })) + } + mockGetElementById = jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + }) + + it('scrolls to element on mount when hash matches', async () => { + window.location.hash = '#test-scroll' + + render() + + await waitFor(() => { + expect(mockRequestAnimationFrame).toHaveBeenCalled() + expect(mockScrollTo).toHaveBeenCalledWith({ + top: 20, + behavior: 'smooth' + }) + }) + }) + + it('scrolls to an element on mount when hash matches', async () => { + window.location.hash = '#test-scroll' + + render() + + await waitFor(() => { + expect(mockRequestAnimationFrame).toHaveBeenCalled() + expect(mockScrollTo).toHaveBeenCalledWith({ + top: 20, + behavior: 'smooth' + }) + }) + }) + + it('does not scroll when hash does not match', () => { + window.location.hash = '#different-hash' + + render() + + expect(mockScrollTo).not.toHaveBeenCalled() + }) + it('handles popstate events correctly', async () => { + render() + + window.location.hash = '#popstate-test' + + const popstateEvent = new PopStateEvent('popstate') + fireEvent(window, popstateEvent) + + await waitFor(() => { + expect(mockRequestAnimationFrame).toHaveBeenCalled() + expect(mockScrollTo).toHaveBeenCalled() + }) + }) + + it('removes popstate event listener on unmount', () => { + const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') + + const { unmount } = render() + unmount() + + expect(mockRemoveEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) + }) + }) + + describe('Edge Cases and Error Handling', () => { + it('handles empty title gracefully', () => { + render() + + const container = document.querySelector('div[id=""]') + expect(container).toBeInTheDocument() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '#') + }) + + it('handles special character in title', () => { + const specialTitle = 'Title with @#$%^&*()' + render() + + expect(screen.getByText(specialTitle)).toBeInTheDocument() + const link = screen.getByRole('link') + expect(link.getAttribute('href')).toMatch(/^#[a-z0-9-]*$/) + }) + + it('handles very long titles', () => { + const longTitle = 'This is a very long title that might cause issues with ID generation and should be handled gracefully by the component' + render() + + expect(screen.getByText(longTitle)).toBeInTheDocument() + const link = screen.getByRole('link') + expect(link.getAttribute('href')).toBeDefined() + }) + + it('handles missing DOM element gracefully', () => { + jest.spyOn(document, 'getElementById').mockReturnValue(null) + jest.spyOn(console, 'error').mockImplementation(() => {}) + + render() + const link = screen.getByRole('link') + + expect(() => fireEvent.click(link)).not.toThrow() + + jest.restoreAllMocks() + }) + + it('handles missing anchor-title element in scroll calculation', () => { + const mockElement = { + getBoundingClientRect: jest.fn(() => ({ + top: 100, height: 50, width: 200, bottom: 150, + left: 0, right: 200, x: 0, y: 100, toJSON: () => {} + })), + querySelector: jest.fn(() => null) + } + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + jest.spyOn(window, 'scrollTo').mockImplementation() + + render() + const link = screen.getByRole('link') + + expect(() => fireEvent.click(link)).not.toThrow() + }) + }) + + describe('Accessibility', () => { + it('has proper link role and attributes', () => { + render() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '#accessibility-test') + expect(link).toBeInTheDocument() + }) + + it('provides proper heading structure', () => { + render() + + const titleElement = screen.getByText('Heading Test') + expect(titleElement).toHaveAttribute('id', 'anchor-title') + expect(titleElement).toHaveClass('text-2xl', 'font-semibold') + }) + + it('maintains focus management on interaction', () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() + + render() + + const link = screen.getByRole('link') + link.focus() + expect(document.activeElement).toBe(link) + + fireEvent.click(link) + + expect(document.activeElement).toBeDefined() + + mockScrollTo.mockRestore() + mockPushState.mockRestore() + }) + }) + + describe('Integration Tests', () => { + it('works correctly with multiple instances', () => { + render( +
+ + +
+ ) + + expect(screen.getByText('First Title')).toBeInTheDocument() + expect(screen.getByText('Second Title')).toBeInTheDocument() + + const links = screen.getAllByRole('link') + expect(links).toHaveLength(2) + expect(links[0]).toHaveAttribute('href', '#first-title') + expect(links[1]).toHaveAttribute('href', '#second-title') + }) + + it('handles rapid successive clicks', () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() + + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + fireEvent.click(link) + fireEvent.click(link) + + expect(mockScrollTo).toHaveBeenCalledTimes(3) + expect(mockPushState).toHaveBeenCalledTimes(3) + }) + + it('updates correctly when title prop changes', () => { + const { rerender } = render() + expect(screen.getByText('Original Title')).toBeInTheDocument() + + rerender() + expect(screen.getByText('Updated Title')).toBeInTheDocument() + expect(screen.queryByText('Original Title')).not.toBeInTheDocument() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '#updated-title') + }) + }) + + + + +}) From db2a6b404c47aba3b2cf348453742e43a463533a Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Thu, 7 Aug 2025 02:46:17 +0530 Subject: [PATCH 2/9] test: added comprehensive unit tests for AnchorTitle component - Added 40+ test cases covering rendering, props, events, and edge cases - Test scroll behavior, URL hash handling, and browser history updates - Include accessibility, performance, and integration test coverage - Mock external dependencies (slugify, FontAwesome, browser APIs) - Verify proper cleanup of event listeners and focus management --- .../unit/components/AnchorTitle.test.tsx | 212 +++++++++++++++++- 1 file changed, 208 insertions(+), 4 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 3547fe59e4..39006354c1 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -401,8 +401,212 @@ describe('AnchorTitle Component', () => { expect(link).toHaveAttribute('href', '#updated-title') }) }) - - - - + + describe('Browser API Interactions', () => { + it('handles window.pageYOffset correctly', () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + + Object.defineProperty(window, 'pageYOffset', { + value: 500, + configurable: true + }) + + const mockElement = { + getBoundingClientRect: jest.fn(() => ({ top: 100 })), + querySelector: jest.fn(() => ({ offsetHeight: 30 })) + } + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + + expect(mockScrollTo).toHaveBeenCalledWith({ + top: 520, + behavior: 'smooth' + }) + }) + + it('handles hash changes in the URL correctly', async () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { + cb(0) + return 0 + }) + + window.location.hash = '' + render() + + window.location.hash = '#hash-test' + const popstateEvent = new PopStateEvent('popstate') + fireEvent(window, popstateEvent) + + await waitFor(() => { + expect(mockScrollTo).toHaveBeenCalled() + }) + }) + }) + + describe('Performance and Optimization', () => { + it('uses useCallback for scrollToElement function', () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + + const { rerender } = render() + const link1 = screen.getByRole('link') + + fireEvent.click(link1) + + rerender() + const link2 = screen.getByRole('link') + + fireEvent.click(link2) + + expect(mockScrollTo).toHaveBeenCalledTimes(2) + }) + + it('cleans up event listeners properly', () => { + const mockAddEventListener = jest.spyOn(window, 'addEventListener') + const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') + + const { unmount } = render() + + expect(mockAddEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) + + unmount() + + expect(mockRemoveEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) + }) + }) + + describe('State Changes and Internal Logic', () => { + it('recalculates scroll position when element dimensions change', () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + + let offsetHeight = 30 + const mockElement = { + getBoundingClientRect: jest.fn(() => ({ top: 100 })), + querySelector: jest.fn(() => ({ offsetHeight })) + } + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + + render() + const link = screen.getByRole('link') + + fireEvent.click(link) + const firstCall = (mockScrollTo.mock.calls[0][0] as unknown as { top: number }).top + + offsetHeight = 60 + fireEvent.click(link) + const secondCall = (mockScrollTo.mock.calls[1][0] as unknown as { top: number }).top + + expect(firstCall).not.toBe(secondCall) + expect(Math.abs(firstCall - secondCall)).toBe(30) + }) + it('handles component rerender with different IDs', () => { + const mockAddEventListener = jest.spyOn(window, 'addEventListener') + const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') + + const { rerender } = render() + const originalAddCalls = mockAddEventListener.mock.calls.length + + rerender() + + expect(mockRemoveEventListener).toHaveBeenCalled() + expect(mockAddEventListener.mock.calls.length).toBeGreaterThan(originalAddCalls) + }) + }) + + describe('CSS and Styling', () => { + it('applies correct CSS classes for visual behaviour', () => { + render() + + const link = screen.getByRole('link') + expect(link).toHaveClass( + 'inherit-color', + 'ml-2', + 'opacity-0', + 'transition-opacity', + 'duration-200', + 'group-hover:opacity-100' + ) + }) + + it('has correct icon styling', () => { + render() + + const icon = screen.getByRole('link').querySelector('svg') + expect(icon).toHaveClass('custom-icon', 'h-7', 'w-5') + }) + + it('maintains proper layout structure', () => { + render() + + const titleContainer = screen.getByText('Layout Test').closest('div') + expect(titleContainer).toHaveAttribute('id', 'anchor-title') + expect(titleContainer).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') + + const groupContainer = titleContainer?.closest('.group') + expect(groupContainer).toHaveClass('group', 'relative', 'flex', 'items-center') + }) + }) + + describe('Dependencies and Mocking', () => { + it('uses slugify utility correctly', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const slugifyMock = require('utils/slugify').default + + render() + + expect(slugifyMock).toHaveBeenCalledWith('Slugify Test') + }) + + it('handles slugify returning different formats', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const slugifyMock = require('utils/slugify').default + slugifyMock.mockReturnValue('custom-slug-format') + + render() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '#custom-slug-format') + + const element = document.getElementById('custom-slug-format') + expect(element).toBeInTheDocument() + }) + }) + + describe('Comprehensive User Interactions', () => { + it('complete user journey: render → hover → click → navigate', async () => { + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() + + render() + + const titleElement = screen.getByText('User Journey') + expect(titleElement).toBeInTheDocument() + + const link = screen.getByRole('link') + expect(link).toHaveClass('opacity-0') + + fireEvent.click(link) + + expect(mockScrollTo).toHaveBeenCalledWith({ + top: expect.any(Number), + behavior: 'smooth' + }) + expect(mockPushState).toHaveBeenCalledWith(null, '', '#custom-slug-format') + }) + + it('handles keyboard navigation', () => { + render() + + const link = screen.getByRole('link') + + link.focus() + expect(document.activeElement).toBe(link) + + fireEvent.keyDown(link, { key: 'Enter', code: 'Enter' }) + }) + }) }) From 47cb583e2ebccb05c6ef42dd503a0820a78c1116 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Thu, 7 Aug 2025 02:50:17 +0530 Subject: [PATCH 3/9] Fixed eslint errors --- .../unit/components/AnchorTitle.test.tsx | 227 ++++++++++-------- 1 file changed, 128 insertions(+), 99 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 39006354c1..ccf26651ce 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -1,14 +1,19 @@ -import React from "react"; -import { screen, render, fireEvent, waitFor } from "@testing-library/react"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faLink } from "@fortawesome/free-solid-svg-icons"; -import AnchorTitle from "components/AnchorTitle"; +import { library } from '@fortawesome/fontawesome-svg-core' +import { faLink } from '@fortawesome/free-solid-svg-icons' +import { screen, render, fireEvent, waitFor } from '@testing-library/react' +import AnchorTitle from 'components/AnchorTitle' library.add(faLink) jest.mock('utils/slugify', () => ({ __esModule: true, - default: jest.fn((str: string) => str.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')) + default: jest.fn((str: string) => + str + .toLowerCase() + .replace(/[^a-z0-9]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + ), })) describe('AnchorTitle Component', () => { @@ -48,7 +53,14 @@ describe('AnchorTitle Component', () => { const link = screen.getByRole('link') const icon = link.querySelector('svg') expect(icon).toBeInTheDocument() - expect(link).toHaveClass('inherit-color', 'ml-2', 'opacity-0', 'transition-opacity', 'duration-200', 'group-hover:opacity-100') + expect(link).toHaveClass( + 'inherit-color', + 'ml-2', + 'opacity-0', + 'transition-opacity', + 'duration-200', + 'group-hover:opacity-100' + ) }) }) @@ -69,10 +81,10 @@ describe('AnchorTitle Component', () => { 'Simple Title', 'Test with Numbers 123', 'Title-with-hyphens', - 'Title_with_underscores' + 'Title_with_underscores', ] - titles.forEach(title => { + titles.forEach((title) => { const { unmount } = render() const link = screen.getByRole('link') expect(link.getAttribute('href')).toMatch(/^#[a-z0-9-]+$/) @@ -81,8 +93,6 @@ describe('AnchorTitle Component', () => { }) it('applies className prop when provided', () => { - const customClass = 'custom-class' - render() const container = screen.getByText('Test').closest('div') expect(container).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') @@ -93,16 +103,20 @@ describe('AnchorTitle Component', () => { let mockScrollTo: jest.SpyInstance let mockPushState: jest.SpyInstance let mockGetBoundingClientRect: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockRequestAnimationFrame: jest.SpyInstance beforeEach(() => { mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() - mockRequestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { - cb(0) - return 0 - }) + mockRequestAnimationFrame = jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb) => { + cb(0) + return 0 + }) mockGetBoundingClientRect = jest.fn(() => ({ top: 100, @@ -113,16 +127,18 @@ describe('AnchorTitle Component', () => { right: 200, x: 0, y: 100, - toJSON: () => {} + toJSON: () => {}, })) const mockElement = { getBoundingClientRect: mockGetBoundingClientRect, querySelector: jest.fn(() => ({ - offsetHeight: 30 - })) + offsetHeight: 30, + })), } - mockGetElementById = jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + mockGetElementById = jest + .spyOn(document, 'getElementById') + .mockReturnValue(mockElement as never) }) it('prevents default behaviour on link click', () => { @@ -144,8 +160,8 @@ describe('AnchorTitle Component', () => { fireEvent.click(link) expect(mockScrollTo).toHaveBeenCalledWith({ - "behavior": "smooth", - "top": 20, + behavior: 'smooth', + top: 20, }) }) @@ -166,22 +182,25 @@ describe('AnchorTitle Component', () => { expect(mockScrollTo).toHaveBeenCalledWith({ top: 20, - behavior: 'smooth' + behavior: 'smooth', }) }) }) describe('useEffect Behaviour', () => { let mockScrollTo: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance beforeEach(() => { mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - mockRequestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { - cb(0) - return 0 - }) + mockRequestAnimationFrame = jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb) => { + cb(0) + return 0 + }) const mockElement = { getBoundingClientRect: jest.fn(() => ({ @@ -193,13 +212,15 @@ describe('AnchorTitle Component', () => { right: 200, x: 0, y: 100, - toJSON: () => {} + toJSON: () => {}, })), querySelector: jest.fn(() => ({ - offsetHeight: 30 - })) + offsetHeight: 30, + })), } - mockGetElementById = jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + mockGetElementById = jest + .spyOn(document, 'getElementById') + .mockReturnValue(mockElement as never) }) it('scrolls to element on mount when hash matches', async () => { @@ -211,7 +232,7 @@ describe('AnchorTitle Component', () => { expect(mockRequestAnimationFrame).toHaveBeenCalled() expect(mockScrollTo).toHaveBeenCalledWith({ top: 20, - behavior: 'smooth' + behavior: 'smooth', }) }) }) @@ -225,7 +246,7 @@ describe('AnchorTitle Component', () => { expect(mockRequestAnimationFrame).toHaveBeenCalled() expect(mockScrollTo).toHaveBeenCalledWith({ top: 20, - behavior: 'smooth' + behavior: 'smooth', }) }) }) @@ -239,12 +260,12 @@ describe('AnchorTitle Component', () => { }) it('handles popstate events correctly', async () => { render() - + window.location.hash = '#popstate-test' - + const popstateEvent = new PopStateEvent('popstate') fireEvent(window, popstateEvent) - + await waitFor(() => { expect(mockRequestAnimationFrame).toHaveBeenCalled() expect(mockScrollTo).toHaveBeenCalled() @@ -258,16 +279,16 @@ describe('AnchorTitle Component', () => { unmount() expect(mockRemoveEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) - }) + }) }) - + describe('Edge Cases and Error Handling', () => { it('handles empty title gracefully', () => { render() - + const container = document.querySelector('div[id=""]') expect(container).toBeInTheDocument() - + const link = screen.getByRole('link') expect(link).toHaveAttribute('href', '#') }) @@ -282,9 +303,10 @@ describe('AnchorTitle Component', () => { }) it('handles very long titles', () => { - const longTitle = 'This is a very long title that might cause issues with ID generation and should be handled gracefully by the component' + const longTitle = + 'This is a very long title that might cause issues with ID generation and should be handled gracefully by the component' render() - + expect(screen.getByText(longTitle)).toBeInTheDocument() const link = screen.getByRole('link') expect(link.getAttribute('href')).toBeDefined() @@ -305,12 +327,19 @@ describe('AnchorTitle Component', () => { it('handles missing anchor-title element in scroll calculation', () => { const mockElement = { getBoundingClientRect: jest.fn(() => ({ - top: 100, height: 50, width: 200, bottom: 150, - left: 0, right: 200, x: 0, y: 100, toJSON: () => {} + top: 100, + height: 50, + width: 200, + bottom: 150, + left: 0, + right: 200, + x: 0, + y: 100, + toJSON: () => {}, })), - querySelector: jest.fn(() => null) + querySelector: jest.fn(() => null), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) jest.spyOn(window, 'scrollTo').mockImplementation() render() @@ -331,7 +360,7 @@ describe('AnchorTitle Component', () => { it('provides proper heading structure', () => { render() - + const titleElement = screen.getByText('Heading Test') expect(titleElement).toHaveAttribute('id', 'anchor-title') expect(titleElement).toHaveClass('text-2xl', 'font-semibold') @@ -340,17 +369,17 @@ describe('AnchorTitle Component', () => { it('maintains focus management on interaction', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() - + render() - + const link = screen.getByRole('link') link.focus() expect(document.activeElement).toBe(link) - + fireEvent.click(link) - + expect(document.activeElement).toBeDefined() - + mockScrollTo.mockRestore() mockPushState.mockRestore() }) @@ -392,11 +421,11 @@ describe('AnchorTitle Component', () => { it('updates correctly when title prop changes', () => { const { rerender } = render() expect(screen.getByText('Original Title')).toBeInTheDocument() - + rerender() expect(screen.getByText('Updated Title')).toBeInTheDocument() expect(screen.queryByText('Original Title')).not.toBeInTheDocument() - + const link = screen.getByRole('link') expect(link).toHaveAttribute('href', '#updated-title') }) @@ -405,26 +434,26 @@ describe('AnchorTitle Component', () => { describe('Browser API Interactions', () => { it('handles window.pageYOffset correctly', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - + Object.defineProperty(window, 'pageYOffset', { value: 500, - configurable: true + configurable: true, }) - + const mockElement = { getBoundingClientRect: jest.fn(() => ({ top: 100 })), - querySelector: jest.fn(() => ({ offsetHeight: 30 })) + querySelector: jest.fn(() => ({ offsetHeight: 30 })), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) - + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) + render() const link = screen.getByRole('link') - + fireEvent.click(link) expect(mockScrollTo).toHaveBeenCalledWith({ top: 520, - behavior: 'smooth' + behavior: 'smooth', }) }) @@ -441,7 +470,7 @@ describe('AnchorTitle Component', () => { window.location.hash = '#hash-test' const popstateEvent = new PopStateEvent('popstate') fireEvent(window, popstateEvent) - + await waitFor(() => { expect(mockScrollTo).toHaveBeenCalled() }) @@ -451,30 +480,30 @@ describe('AnchorTitle Component', () => { describe('Performance and Optimization', () => { it('uses useCallback for scrollToElement function', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - + const { rerender } = render() const link1 = screen.getByRole('link') - + fireEvent.click(link1) - + rerender() const link2 = screen.getByRole('link') - + fireEvent.click(link2) - + expect(mockScrollTo).toHaveBeenCalledTimes(2) }) it('cleans up event listeners properly', () => { const mockAddEventListener = jest.spyOn(window, 'addEventListener') const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') - + const { unmount } = render() - + expect(mockAddEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) - + unmount() - + expect(mockRemoveEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) }) }) @@ -482,36 +511,36 @@ describe('AnchorTitle Component', () => { describe('State Changes and Internal Logic', () => { it('recalculates scroll position when element dimensions change', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - + let offsetHeight = 30 const mockElement = { getBoundingClientRect: jest.fn(() => ({ top: 100 })), - querySelector: jest.fn(() => ({ offsetHeight })) + querySelector: jest.fn(() => ({ offsetHeight })), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) - + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) + render() const link = screen.getByRole('link') - + fireEvent.click(link) const firstCall = (mockScrollTo.mock.calls[0][0] as unknown as { top: number }).top - + offsetHeight = 60 fireEvent.click(link) const secondCall = (mockScrollTo.mock.calls[1][0] as unknown as { top: number }).top - + expect(firstCall).not.toBe(secondCall) expect(Math.abs(firstCall - secondCall)).toBe(30) }) it('handles component rerender with different IDs', () => { const mockAddEventListener = jest.spyOn(window, 'addEventListener') const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') - + const { rerender } = render() const originalAddCalls = mockAddEventListener.mock.calls.length - + rerender() - + expect(mockRemoveEventListener).toHaveBeenCalled() expect(mockAddEventListener.mock.calls.length).toBeGreaterThan(originalAddCalls) }) @@ -524,7 +553,7 @@ describe('AnchorTitle Component', () => { const link = screen.getByRole('link') expect(link).toHaveClass( 'inherit-color', - 'ml-2', + 'ml-2', 'opacity-0', 'transition-opacity', 'duration-200', @@ -534,18 +563,18 @@ describe('AnchorTitle Component', () => { it('has correct icon styling', () => { render() - + const icon = screen.getByRole('link').querySelector('svg') expect(icon).toHaveClass('custom-icon', 'h-7', 'w-5') }) it('maintains proper layout structure', () => { render() - + const titleContainer = screen.getByText('Layout Test').closest('div') expect(titleContainer).toHaveAttribute('id', 'anchor-title') expect(titleContainer).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') - + const groupContainer = titleContainer?.closest('.group') expect(groupContainer).toHaveClass('group', 'relative', 'flex', 'items-center') }) @@ -555,9 +584,9 @@ describe('AnchorTitle Component', () => { it('uses slugify utility correctly', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const slugifyMock = require('utils/slugify').default - + render() - + expect(slugifyMock).toHaveBeenCalledWith('Slugify Test') }) @@ -565,12 +594,12 @@ describe('AnchorTitle Component', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const slugifyMock = require('utils/slugify').default slugifyMock.mockReturnValue('custom-slug-format') - + render() - + const link = screen.getByRole('link') expect(link).toHaveAttribute('href', '#custom-slug-format') - + const element = document.getElementById('custom-slug-format') expect(element).toBeInTheDocument() }) @@ -580,32 +609,32 @@ describe('AnchorTitle Component', () => { it('complete user journey: render → hover → click → navigate', async () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() - + render() - + const titleElement = screen.getByText('User Journey') expect(titleElement).toBeInTheDocument() - + const link = screen.getByRole('link') expect(link).toHaveClass('opacity-0') - + fireEvent.click(link) - + expect(mockScrollTo).toHaveBeenCalledWith({ top: expect.any(Number), - behavior: 'smooth' + behavior: 'smooth', }) expect(mockPushState).toHaveBeenCalledWith(null, '', '#custom-slug-format') }) it('handles keyboard navigation', () => { render() - + const link = screen.getByRole('link') - + link.focus() expect(document.activeElement).toBe(link) - + fireEvent.keyDown(link, { key: 'Enter', code: 'Enter' }) }) }) From a020054f87bcb359455d0d785878f4b467c75565 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Thu, 7 Aug 2025 03:11:54 +0530 Subject: [PATCH 4/9] Fixed issues mentioned by the bot --- .../unit/components/AnchorTitle.test.tsx | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index ccf26651ce..5783508465 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { library } from '@fortawesome/fontawesome-svg-core' import { faLink } from '@fortawesome/free-solid-svg-icons' import { screen, render, fireEvent, waitFor } from '@testing-library/react' @@ -91,21 +92,13 @@ describe('AnchorTitle Component', () => { unmount() }) }) - - it('applies className prop when provided', () => { - render() - const container = screen.getByText('Test').closest('div') - expect(container).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') - }) }) describe('Event Handling', () => { let mockScrollTo: jest.SpyInstance let mockPushState: jest.SpyInstance let mockGetBoundingClientRect: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockRequestAnimationFrame: jest.SpyInstance beforeEach(() => { @@ -138,7 +131,8 @@ describe('AnchorTitle Component', () => { } mockGetElementById = jest .spyOn(document, 'getElementById') - .mockReturnValue(mockElement as never) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockReturnValue(mockElement as any) }) it('prevents default behaviour on link click', () => { @@ -189,7 +183,6 @@ describe('AnchorTitle Component', () => { describe('useEffect Behaviour', () => { let mockScrollTo: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance @@ -220,7 +213,8 @@ describe('AnchorTitle Component', () => { } mockGetElementById = jest .spyOn(document, 'getElementById') - .mockReturnValue(mockElement as never) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockReturnValue(mockElement as any) }) it('scrolls to element on mount when hash matches', async () => { @@ -237,20 +231,6 @@ describe('AnchorTitle Component', () => { }) }) - it('scrolls to an element on mount when hash matches', async () => { - window.location.hash = '#test-scroll' - - render() - - await waitFor(() => { - expect(mockRequestAnimationFrame).toHaveBeenCalled() - expect(mockScrollTo).toHaveBeenCalledWith({ - top: 20, - behavior: 'smooth', - }) - }) - }) - it('does not scroll when hash does not match', () => { window.location.hash = '#different-hash' @@ -339,7 +319,8 @@ describe('AnchorTitle Component', () => { })), querySelector: jest.fn(() => null), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) jest.spyOn(window, 'scrollTo').mockImplementation() render() @@ -444,7 +425,8 @@ describe('AnchorTitle Component', () => { getBoundingClientRect: jest.fn(() => ({ top: 100 })), querySelector: jest.fn(() => ({ offsetHeight: 30 })), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) render() const link = screen.getByRole('link') @@ -517,7 +499,8 @@ describe('AnchorTitle Component', () => { getBoundingClientRect: jest.fn(() => ({ top: 100 })), querySelector: jest.fn(() => ({ offsetHeight })), } - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as never) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) render() const link = screen.getByRole('link') @@ -532,6 +515,7 @@ describe('AnchorTitle Component', () => { expect(firstCall).not.toBe(secondCall) expect(Math.abs(firstCall - secondCall)).toBe(30) }) + it('handles component rerender with different IDs', () => { const mockAddEventListener = jest.spyOn(window, 'addEventListener') const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener') @@ -607,6 +591,16 @@ describe('AnchorTitle Component', () => { describe('Comprehensive User Interactions', () => { it('complete user journey: render → hover → click → navigate', async () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const slugifyMock = require('utils/slugify').default + slugifyMock.mockImplementation((str: string) => + str + .toLowerCase() + .replace(/[^a-z0-9]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + ) + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() @@ -624,7 +618,7 @@ describe('AnchorTitle Component', () => { top: expect.any(Number), behavior: 'smooth', }) - expect(mockPushState).toHaveBeenCalledWith(null, '', '#custom-slug-format') + expect(mockPushState).toHaveBeenCalledWith(null, '', '#user-journey') }) it('handles keyboard navigation', () => { From db6ad1ee69b7c734689f7b6128214118c9a28a11 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Thu, 7 Aug 2025 12:29:39 +0530 Subject: [PATCH 5/9] Fixed issues flagged by the bot --- .../unit/components/AnchorTitle.test.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 5783508465..d8bee09cba 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { library } from '@fortawesome/fontawesome-svg-core' import { faLink } from '@fortawesome/free-solid-svg-icons' import { screen, render, fireEvent, waitFor } from '@testing-library/react' @@ -98,7 +97,9 @@ describe('AnchorTitle Component', () => { let mockScrollTo: jest.SpyInstance let mockPushState: jest.SpyInstance let mockGetBoundingClientRect: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockRequestAnimationFrame: jest.SpyInstance beforeEach(() => { @@ -183,6 +184,7 @@ describe('AnchorTitle Component', () => { describe('useEffect Behaviour', () => { let mockScrollTo: jest.SpyInstance + // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance @@ -345,6 +347,10 @@ describe('AnchorTitle Component', () => { const titleElement = screen.getByText('Heading Test') expect(titleElement).toHaveAttribute('id', 'anchor-title') expect(titleElement).toHaveClass('text-2xl', 'font-semibold') + + const container = document.getElementById('heading-test') + expect(container).toHaveAttribute('id', 'heading-test') + expect(container).toBeInTheDocument() }) it('maintains focus management on interaction', () => { @@ -415,6 +421,7 @@ describe('AnchorTitle Component', () => { describe('Browser API Interactions', () => { it('handles window.pageYOffset correctly', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() + const originalPageYOffset = window.pageYOffset Object.defineProperty(window, 'pageYOffset', { value: 500, @@ -437,6 +444,10 @@ describe('AnchorTitle Component', () => { top: 520, behavior: 'smooth', }) + Object.defineProperty(window, 'pageYOffset', { + value: originalPageYOffset, + configurable: true, + }) }) it('handles hash changes in the URL correctly', async () => { @@ -556,7 +567,7 @@ describe('AnchorTitle Component', () => { render() const titleContainer = screen.getByText('Layout Test').closest('div') - expect(titleContainer).toHaveAttribute('id', 'anchor-title') + // expect(titleContainer).toHaveAttribute('id', 'anchor-title') expect(titleContainer).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') const groupContainer = titleContainer?.closest('.group') @@ -577,6 +588,7 @@ describe('AnchorTitle Component', () => { it('handles slugify returning different formats', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const slugifyMock = require('utils/slugify').default + const originalImplementation = slugifyMock.getMockImplementation() slugifyMock.mockReturnValue('custom-slug-format') render() @@ -586,6 +598,9 @@ describe('AnchorTitle Component', () => { const element = document.getElementById('custom-slug-format') expect(element).toBeInTheDocument() + if (originalImplementation) { + slugifyMock.mockImplementation(originalImplementation) + } }) }) From 38faefaf5fed6a2a9ab7a4e5dd0a99c5b522fb3d Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Sat, 9 Aug 2025 12:10:01 +0530 Subject: [PATCH 6/9] Fixed issues flagged by the bot --- .../unit/components/AnchorTitle.test.tsx | 85 ++++++++++++++----- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index d8bee09cba..907b727fa2 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -97,9 +97,7 @@ describe('AnchorTitle Component', () => { let mockScrollTo: jest.SpyInstance let mockPushState: jest.SpyInstance let mockGetBoundingClientRect: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockRequestAnimationFrame: jest.SpyInstance beforeEach(() => { @@ -132,8 +130,14 @@ describe('AnchorTitle Component', () => { } mockGetElementById = jest .spyOn(document, 'getElementById') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockReturnValue(mockElement as any) + .mockReturnValue(mockElement as unknown as HTMLElement) + }) + + afterEach(() => { + mockScrollTo.mockRestore() + mockPushState.mockRestore() + mockRequestAnimationFrame.mockRestore() + mockGetElementById.mockRestore() }) it('prevents default behaviour on link click', () => { @@ -184,7 +188,6 @@ describe('AnchorTitle Component', () => { describe('useEffect Behaviour', () => { let mockScrollTo: jest.SpyInstance - // eslint-disable-next-line @typescript-eslint/no-unused-vars let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance @@ -215,8 +218,13 @@ describe('AnchorTitle Component', () => { } mockGetElementById = jest .spyOn(document, 'getElementById') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .mockReturnValue(mockElement as any) + .mockReturnValue(mockElement as unknown as HTMLElement) + }) + + afterEach(() => { + mockScrollTo.mockRestore() + mockRequestAnimationFrame.mockRestore() + mockGetElementById.mockRestore() }) it('scrolls to element on mount when hash matches', async () => { @@ -295,15 +303,16 @@ describe('AnchorTitle Component', () => { }) it('handles missing DOM element gracefully', () => { - jest.spyOn(document, 'getElementById').mockReturnValue(null) - jest.spyOn(console, 'error').mockImplementation(() => {}) + const mockGetElementById = jest.spyOn(document, 'getElementById').mockReturnValue(null) + const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {}) render() const link = screen.getByRole('link') expect(() => fireEvent.click(link)).not.toThrow() - jest.restoreAllMocks() + mockGetElementById.mockRestore() + mockConsoleError.mockRestore() }) it('handles missing anchor-title element in scroll calculation', () => { @@ -321,14 +330,18 @@ describe('AnchorTitle Component', () => { })), querySelector: jest.fn(() => null), } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) - jest.spyOn(window, 'scrollTo').mockImplementation() + const mockGetElementById = jest + .spyOn(document, 'getElementById') + .mockReturnValue(mockElement as unknown as HTMLElement) + const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() render() const link = screen.getByRole('link') expect(() => fireEvent.click(link)).not.toThrow() + + mockGetElementById.mockRestore() + mockScrollTo.mockRestore() }) }) @@ -403,6 +416,9 @@ describe('AnchorTitle Component', () => { expect(mockScrollTo).toHaveBeenCalledTimes(3) expect(mockPushState).toHaveBeenCalledTimes(3) + + mockScrollTo.mockRestore() + mockPushState.mockRestore() }) it('updates correctly when title prop changes', () => { @@ -432,8 +448,9 @@ describe('AnchorTitle Component', () => { getBoundingClientRect: jest.fn(() => ({ top: 100 })), querySelector: jest.fn(() => ({ offsetHeight: 30 })), } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + const mockGetElementById = jest + .spyOn(document, 'getElementById') + .mockReturnValue(mockElement as unknown as HTMLElement) render() const link = screen.getByRole('link') @@ -444,18 +461,24 @@ describe('AnchorTitle Component', () => { top: 520, behavior: 'smooth', }) + Object.defineProperty(window, 'pageYOffset', { value: originalPageYOffset, configurable: true, }) + + mockScrollTo.mockRestore() + mockGetElementById.mockRestore() }) it('handles hash changes in the URL correctly', async () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { - cb(0) - return 0 - }) + const mockRequestAnimationFrame = jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb) => { + cb(0) + return 0 + }) window.location.hash = '' render() @@ -467,6 +490,9 @@ describe('AnchorTitle Component', () => { await waitFor(() => { expect(mockScrollTo).toHaveBeenCalled() }) + + mockScrollTo.mockRestore() + mockRequestAnimationFrame.mockRestore() }) }) @@ -485,6 +511,7 @@ describe('AnchorTitle Component', () => { fireEvent.click(link2) expect(mockScrollTo).toHaveBeenCalledTimes(2) + mockScrollTo.mockRestore() }) it('cleans up event listeners properly', () => { @@ -498,6 +525,9 @@ describe('AnchorTitle Component', () => { unmount() expect(mockRemoveEventListener).toHaveBeenCalledWith('popstate', expect.any(Function)) + + mockAddEventListener.mockRestore() + mockRemoveEventListener.mockRestore() }) }) @@ -510,8 +540,9 @@ describe('AnchorTitle Component', () => { getBoundingClientRect: jest.fn(() => ({ top: 100 })), querySelector: jest.fn(() => ({ offsetHeight })), } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jest.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + const mockGetElementById = jest + .spyOn(document, 'getElementById') + .mockReturnValue(mockElement as unknown as HTMLElement) render() const link = screen.getByRole('link') @@ -525,6 +556,9 @@ describe('AnchorTitle Component', () => { expect(firstCall).not.toBe(secondCall) expect(Math.abs(firstCall - secondCall)).toBe(30) + + mockScrollTo.mockRestore() + mockGetElementById.mockRestore() }) it('handles component rerender with different IDs', () => { @@ -538,6 +572,9 @@ describe('AnchorTitle Component', () => { expect(mockRemoveEventListener).toHaveBeenCalled() expect(mockAddEventListener.mock.calls.length).toBeGreaterThan(originalAddCalls) + + mockAddEventListener.mockRestore() + mockRemoveEventListener.mockRestore() }) }) @@ -560,6 +597,7 @@ describe('AnchorTitle Component', () => { render() const icon = screen.getByRole('link').querySelector('svg') + expect(icon).toBeInTheDocument() expect(icon).toHaveClass('custom-icon', 'h-7', 'w-5') }) @@ -567,7 +605,6 @@ describe('AnchorTitle Component', () => { render() const titleContainer = screen.getByText('Layout Test').closest('div') - // expect(titleContainer).toHaveAttribute('id', 'anchor-title') expect(titleContainer).toHaveClass('flex', 'items-center', 'text-2xl', 'font-semibold') const groupContainer = titleContainer?.closest('.group') @@ -598,6 +635,7 @@ describe('AnchorTitle Component', () => { const element = document.getElementById('custom-slug-format') expect(element).toBeInTheDocument() + if (originalImplementation) { slugifyMock.mockImplementation(originalImplementation) } @@ -634,6 +672,9 @@ describe('AnchorTitle Component', () => { behavior: 'smooth', }) expect(mockPushState).toHaveBeenCalledWith(null, '', '#user-journey') + + mockScrollTo.mockRestore() + mockPushState.mockRestore() }) it('handles keyboard navigation', () => { From 75a1924a25ae6ec01d0d5e79d196040945791c35 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Sat, 9 Aug 2025 12:42:42 +0530 Subject: [PATCH 7/9] Fixed issues flagged by the bot --- .../unit/components/AnchorTitle.test.tsx | 191 +++++++++--------- 1 file changed, 97 insertions(+), 94 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 907b727fa2..2b92ab4e16 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -1,6 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faLink } from '@fortawesome/free-solid-svg-icons' import { screen, render, fireEvent, waitFor } from '@testing-library/react' +import slugifyMock from 'utils/slugify' import AnchorTitle from 'components/AnchorTitle' library.add(faLink) @@ -11,11 +12,63 @@ jest.mock('utils/slugify', () => ({ str .toLowerCase() .replace(/[^a-z0-9]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') + .replace(/-{2,}/g, '-') + .replace(/^(-)+|(-)+$/g, '') ), })) +// Helper functions to reduce nesting +const createMockBoundingClientRect = () => ({ + top: 100, + height: 50, + width: 200, + bottom: 150, + left: 0, + right: 200, + x: 0, + y: 100, + toJSON: () => {}, +}) + +const createMockElement = (getBoundingClientRect: jest.SpyInstance | jest.Mock) => ({ + getBoundingClientRect, + querySelector: jest.fn(() => ({ + offsetHeight: 30, + })), +}) + +const setupMockAnimationFrame = (cb: (time: number) => void) => { + cb(0) + return 0 +} + +const createMockElementForUseEffect = () => ({ + getBoundingClientRect: jest.fn(() => createMockBoundingClientRect()), + querySelector: jest.fn(() => ({ + offsetHeight: 30, + })), +}) + +const setupAnimationFrameForUseEffect = (cb: (time: number) => void) => { + cb(0) + return 0 +} + +const createMockElementWithNullQuery = () => ({ + getBoundingClientRect: jest.fn(() => createMockBoundingClientRect()), + querySelector: jest.fn(() => null), +}) + +const createMockElementForOffset = () => ({ + getBoundingClientRect: jest.fn(() => ({ top: 100 })), + querySelector: jest.fn(() => ({ offsetHeight: 30 })), +}) + +const setupAnimationFrameForHash = (cb: (time: number) => void) => { + cb(0) + return 0 +} + describe('AnchorTitle Component', () => { afterEach(() => { jest.restoreAllMocks() @@ -84,10 +137,13 @@ describe('AnchorTitle Component', () => { 'Title_with_underscores', ] + const titleRegex = /^#[a-z0-9-]+$/ + titles.forEach((title) => { const { unmount } = render() const link = screen.getByRole('link') - expect(link.getAttribute('href')).toMatch(/^#[a-z0-9-]+$/) + const href = link.getAttribute('href') + expect(href).toMatch(titleRegex) unmount() }) }) @@ -100,45 +156,29 @@ describe('AnchorTitle Component', () => { let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance - beforeEach(() => { + const setupMocks = () => { mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation() mockRequestAnimationFrame = jest .spyOn(window, 'requestAnimationFrame') - .mockImplementation((cb) => { - cb(0) - return 0 - }) - - mockGetBoundingClientRect = jest.fn(() => ({ - top: 100, - height: 50, - width: 200, - bottom: 150, - left: 0, - right: 200, - x: 0, - y: 100, - toJSON: () => {}, - })) + .mockImplementation(setupMockAnimationFrame) - const mockElement = { - getBoundingClientRect: mockGetBoundingClientRect, - querySelector: jest.fn(() => ({ - offsetHeight: 30, - })), - } + mockGetBoundingClientRect = jest.fn(createMockBoundingClientRect) + const mockElement = createMockElement(mockGetBoundingClientRect) mockGetElementById = jest .spyOn(document, 'getElementById') .mockReturnValue(mockElement as unknown as HTMLElement) - }) + } - afterEach(() => { + const cleanupMocks = () => { mockScrollTo.mockRestore() mockPushState.mockRestore() mockRequestAnimationFrame.mockRestore() mockGetElementById.mockRestore() - }) + } + + beforeEach(setupMocks) + afterEach(cleanupMocks) it('prevents default behaviour on link click', () => { render() @@ -191,41 +231,26 @@ describe('AnchorTitle Component', () => { let mockGetElementById: jest.SpyInstance let mockRequestAnimationFrame: jest.SpyInstance - beforeEach(() => { + const setupUseEffectMocks = () => { mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() mockRequestAnimationFrame = jest .spyOn(window, 'requestAnimationFrame') - .mockImplementation((cb) => { - cb(0) - return 0 - }) + .mockImplementation(setupAnimationFrameForUseEffect) - const mockElement = { - getBoundingClientRect: jest.fn(() => ({ - top: 100, - height: 50, - width: 200, - bottom: 150, - left: 0, - right: 200, - x: 0, - y: 100, - toJSON: () => {}, - })), - querySelector: jest.fn(() => ({ - offsetHeight: 30, - })), - } + const mockElement = createMockElementForUseEffect() mockGetElementById = jest .spyOn(document, 'getElementById') .mockReturnValue(mockElement as unknown as HTMLElement) - }) + } - afterEach(() => { + const cleanupUseEffectMocks = () => { mockScrollTo.mockRestore() mockRequestAnimationFrame.mockRestore() mockGetElementById.mockRestore() - }) + } + + beforeEach(setupUseEffectMocks) + afterEach(cleanupUseEffectMocks) it('scrolls to element on mount when hash matches', async () => { window.location.hash = '#test-scroll' @@ -289,7 +314,9 @@ describe('AnchorTitle Component', () => { expect(screen.getByText(specialTitle)).toBeInTheDocument() const link = screen.getByRole('link') - expect(link.getAttribute('href')).toMatch(/^#[a-z0-9-]*$/) + const specialCharRegex = /^#[a-z0-9-]*$/ + const href = link.getAttribute('href') + expect(href).toMatch(specialCharRegex) }) it('handles very long titles', () => { @@ -316,20 +343,7 @@ describe('AnchorTitle Component', () => { }) it('handles missing anchor-title element in scroll calculation', () => { - const mockElement = { - getBoundingClientRect: jest.fn(() => ({ - top: 100, - height: 50, - width: 200, - bottom: 150, - left: 0, - right: 200, - x: 0, - y: 100, - toJSON: () => {}, - })), - querySelector: jest.fn(() => null), - } + const mockElement = createMockElementWithNullQuery() const mockGetElementById = jest .spyOn(document, 'getElementById') .mockReturnValue(mockElement as unknown as HTMLElement) @@ -444,10 +458,7 @@ describe('AnchorTitle Component', () => { configurable: true, }) - const mockElement = { - getBoundingClientRect: jest.fn(() => ({ top: 100 })), - querySelector: jest.fn(() => ({ offsetHeight: 30 })), - } + const mockElement = createMockElementForOffset() const mockGetElementById = jest .spyOn(document, 'getElementById') .mockReturnValue(mockElement as unknown as HTMLElement) @@ -475,10 +486,7 @@ describe('AnchorTitle Component', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() const mockRequestAnimationFrame = jest .spyOn(window, 'requestAnimationFrame') - .mockImplementation((cb) => { - cb(0) - return 0 - }) + .mockImplementation(setupAnimationFrameForHash) window.location.hash = '' render() @@ -535,10 +543,10 @@ describe('AnchorTitle Component', () => { it('recalculates scroll position when element dimensions change', () => { const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() - let offsetHeight = 30 + const offsetHeights = { current: 30 } const mockElement = { getBoundingClientRect: jest.fn(() => ({ top: 100 })), - querySelector: jest.fn(() => ({ offsetHeight })), + querySelector: jest.fn(() => ({ offsetHeight: offsetHeights.current })), } const mockGetElementById = jest .spyOn(document, 'getElementById') @@ -550,7 +558,7 @@ describe('AnchorTitle Component', () => { fireEvent.click(link) const firstCall = (mockScrollTo.mock.calls[0][0] as unknown as { top: number }).top - offsetHeight = 60 + offsetHeights.current = 60 fireEvent.click(link) const secondCall = (mockScrollTo.mock.calls[1][0] as unknown as { top: number }).top @@ -614,19 +622,14 @@ describe('AnchorTitle Component', () => { describe('Dependencies and Mocking', () => { it('uses slugify utility correctly', () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const slugifyMock = require('utils/slugify').default - render() - expect(slugifyMock).toHaveBeenCalledWith('Slugify Test') }) it('handles slugify returning different formats', () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const slugifyMock = require('utils/slugify').default - const originalImplementation = slugifyMock.getMockImplementation() - slugifyMock.mockReturnValue('custom-slug-format') + const mockFn = slugifyMock as jest.MockedFunction + const originalImplementation = mockFn.getMockImplementation() + mockFn.mockReturnValue('custom-slug-format') render() @@ -637,21 +640,21 @@ describe('AnchorTitle Component', () => { expect(element).toBeInTheDocument() if (originalImplementation) { - slugifyMock.mockImplementation(originalImplementation) + mockFn.mockImplementation(originalImplementation) } }) }) describe('Comprehensive User Interactions', () => { - it('complete user journey: render → hover → click → navigate', async () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const slugifyMock = require('utils/slugify').default - slugifyMock.mockImplementation((str: string) => + it('complete user journey: render → hover → click → navigate', () => { + const mockFn = slugifyMock as jest.MockedFunction + + mockFn.mockImplementation((str: string) => str .toLowerCase() .replace(/[^a-z0-9]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') + .replace(/-{2,}/g, '-') + .replace(/^(-)+|(-)+$/g, '') ) const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() From 85737432708513651694d4b3e7361451e1628a86 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Sat, 9 Aug 2025 12:48:17 +0530 Subject: [PATCH 8/9] Fixed issues flagged by the bot --- frontend/__tests__/unit/components/AnchorTitle.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 2b92ab4e16..7ad6dd41bc 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -13,7 +13,7 @@ jest.mock('utils/slugify', () => ({ .toLowerCase() .replace(/[^a-z0-9]/g, '-') .replace(/-{2,}/g, '-') - .replace(/^(-)+|(-)+$/g, '') + .replace(/(^(-)+|(-)+$)/g, '') ), })) @@ -654,7 +654,7 @@ describe('AnchorTitle Component', () => { .toLowerCase() .replace(/[^a-z0-9]/g, '-') .replace(/-{2,}/g, '-') - .replace(/^(-)+|(-)+$/g, '') + .replace(/(^(-)+|(-)+$)/g, '') ) const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation() From 5fcfe5ec7a2300f71ca573d44c2c4da7c6c78d27 Mon Sep 17 00:00:00 2001 From: RizWaaN3024 Date: Sat, 9 Aug 2025 12:54:01 +0530 Subject: [PATCH 9/9] Fixed issues flagged by the bot --- frontend/__tests__/unit/components/AnchorTitle.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index 7ad6dd41bc..09255fb957 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -12,8 +12,8 @@ jest.mock('utils/slugify', () => ({ str .toLowerCase() .replace(/[^a-z0-9]/g, '-') - .replace(/-{2,}/g, '-') - .replace(/(^(-)+|(-)+$)/g, '') + .replace(/-{2,10}/g, '-') + .replace(/(^-{1,10}|-{1,10}$)/g, '') ), })) @@ -653,8 +653,8 @@ describe('AnchorTitle Component', () => { str .toLowerCase() .replace(/[^a-z0-9]/g, '-') - .replace(/-{2,}/g, '-') - .replace(/(^(-)+|(-)+$)/g, '') + .replace(/-{2,10}/g, '-') + .replace(/(^-{1,10}|-{1,10}$)/g, '') ) const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()