From e298222dd9b74bc48ff822b56d211b44963dc66f Mon Sep 17 00:00:00 2001 From: idoshamun Date: Sat, 18 Apr 2026 08:45:13 +0000 Subject: [PATCH 1/2] fix: correct agentic highlights route --- .../highlights/HighlightsPage.spec.tsx | 52 ++++++++++++++ .../components/highlights/HighlightsPage.tsx | 11 ++- packages/shared/src/graphql/highlights.ts | 17 +++++ .../HighlightsChannelStaticProps.spec.ts | 68 +++++++++++++++++++ .../webapp/pages/highlights/[channel].tsx | 6 +- 5 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 packages/shared/src/components/highlights/HighlightsPage.spec.tsx create mode 100644 packages/webapp/__tests__/HighlightsChannelStaticProps.spec.ts 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..163ec6e8c1 --- /dev/null +++ b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx @@ -0,0 +1,52 @@ +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', () => { + 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: { + majorHeadlines: { + edges: [], + }, + channelConfigurations: [ + { + channel: 'vibes', + displayName: 'Agentic', + }, + ], + }, + isFetching: false, + }); + + render(); + + expect(screen.getByRole('link', { name: 'Agentic' })).toHaveAttribute( + 'href', + '/highlights/agentic', + ); + }); +}); diff --git a/packages/shared/src/components/highlights/HighlightsPage.tsx b/packages/shared/src/components/highlights/HighlightsPage.tsx index 194be925d8..78e10e66b4 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.tsx @@ -8,7 +8,9 @@ import type { } from '../../graphql/highlights'; import { channelHighlightsFeedQueryOptions, + getHighlightsChannelSlug, highlightsPageQueryOptions, + resolveHighlightsChannelSlug, } from '../../graphql/highlights'; import { Tab, TabContainer } from '../tabs/TabContainer'; import { DigestCTA } from './DigestCTA'; @@ -127,6 +129,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 +143,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 +181,9 @@ export const HighlightsPage = (): ReactElement => { diff --git a/packages/shared/src/graphql/highlights.ts b/packages/shared/src/graphql/highlights.ts index e9d18575c8..bfcaac8cc8 100644 --- a/packages/shared/src/graphql/highlights.ts +++ b/packages/shared/src/graphql/highlights.ts @@ -163,6 +163,23 @@ export interface HighlightsPageData { channelConfigurations: ChannelConfiguration[]; } +const HIGHLIGHTS_CHANNEL_TO_SLUG: Record = { + vibes: 'agentic', +}; + +const HIGHLIGHTS_SLUG_TO_CHANNEL = Object.fromEntries( + Object.entries(HIGHLIGHTS_CHANNEL_TO_SLUG).map(([channel, slug]) => [ + slug, + channel, + ]), +); + +export const getHighlightsChannelSlug = (channel: string): string => + HIGHLIGHTS_CHANNEL_TO_SLUG[channel] ?? channel; + +export const resolveHighlightsChannelSlug = (slug: string): string => + HIGHLIGHTS_SLUG_TO_CHANNEL[slug] ?? slug; + export const HIGHLIGHTS_PAGE_QUERY = gql` query HighlightsPage($first: Int, $after: String) { majorHeadlines(first: $first, after: $after) { 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..ac8684e792 100644 --- a/packages/webapp/pages/highlights/[channel].tsx +++ b/packages/webapp/pages/highlights/[channel].tsx @@ -11,6 +11,7 @@ import { dehydrate, QueryClient } from '@tanstack/react-query'; import { channelHighlightsFeedQueryOptions, highlightsPageQueryOptions, + resolveHighlightsChannelSlug, } from '@dailydotdev/shared/src/graphql/highlights'; import { HighlightsPage } from '@dailydotdev/shared/src/components/highlights/HighlightsPage'; import { getLayout as getFooterNavBarLayout } from '../../components/layouts/FooterNavBarLayout'; @@ -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 { From 9226cb6cf6ec076eacf4da553a971ecec0851474 Mon Sep 17 00:00:00 2001 From: idoshamun Date: Sat, 18 Apr 2026 08:48:52 +0000 Subject: [PATCH 2/2] refactor: simplify highlights route aliasing --- .../highlights/HighlightsPage.spec.tsx | 40 ++++++++++++++----- .../components/highlights/HighlightsPage.tsx | 6 ++- packages/shared/src/graphql/highlights.ts | 17 -------- packages/shared/src/lib/highlights.ts | 8 ++++ .../webapp/pages/highlights/[channel].tsx | 2 +- 5 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 packages/shared/src/lib/highlights.ts diff --git a/packages/shared/src/components/highlights/HighlightsPage.spec.tsx b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx index 163ec6e8c1..8d8d317a6b 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.spec.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx @@ -17,6 +17,18 @@ 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({ @@ -28,17 +40,7 @@ describe('HighlightsPage', () => { it('should use the agentic alias for the vibes channel tab URL', () => { mockUseQuery.mockReturnValue({ - data: { - majorHeadlines: { - edges: [], - }, - channelConfigurations: [ - { - channel: 'vibes', - displayName: 'Agentic', - }, - ], - }, + data: highlightsPageData, isFetching: false, }); @@ -49,4 +51,20 @@ describe('HighlightsPage', () => { '/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 78e10e66b4..c072658085 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.tsx @@ -8,10 +8,12 @@ import type { } from '../../graphql/highlights'; import { channelHighlightsFeedQueryOptions, - getHighlightsChannelSlug, highlightsPageQueryOptions, - resolveHighlightsChannelSlug, } from '../../graphql/highlights'; +import { + getHighlightsChannelSlug, + resolveHighlightsChannelSlug, +} from '../../lib/highlights'; import { Tab, TabContainer } from '../tabs/TabContainer'; import { DigestCTA } from './DigestCTA'; import { HighlightItem } from './HighlightItem'; diff --git a/packages/shared/src/graphql/highlights.ts b/packages/shared/src/graphql/highlights.ts index bfcaac8cc8..e9d18575c8 100644 --- a/packages/shared/src/graphql/highlights.ts +++ b/packages/shared/src/graphql/highlights.ts @@ -163,23 +163,6 @@ export interface HighlightsPageData { channelConfigurations: ChannelConfiguration[]; } -const HIGHLIGHTS_CHANNEL_TO_SLUG: Record = { - vibes: 'agentic', -}; - -const HIGHLIGHTS_SLUG_TO_CHANNEL = Object.fromEntries( - Object.entries(HIGHLIGHTS_CHANNEL_TO_SLUG).map(([channel, slug]) => [ - slug, - channel, - ]), -); - -export const getHighlightsChannelSlug = (channel: string): string => - HIGHLIGHTS_CHANNEL_TO_SLUG[channel] ?? channel; - -export const resolveHighlightsChannelSlug = (slug: string): string => - HIGHLIGHTS_SLUG_TO_CHANNEL[slug] ?? slug; - export const HIGHLIGHTS_PAGE_QUERY = gql` query HighlightsPage($first: Int, $after: String) { majorHeadlines(first: $first, after: $after) { 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/pages/highlights/[channel].tsx b/packages/webapp/pages/highlights/[channel].tsx index ac8684e792..2d5d35263a 100644 --- a/packages/webapp/pages/highlights/[channel].tsx +++ b/packages/webapp/pages/highlights/[channel].tsx @@ -11,9 +11,9 @@ import { dehydrate, QueryClient } from '@tanstack/react-query'; import { channelHighlightsFeedQueryOptions, highlightsPageQueryOptions, - resolveHighlightsChannelSlug, } 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';