Skip to content
Merged
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
10 changes: 7 additions & 3 deletions packages/shared/src/components/sidebar/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export function Section({
// persisted `flag` (e.g. the settings panel groups) — otherwise the
// collapse never visibly happens.
const [isVisible, setIsVisible] = useState(initialIsVisible);
const shouldRenderItems = !title || isVisible || shouldAlwaysBeVisible;
const hasVisibleItems = items.some((item) => !item.isSeparator);
const shouldRenderHeader =
!!title && (sidebarExpanded || (shouldRenderItems && hasVisibleItems));

const toggleFlag = () => {
const nextIsVisible = !isVisible;
Expand All @@ -75,9 +79,9 @@ export function Section({

return (
<NavSection className={classNames('group/section mt-1', className)}>
{title && (
{shouldRenderHeader && (
<NavHeader className="relative hidden laptop:flex">
{/* Divider shown when a collapsible (titled) section is collapsed */}
{/* Divider shown when a visible titled section is collapsed */}
<div
className={classNames(
'absolute inset-x-0 flex items-center justify-center px-2 transition-opacity duration-300',
Expand Down Expand Up @@ -161,7 +165,7 @@ export function Section({
// only toggle). A flagged-but-title-less section — e.g. the Squads
// and Saved panels — would otherwise get stuck hidden when its flag
// is false, with no arrow to re-expand it.
!title || isVisible || shouldAlwaysBeVisible
shouldRenderItems
? 'grid-rows-[1fr] opacity-100'
: 'grid-rows-[0fr] opacity-0',
)}
Expand Down
77 changes: 76 additions & 1 deletion packages/shared/src/components/sidebar/Sidebar.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { createTestSettings } from '../../../__tests__/fixture/settings';
import AuthContext from '../../contexts/AuthContext';
import defaultUser from '../../../__tests__/fixture/loggedUser';
import type { LoggedUser } from '../../lib/user';
import SettingsContext from '../../contexts/SettingsContext';
import SettingsContext, {
type SettingsContextData,
} from '../../contexts/SettingsContext';
import type { MockedGraphQLResponse } from '../../../__tests__/helpers/graphql';
import { mockGraphQL } from '../../../__tests__/helpers/graphql';
import { FEED_SETTINGS_QUERY } from '../../graphql/feedSettings';
Expand All @@ -17,6 +19,9 @@ import ProgressiveEnhancementContext from '../../contexts/ProgressiveEnhancement
import type { Alerts } from '../../graphql/alerts';
import { TOAST_NOTIF_KEY } from '../../hooks/useToastNotification';
import { SidebarDesktop } from './SidebarDesktop';
import type { Feed } from '../../graphql/feed';
import { FeedType } from '../../graphql/feed';
import type { SettingsFlags } from '../../graphql/settings';

let client: QueryClient;
const updateAlerts = jest.fn();
Expand All @@ -35,16 +40,46 @@ const createMockFeedSettings = () => ({

const defaultAlerts: Alerts = { filter: true };

type RenderComponentOptions = {
feeds?: Feed[];
settings?: Partial<SettingsContextData>;
};

const createCustomFeed = (): Feed => ({
id: 'cf1',
userId: 'u1',
flags: {
name: 'Cool feed',
},
slug: 'cool-feed-cf1',
createdAt: new Date('2024-01-01T00:00:00.000Z'),
type: FeedType.Custom,
});

const createSidebarFlags = (
flags: Partial<SettingsFlags> = {},
): SettingsFlags => ({
sidebarSquadExpanded: true,
sidebarCustomFeedsExpanded: true,
sidebarOtherExpanded: true,
sidebarResourcesExpanded: true,
sidebarBookmarksExpanded: true,
clickbaitShieldEnabled: true,
...flags,
});

const renderComponent = (
alertsData = defaultAlerts,
mocks: MockedGraphQLResponse[] = [createMockFeedSettings()],
user: LoggedUser | null | undefined = defaultUser,
sidebarExpanded = true,
options: RenderComponentOptions = {},
): RenderResult => {
const resolvedUser = user === null ? undefined : user;
const settingsContext = createTestSettings({
sidebarExpanded,
toggleSidebarExpanded,
...options.settings,
});
client = new QueryClient();
client.setQueryData(TOAST_NOTIF_KEY, null);
Expand All @@ -70,6 +105,7 @@ const renderComponent = (
tokenRefreshed: true,
getRedirectUri: jest.fn(),
closeLogin: jest.fn(),
feeds: options.feeds,
}}
>
<ProgressiveEnhancementContext.Provider
Expand Down Expand Up @@ -117,6 +153,45 @@ it('should show the sidebar as closed if user has this set', async () => {
expect(section).toHaveClass('opacity-0');
});

it('should not render a collapsed divider for empty custom feeds', async () => {
renderComponent(defaultAlerts, [], null, false);

await screen.findByLabelText('Find Squads');

expect(screen.getAllByRole('separator')).toHaveLength(3);
});

it('should render a collapsed divider for custom feeds with items', async () => {
renderComponent(defaultAlerts, [], null, false, {
feeds: [createCustomFeed()],
});

await screen.findByLabelText('Cool feed');

expect(screen.getAllByRole('separator')).toHaveLength(4);
});

it('should keep the expanded empty custom feeds header add affordance', async () => {
renderComponent();

const section = await screen.findByText('Feeds');
expect(section).toBeInTheDocument();
expect(screen.getByLabelText('Add to Feeds')).toBeInTheDocument();
});

it('should not render a collapsed divider for a flag-collapsed section', async () => {
renderComponent(defaultAlerts, [], null, false, {
feeds: [createCustomFeed()],
settings: {
flags: createSidebarFlags({ sidebarCustomFeedsExpanded: false }),
},
});

await screen.findByLabelText('Find Squads');

expect(screen.getAllByRole('separator')).toHaveLength(3);
});

it('should show the For You items if the user has filters', async () => {
renderComponent({ filter: false });
const section = await screen.findByText('For You');
Expand Down
Loading