diff --git a/src/__testing__/ListItemText.test.tsx b/src/__testing__/ListItemText.test.tsx new file mode 100644 index 000000000..e26d8ce70 --- /dev/null +++ b/src/__testing__/ListItemText.test.tsx @@ -0,0 +1,197 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { ListItemText } from '../base/ListItemText'; + +describe('ListItemText', () => { + describe('default truncation behavior', () => { + it('applies noWrap by default to primary text', () => { + render(); + const primaryElement = screen.getByText('Truncated Primary'); + expect(primaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + }); + + it('applies noWrap by default to secondary text', () => { + render(); + const secondaryElement = screen.getByText('Truncated Secondary'); + expect(secondaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + }); + }); + + describe('object-valued slotProps', () => { + it('preserves custom slotProps on primary while keeping noWrap by default', () => { + render( + + }} + /> + ); + const primaryElement = screen.getByTestId('custom-primary'); + expect(primaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + expect(primaryElement.classList.contains('custom-class')).toBe(true); + }); + + it('preserves custom slotProps on secondary while keeping noWrap by default', () => { + render( + + }} + /> + ); + const secondaryElement = screen.getByTestId('custom-secondary'); + expect(secondaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + expect(secondaryElement.classList.contains('custom-class-secondary')).toBe(true); + }); + + it('allows consumer to explicitly override noWrap to false on primary', () => { + render( + + ); + const primaryElement = screen.getByTestId('multiline-primary'); + expect(primaryElement.classList.contains('MuiTypography-noWrap')).toBe(false); + }); + + it('allows consumer to explicitly override noWrap to false on secondary', () => { + render( + + ); + const secondaryElement = screen.getByTestId('multiline-secondary'); + expect(secondaryElement.classList.contains('MuiTypography-noWrap')).toBe(false); + }); + }); + + describe('function-valued slotProps', () => { + it('executes primary slot callback with ownerState and retains noWrap by default', () => { + const primaryFn = jest.fn((ownerState) => ({ + 'data-testid': 'callback-primary', + className: ownerState.inset ? 'is-inset' : 'not-inset' + })); + + render( + + ); + + expect(primaryFn).toHaveBeenCalled(); + const ownerStateArg = primaryFn.mock.calls[0][0]; + expect(ownerStateArg.inset).toBe(true); + + const primaryElement = screen.getByTestId('callback-primary'); + expect(primaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + expect(primaryElement.classList.contains('is-inset')).toBe(true); + }); + + it('executes secondary slot callback with ownerState and retains noWrap by default', () => { + const secondaryFn = jest.fn(() => ({ + 'data-testid': 'callback-secondary', + className: 'dynamic-secondary' + })); + + render( + + ); + + expect(secondaryFn).toHaveBeenCalled(); + const secondaryElement = screen.getByTestId('callback-secondary'); + expect(secondaryElement.classList.contains('MuiTypography-noWrap')).toBe(true); + expect(secondaryElement.classList.contains('dynamic-secondary')).toBe(true); + }); + + it('allows function callback to explicitly override noWrap to false on primary', () => { + render( + ({ + noWrap: false, + 'data-testid': 'multiline-fn-primary' + }) + }} + /> + ); + const primaryElement = screen.getByTestId('multiline-fn-primary'); + expect(primaryElement.classList.contains('MuiTypography-noWrap')).toBe(false); + }); + + it('allows function callback to explicitly override noWrap to false on secondary', () => { + render( + ({ + noWrap: false, + 'data-testid': 'multiline-fn-secondary' + }) + }} + /> + ); + const secondaryElement = screen.getByTestId('multiline-fn-secondary'); + expect(secondaryElement.classList.contains('MuiTypography-noWrap')).toBe(false); + }); + }); + + describe('preservation of root slot and other props', () => { + it('preserves root slotProps', () => { + render( + + }} + /> + ); + const rootElement = screen.getByTestId('custom-root-slot'); + expect(rootElement.classList.contains('custom-root-class')).toBe(true); + }); + + it('forwards top-level props to root element', () => { + render( + + ); + const rootElement = screen.getByTestId('top-level-root'); + expect(rootElement.classList.contains('top-level-class')).toBe(true); + }); + }); +}); diff --git a/src/base/ListItemText/ListItemText.tsx b/src/base/ListItemText/ListItemText.tsx index 158ef40d7..aa5e88d88 100644 --- a/src/base/ListItemText/ListItemText.tsx +++ b/src/base/ListItemText/ListItemText.tsx @@ -2,7 +2,34 @@ import { ListItemText as MuiListItemText, ListItemTextProps as MuiListItemTextProps } from '@mui/material'; +import type { ListItemTextOwnerState } from '@mui/material/ListItemText'; -export function ListItemText(props: MuiListItemTextProps): JSX.Element { - return ; +function withNoWrapDefault( + slotProp: T | ((ownerState: ListItemTextOwnerState) => T) | undefined +): T | ((ownerState: ListItemTextOwnerState) => T) { + if (typeof slotProp === 'function') { + return (ownerState: ListItemTextOwnerState) => ({ + noWrap: true, + ...slotProp(ownerState) + }); + } + return { noWrap: true, ...slotProp } as T; +} + +export function ListItemText({ + slotProps, + ...props +}: MuiListItemTextProps): JSX.Element { + const { primary, secondary } = slotProps ?? {}; + + return ( + + ); }