diff --git a/packages/shared/src/components/highlights/HighlightsPage.spec.tsx b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx new file mode 100644 index 0000000000..8d8d317a6b --- /dev/null +++ b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { useQuery } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import { HighlightsPage } from './HighlightsPage'; + +jest.mock('@tanstack/react-query', () => ({ + ...jest.requireActual('@tanstack/react-query'), + useQuery: jest.fn(), +})); + +jest.mock('next/router', () => ({ + useRouter: jest.fn(), +})); + +const mockUseQuery = useQuery as jest.Mock; +const mockUseRouter = useRouter as jest.Mock; + +describe('HighlightsPage', () => { + const highlightsPageData = { + majorHeadlines: { + edges: [], + }, + channelConfigurations: [ + { + channel: 'vibes', + displayName: 'Agentic', + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseRouter.mockReturnValue({ + pathname: '/highlights', + query: {}, + push: jest.fn(), + }); + }); + + it('should use the agentic alias for the vibes channel tab URL', () => { + mockUseQuery.mockReturnValue({ + data: highlightsPageData, + isFetching: false, + }); + + render(); + + expect(screen.getByRole('link', { name: 'Agentic' })).toHaveAttribute( + 'href', + '/highlights/agentic', + ); + }); + + it('should keep the agentic tab selected for the alias route', () => { + mockUseRouter.mockReturnValue({ + pathname: '/highlights/[channel]', + query: { channel: 'agentic' }, + push: jest.fn(), + }); + mockUseQuery.mockReturnValue({ + data: highlightsPageData, + isFetching: false, + }); + + render(); + + expect(screen.getByText('Agentic')).toHaveClass('bg-theme-active'); + }); +}); diff --git a/packages/shared/src/components/highlights/HighlightsPage.tsx b/packages/shared/src/components/highlights/HighlightsPage.tsx index 194be925d8..c072658085 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.tsx @@ -10,6 +10,10 @@ import { channelHighlightsFeedQueryOptions, highlightsPageQueryOptions, } from '../../graphql/highlights'; +import { + getHighlightsChannelSlug, + resolveHighlightsChannelSlug, +} from '../../lib/highlights'; import { Tab, TabContainer } from '../tabs/TabContainer'; import { DigestCTA } from './DigestCTA'; import { HighlightItem } from './HighlightItem'; @@ -127,6 +131,9 @@ const ChannelTab = ({ export const HighlightsPage = (): ReactElement => { const router = useRouter(); const channel = getSingleQueryParam(router.query.channel); + const resolvedChannel = channel + ? resolveHighlightsChannelSlug(channel) + : undefined; const expandedId = getSingleQueryParam(router.query.highlight); const { data, isFetching } = useQuery(highlightsPageQueryOptions()); @@ -138,7 +145,7 @@ export const HighlightsPage = (): ReactElement => { const majorLoading = isFetching && !data; const activeTab = - channels.find((c) => c.channel === channel)?.displayName ?? + channels.find((c) => c.channel === resolvedChannel)?.displayName ?? MAJOR_HEADLINES_LABEL; return ( @@ -176,7 +183,9 @@ export const HighlightsPage = (): ReactElement => { diff --git a/packages/shared/src/lib/highlights.ts b/packages/shared/src/lib/highlights.ts new file mode 100644 index 0000000000..4c519fad2f --- /dev/null +++ b/packages/shared/src/lib/highlights.ts @@ -0,0 +1,8 @@ +const AGENTIC_HIGHLIGHTS_CHANNEL = 'vibes'; +const AGENTIC_HIGHLIGHTS_SLUG = 'agentic'; + +export const getHighlightsChannelSlug = (channel: string): string => + channel === AGENTIC_HIGHLIGHTS_CHANNEL ? AGENTIC_HIGHLIGHTS_SLUG : channel; + +export const resolveHighlightsChannelSlug = (slug: string): string => + slug === AGENTIC_HIGHLIGHTS_SLUG ? AGENTIC_HIGHLIGHTS_CHANNEL : slug; diff --git a/packages/webapp/__tests__/HighlightsChannelStaticProps.spec.ts b/packages/webapp/__tests__/HighlightsChannelStaticProps.spec.ts new file mode 100644 index 0000000000..1c8883cc97 --- /dev/null +++ b/packages/webapp/__tests__/HighlightsChannelStaticProps.spec.ts @@ -0,0 +1,68 @@ +import { gqlClient } from '@dailydotdev/shared/src/graphql/common'; +import { + HIGHLIGHTS_PAGE_QUERY, + POST_HIGHLIGHTS_FEED_QUERY, +} from '@dailydotdev/shared/src/graphql/highlights'; +import { getStaticProps } from '../pages/highlights/[channel]'; + +jest.mock('@dailydotdev/shared/src/graphql/common', () => { + const actual = jest.requireActual('@dailydotdev/shared/src/graphql/common'); + + return { + ...actual, + gqlClient: { + request: jest.fn(), + }, + }; +}); + +const mockRequest = gqlClient.request as jest.Mock; + +describe('highlights channel static props', () => { + beforeEach(() => { + mockRequest.mockReset(); + }); + + it('should resolve the agentic route alias to the vibes channel', async () => { + mockRequest.mockImplementation((query: string, variables?: object) => { + if (query === HIGHLIGHTS_PAGE_QUERY) { + return Promise.resolve({ + majorHeadlines: { + edges: [], + pageInfo: { + endCursor: null, + hasNextPage: false, + }, + }, + channelConfigurations: [ + { + channel: 'vibes', + displayName: 'Agentic', + }, + ], + }); + } + + if (query === POST_HIGHLIGHTS_FEED_QUERY) { + expect(variables).toEqual({ channel: 'vibes' }); + + return Promise.resolve({ + postHighlights: [], + }); + } + + return Promise.reject(new Error('Unexpected query')); + }); + + const result = await getStaticProps({ + params: { channel: 'agentic' }, + } as never); + + expect(result).toMatchObject({ + props: { + dehydratedState: expect.any(Object), + }, + revalidate: 60, + }); + }); +}); diff --git a/packages/webapp/pages/highlights/[channel].tsx b/packages/webapp/pages/highlights/[channel].tsx index a825e72b69..2d5d35263a 100644 --- a/packages/webapp/pages/highlights/[channel].tsx +++ b/packages/webapp/pages/highlights/[channel].tsx @@ -13,6 +13,7 @@ import { highlightsPageQueryOptions, } from '@dailydotdev/shared/src/graphql/highlights'; import { HighlightsPage } from '@dailydotdev/shared/src/components/highlights/HighlightsPage'; +import { resolveHighlightsChannelSlug } from '@dailydotdev/shared/src/lib/highlights'; import { getLayout as getFooterNavBarLayout } from '../../components/layouts/FooterNavBarLayout'; import { getLayout } from '../../components/layouts/MainLayout'; import { defaultOpenGraph, defaultSeo } from '../../next-seo'; @@ -64,7 +65,10 @@ export async function getStaticProps({ }: GetStaticPropsContext): Promise< GetStaticPropsResult > { - const channel = params?.channel; + const channelSlug = params?.channel; + const channel = channelSlug + ? resolveHighlightsChannelSlug(channelSlug) + : undefined; if (!channel) { return {