diff --git a/projects/packages/premium-analytics/changelog/update-pa-chart-empty-state-detail-widgets b/projects/packages/premium-analytics/changelog/update-pa-chart-empty-state-detail-widgets new file mode 100644 index 000000000000..c5e6c59c7560 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/update-pa-chart-empty-state-detail-widgets @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Dashboard: Show the no-results message in the Post views, Author performance, Video views and Store performance charts for a period with no data, instead of a flat zero line. diff --git a/projects/packages/premium-analytics/widgets/author-performance/__tests__/author-performance.test.tsx b/projects/packages/premium-analytics/widgets/author-performance/__tests__/author-performance.test.tsx index cb3c915e547c..3824166bd4e6 100644 --- a/projects/packages/premium-analytics/widgets/author-performance/__tests__/author-performance.test.tsx +++ b/projects/packages/premium-analytics/widgets/author-performance/__tests__/author-performance.test.tsx @@ -4,6 +4,7 @@ import { getDefaultQueryParams, queryClient } from '@jetpack-premium-analytics/data'; import { render, screen } from '@testing-library/react'; import apiFetch from '@wordpress/api-fetch'; +import type { ReactNode } from 'react'; /** * Internal dependencies */ @@ -18,6 +19,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { MetricTabsChart: ( { metrics, chartType, + empty, }: { metrics: { key: string; @@ -27,6 +29,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { countLabel?: ( count: number ) => string; }[]; chartType?: string; + empty?: ReactNode; } ) => (
( { dates: metric.current.map( point => point.date.toISOString().slice( 0, 10 ) ), } ) ) ) } - /> + > + { empty } +
), } ) ); @@ -200,6 +205,19 @@ describe( 'AuthorPerformanceWidget', () => { ); } ); + it( 'shows the no-results message in the chart for a window without views', async () => { + mockApiFetch.mockResolvedValue( { + ...TOP_AUTHORS_DAYS, + days: { '2026-07-02': TOP_AUTHORS_DAYS.days[ '2026-07-02' ] }, + } ); + + render( ); + + await expect( + screen.findByText( 'We couldn’t find results for this time period.' ) + ).resolves.toBeInTheDocument(); + } ); + it( 'renders the scopeless empty state and makes no request without an author scope', async () => { render( ); diff --git a/projects/packages/premium-analytics/widgets/author-performance/render.tsx b/projects/packages/premium-analytics/widgets/author-performance/render.tsx index 89bd7e04e91c..e8b6692c5d00 100644 --- a/projects/packages/premium-analytics/widgets/author-performance/render.tsx +++ b/projects/packages/premium-analytics/widgets/author-performance/render.tsx @@ -4,6 +4,7 @@ import { STATS_CHART_BUCKET_PERIODS, toAuthorId } from '@jetpack-premium-analytics/data'; import { reports } from '@jetpack-premium-analytics/icons'; import { + ChartEmptyState, MetricTabsChart, MetricTabsChartSkeleton, WidgetRoot, @@ -97,6 +98,7 @@ function AuthorPerformanceInner( { chartType }: AuthorPerformanceInnerProps ) { metrics={ metricTabs } dataFormat={ DATA_FORMAT } chartType={ chartType } + empty={ } /> diff --git a/projects/packages/premium-analytics/widgets/post-views/__tests__/post-views.test.tsx b/projects/packages/premium-analytics/widgets/post-views/__tests__/post-views.test.tsx index 0c821939b561..2acb485fe26b 100644 --- a/projects/packages/premium-analytics/widgets/post-views/__tests__/post-views.test.tsx +++ b/projects/packages/premium-analytics/widgets/post-views/__tests__/post-views.test.tsx @@ -5,6 +5,7 @@ import { getDefaultQueryParams, queryClient } from '@jetpack-premium-analytics/d import { render, screen } from '@testing-library/react'; import apiFetch from '@wordpress/api-fetch'; import { getSettings, setSettings } from '@wordpress/date'; +import type { ReactNode } from 'react'; /** * Internal dependencies */ @@ -19,6 +20,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { MetricTabsChart: ( { metrics, chartType, + empty, }: { metrics: { key: string; @@ -29,6 +31,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { dataFormat?: { type: string }; }[]; chartType?: string; + empty?: ReactNode; } ) => (
( { days: metric.current.map( point => point.date.getDate() ), } ) ) ) } - /> + > + { empty } +
), } ) ); @@ -200,6 +205,16 @@ describe( 'PostViewsWidget', () => { expect( mockApiFetch ).toHaveBeenCalledTimes( 1 ); } ); + it( 'shows the no-results message in the chart for a window without views', async () => { + mockApiFetch.mockResolvedValue( { data: [] } ); + + render( ); + + await expect( + screen.findByText( 'We couldn’t find results for this time period.' ) + ).resolves.toBeInTheDocument(); + } ); + it( 'renders the scopeless empty state and makes no request without a post scope', async () => { render( ); diff --git a/projects/packages/premium-analytics/widgets/post-views/render.tsx b/projects/packages/premium-analytics/widgets/post-views/render.tsx index 3002862f8a75..1ea46713463e 100644 --- a/projects/packages/premium-analytics/widgets/post-views/render.tsx +++ b/projects/packages/premium-analytics/widgets/post-views/render.tsx @@ -4,6 +4,7 @@ import { STATS_CHART_BUCKET_PERIODS, toPostId } from '@jetpack-premium-analytics/data'; import { reports } from '@jetpack-premium-analytics/icons'; import { + ChartEmptyState, MetricTabsChart, MetricTabsChartSkeleton, WidgetRoot, @@ -97,6 +98,7 @@ function PostViewsInner( { chartType }: PostViewsInnerProps ) { metrics={ metricTabs } dataFormat={ DATA_FORMAT } chartType={ chartType } + empty={ } /> diff --git a/projects/packages/premium-analytics/widgets/store-performance/__tests__/render.test.tsx b/projects/packages/premium-analytics/widgets/store-performance/__tests__/render.test.tsx new file mode 100644 index 000000000000..6b0fc687c155 --- /dev/null +++ b/projects/packages/premium-analytics/widgets/store-performance/__tests__/render.test.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; +import type { ReactNode } from 'react'; +/** + * Internal dependencies + */ +import StorePerformanceRender from '../render'; + +const mockEmptyReport = { + primary: { data: { summary: {}, data: [] } }, + comparison: { data: { summary: {}, data: [] } }, + timezone: 'UTC', + isLoading: false, + isFetching: false, + isError: false, + hasData: false, + refetch: jest.fn(), +}; + +jest.mock( '@jetpack-premium-analytics/data', () => ( { + ...jest.requireActual( '@jetpack-premium-analytics/data' ), + useReportOrders: () => mockEmptyReport, + useReportVisitors: () => mockEmptyReport, + useReportConversionRate: () => mockEmptyReport, + useReportCustomersByDate: () => mockEmptyReport, +} ) ); + +// The chart itself is visx SVG rendering, outside this widget's concern. +jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { + ...jest.requireActual( '@jetpack-premium-analytics/widgets-toolkit' ), + MetricTabsChart: ( { empty }: { empty?: ReactNode } ) => ( +
{ empty }
+ ), +} ) ); + +// WidgetRoot reads URL search params as a fallback for report params; outside +// a matched route the real hook warns and throws. +jest.mock( '@wordpress/route', () => jest.requireActual( '../../test-utils' ).mockWordPressRoute ); + +describe( 'StorePerformanceRender', () => { + it( 'shows the no-results message in the chart for a store without orders or visits', () => { + render( ); + + expect( + screen.getByText( 'We couldn’t find results for this time period.' ) + ).toBeInTheDocument(); + } ); +} ); diff --git a/projects/packages/premium-analytics/widgets/store-performance/render.tsx b/projects/packages/premium-analytics/widgets/store-performance/render.tsx index 57e91c5d732f..4b58b9b5ecf9 100644 --- a/projects/packages/premium-analytics/widgets/store-performance/render.tsx +++ b/projects/packages/premium-analytics/widgets/store-performance/render.tsx @@ -7,6 +7,7 @@ import { } from '@jetpack-premium-analytics/data'; import { BOOKINGS_FILTER, + ChartEmptyState, MetricTabsChart, MetricTabsChartSkeleton, WidgetRoot, @@ -361,6 +362,7 @@ function StorePerformanceContent() { metrics={ metricTabs } dataFormat={ DEFAULT_DATA_FORMAT } groupLabel={ __( 'Store metric', 'jetpack-premium-analytics-pkg' ) } + empty={ } /> diff --git a/projects/packages/premium-analytics/widgets/video-detail-views-performance/__tests__/video-detail-views-performance.test.tsx b/projects/packages/premium-analytics/widgets/video-detail-views-performance/__tests__/video-detail-views-performance.test.tsx index 07c149caad23..aa54e8ca91e5 100644 --- a/projects/packages/premium-analytics/widgets/video-detail-views-performance/__tests__/video-detail-views-performance.test.tsx +++ b/projects/packages/premium-analytics/widgets/video-detail-views-performance/__tests__/video-detail-views-performance.test.tsx @@ -4,6 +4,7 @@ import { getDefaultQueryParams, queryClient } from '@jetpack-premium-analytics/data'; import { render, screen } from '@testing-library/react'; import apiFetch from '@wordpress/api-fetch'; +import type { ReactNode } from 'react'; /** * Internal dependencies */ @@ -18,6 +19,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { MetricTabsChart: ( { metrics, chartType, + empty, }: { metrics: { key: string; @@ -28,6 +30,7 @@ jest.mock( '@jetpack-premium-analytics/widgets-toolkit', () => ( { dataFormat?: { type: string }; }[]; chartType?: string; + empty?: ReactNode; } ) => (
( { days: metric.current.map( point => point.date.getDate() ), } ) ) ) } - /> + > + { empty } +
), } ) ); @@ -321,6 +326,16 @@ describe( 'VideoDetailViewsPerformanceWidget', () => { expect( metrics[ 0 ].value ).toBe( 12 ); } ); + it( 'shows the no-results message in the chart for a window without views', async () => { + mockApiFetch.mockImplementation( respondByWindow( {} ) ); + + render( ); + + await expect( + screen.findByText( 'We couldn’t find results for this time period.' ) + ).resolves.toBeInTheDocument(); + } ); + it( 'renders the scopeless empty state and makes no request without a video scope', async () => { render( ); diff --git a/projects/packages/premium-analytics/widgets/video-detail-views-performance/render.tsx b/projects/packages/premium-analytics/widgets/video-detail-views-performance/render.tsx index f305b4c1be81..d1382d389fc1 100644 --- a/projects/packages/premium-analytics/widgets/video-detail-views-performance/render.tsx +++ b/projects/packages/premium-analytics/widgets/video-detail-views-performance/render.tsx @@ -3,6 +3,7 @@ */ import { STATS_CHART_BUCKET_PERIODS, toPostId } from '@jetpack-premium-analytics/data'; import { + ChartEmptyState, MetricTabsChart, MetricTabsChartSkeleton, WidgetRoot, @@ -81,6 +82,7 @@ function VideoDetailViewsPerformanceInner( { chartType }: VideoDetailViewsPerfor dataFormat={ COUNT_FORMAT } chartType={ chartType } groupLabel={ groupLabel } + empty={ } />