From c00d5acc8a43cbbd92c5a36ac57907211c28c147 Mon Sep 17 00:00:00 2001 From: Salman Najah Date: Sat, 21 Feb 2026 14:01:44 +0530 Subject: [PATCH 1/2] feat: Add tooltip to collection item remove button, including styles and unit tests. --- .../User/components/CollectionItemRow.jsx | 17 +-- .../CollectionItemRow.unit.test.jsx | 112 ++++++++++++++++++ client/styles/components/_collection.scss | 16 +++ 3 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 client/modules/User/components/CollectionItemRow.unit.test.jsx diff --git a/client/modules/User/components/CollectionItemRow.jsx b/client/modules/User/components/CollectionItemRow.jsx index e6f3e6d30e..6a6584ccd1 100644 --- a/client/modules/User/components/CollectionItemRow.jsx +++ b/client/modules/User/components/CollectionItemRow.jsx @@ -6,6 +6,7 @@ import { useDispatch } from 'react-redux'; import { removeFromCollection } from '../../IDE/actions/collections'; import { formatDateToString } from '../../../utils/formatDate'; import RemoveIcon from '../../../images/close.svg'; +import { Tooltip } from '../../../common/Tooltip'; const CollectionItemRow = ({ collection, item, isOwner }) => { const { t } = useTranslation(); @@ -48,13 +49,15 @@ const CollectionItemRow = ({ collection, item, isOwner }) => { {sketchOwnerUsername} {isOwner && ( - + + + )} diff --git a/client/modules/User/components/CollectionItemRow.unit.test.jsx b/client/modules/User/components/CollectionItemRow.unit.test.jsx new file mode 100644 index 0000000000..0d09fe0a82 --- /dev/null +++ b/client/modules/User/components/CollectionItemRow.unit.test.jsx @@ -0,0 +1,112 @@ +import React from 'react'; +import thunk from 'redux-thunk'; +import configureStore from 'redux-mock-store'; +import userEvent from '@testing-library/user-event'; +import { reduxRender, screen, act } from '../../../test-utils'; +import { initialTestState } from '../../../testData/testReduxStore'; +import CollectionItemRow from './CollectionItemRow'; + +jest.mock('../../../i18n'); + +const mockStore = configureStore([thunk]); +const store = mockStore(initialTestState); + +let subjectProps = { + collection: { + id: 'collection-123', + name: 'Test Collection' + }, + item: { + createdAt: '2026-01-15T10:30:00.000Z', + isDeleted: false, + project: { + id: 'project-456', + name: 'My Sketch', + user: { username: 'testuser' }, + visibility: 'Public' + } + }, + isOwner: true +}; + +const subject = () => { + reduxRender( + + + + +
, + { store } + ); +}; + +describe('', () => { + afterEach(() => { + store.clearActions(); + }); + + it('renders the sketch name as a link', () => { + subject(); + const link = screen.getByRole('link', { name: 'My Sketch' }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', '/testuser/sketches/project-456'); + }); + + it('renders the owner username', () => { + subject(); + expect(screen.getByText('testuser')).toBeInTheDocument(); + }); + + it('shows the remove button when user is the owner', () => { + subject(); + expect( + screen.getByRole('button', { name: /remove/i }) + ).toBeInTheDocument(); + }); + + describe('when user is not the owner', () => { + beforeAll(() => { + subjectProps = { ...subjectProps, isOwner: false }; + }); + + afterAll(() => { + subjectProps = { ...subjectProps, isOwner: true }; + }); + + it('does not show the remove button', () => { + subject(); + expect( + screen.queryByRole('button', { name: /remove/i }) + ).not.toBeInTheDocument(); + }); + }); + + it('wraps the remove button with a tooltip', async () => { + const user = userEvent.setup(); + subject(); + + const button = screen.getByRole('button', { name: /remove/i }); + await act(async () => { + await user.hover(button); + }); + expect(button).toHaveClass('tooltipped'); + }); + + describe('when the project is deleted', () => { + beforeAll(() => { + subjectProps = { + ...subjectProps, + item: { + ...subjectProps.item, + isDeleted: true, + project: undefined + } + }; + }); + + it('shows the deleted sketch label', () => { + subject(); + expect(screen.getByText('Sketch deleted')).toBeInTheDocument(); + }); + }); +}); diff --git a/client/styles/components/_collection.scss b/client/styles/components/_collection.scss index bc5f9db5db..adfc1caa5c 100644 --- a/client/styles/components/_collection.scss +++ b/client/styles/components/_collection.scss @@ -148,6 +148,22 @@ .collection-row__action-column { width: #{math.div(60, $base-font-size)}rem; position: relative; + + .tooltip-wrapper { + display: inline-flex; + width: auto; + + .tooltipped::after { + right: 0; + left: auto; + } + + .tooltipped-n::before, + .tooltipped::before { + right: #{math.div(10, $base-font-size)}rem; + left: auto; + } + } } .collection-row__remove-button { From cacaa92d2f332fa52601c430d4d4bae688c992c4 Mon Sep 17 00:00:00 2001 From: Salman Najah Date: Sat, 21 Feb 2026 14:32:02 +0530 Subject: [PATCH 2/2] fix lint --- .../User/components/CollectionItemRow.unit.test.jsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/client/modules/User/components/CollectionItemRow.unit.test.jsx b/client/modules/User/components/CollectionItemRow.unit.test.jsx index 0d09fe0a82..933a5e9d07 100644 --- a/client/modules/User/components/CollectionItemRow.unit.test.jsx +++ b/client/modules/User/components/CollectionItemRow.unit.test.jsx @@ -1,8 +1,7 @@ import React from 'react'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; -import userEvent from '@testing-library/user-event'; -import { reduxRender, screen, act } from '../../../test-utils'; +import { reduxRender, screen, fireEvent, act } from '../../../test-utils'; import { initialTestState } from '../../../testData/testReduxStore'; import CollectionItemRow from './CollectionItemRow'; @@ -59,9 +58,7 @@ describe('', () => { it('shows the remove button when user is the owner', () => { subject(); - expect( - screen.getByRole('button', { name: /remove/i }) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument(); }); describe('when user is not the owner', () => { @@ -82,12 +79,11 @@ describe('', () => { }); it('wraps the remove button with a tooltip', async () => { - const user = userEvent.setup(); subject(); const button = screen.getByRole('button', { name: /remove/i }); await act(async () => { - await user.hover(button); + fireEvent.mouseEnter(button); }); expect(button).toHaveClass('tooltipped'); });