11/**
2- * PDFExportOrchestrator tests.
2+ * PDFExportOrchestrator component tests.
33 *
44 * Site Kit by Google, Copyright 2026 Google LLC
55 *
@@ -26,6 +26,7 @@ import { pdf } from '@react-pdf/renderer';
2626 */
2727import { VIEW_CONTEXT_MAIN_DASHBOARD } from '@/js/googlesitekit/constants' ;
2828import { CORE_PDF } from '@/js/googlesitekit/datastore/pdf/constants' ;
29+ import { CORE_SITE } from '@/js/googlesitekit/datastore/site/constants' ;
2930import { CORE_USER } from '@/js/googlesitekit/datastore/user/constants' ;
3031import { CORE_WIDGETS } from '@/js/googlesitekit/widgets/datastore/constants' ;
3132import { CONTEXT_MAIN_DASHBOARD_TRAFFIC } from '@/js/googlesitekit/widgets/default-contexts' ;
@@ -39,6 +40,13 @@ import {
3940import { registerPDFFonts } from './pdf-fonts-react' ;
4041import PDFExportOrchestrator from './PDFExportOrchestrator' ;
4142
43+ // `@react-pdf/renderer` is auto-mocked via `__mocks__/@react-pdf/renderer.js`,
44+ // which exports `pdf` as a `jest.fn()` returning a stub `toBlob()`. That lets
45+ // the orchestrator's BUILDING stage resolve instantly so we can capture the
46+ // element handed to `pdf()`, all without loading fontkit (which needs Node APIs
47+ // JSDOM lacks). The mock also renders the report primitives as host elements,
48+ // so `DashboardReport`/`PDFFooter` import cleanly.
49+
4250// Stub the download trigger so the anchor click does not attempt a JSDOM
4351// navigation; the filename helper stays real.
4452jest . mock ( './pdf-utils' , ( ) => ( {
@@ -55,14 +63,18 @@ function NullComponent() {
5563}
5664
5765describe ( 'PDFExportOrchestrator' , ( ) => {
66+ const ADMIN_URL = 'http://example.com/wp-admin/' ;
5867 let registry : ReturnType < typeof createTestRegistry > ;
5968 const OriginalAbortController = global . AbortController ;
6069 const originalCreateObjectURL = global . URL . createObjectURL ;
6170 const originalRevokeObjectURL = global . URL . revokeObjectURL ;
6271
6372 beforeEach ( ( ) => {
6473 registry = createTestRegistry ( ) ;
65- provideSiteInfo ( registry , { siteName : 'Example Site' } ) ;
74+ provideSiteInfo ( registry , {
75+ adminURL : ADMIN_URL ,
76+ siteName : 'Example Site' ,
77+ } ) ;
6678 provideUserInfo ( registry ) ;
6779 registry . dispatch ( CORE_USER ) . setReferenceDate ( '2021-01-10' ) ;
6880 registry . dispatch ( CORE_USER ) . setDateRange ( 'last-28-days' ) ;
@@ -113,6 +125,31 @@ describe( 'PDFExportOrchestrator', () => {
113125 } ) ;
114126 }
115127
128+ /**
129+ * Renders the orchestrator and resolves with the React element passed to
130+ * the mocked `pdf()` once the BUILDING stage runs.
131+ *
132+ * @since n.e.x.t
133+ *
134+ * @return The captured `DashboardReport` element.
135+ */
136+ async function renderAndCaptureReport ( ) {
137+ const getData : jest . Mock = jest . fn ( ( ) =>
138+ Promise . resolve ( { data : { totalUsers : 100 } } )
139+ ) ;
140+ registerPDFWidget ( 'trafficArea' , 'trafficWidget' , getData ) ;
141+ registry . dispatch ( CORE_PDF ) . setSelection ( {
142+ contextSlugs : [ CONTEXT_MAIN_DASHBOARD_TRAFFIC ] ,
143+ widgetSlugs : [ ] ,
144+ } ) ;
145+
146+ renderOrchestrator ( ) ;
147+
148+ await waitFor ( ( ) => expect ( pdf ) . toHaveBeenCalled ( ) ) ;
149+
150+ return ( pdf as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
151+ }
152+
116153 // The orchestrator creates its own `AbortController` on mount and keeps it
117154 // private. To read that controller's signal in a test, replace the global
118155 // constructor with a subclass that records each new instance. The records
@@ -134,7 +171,35 @@ describe( 'PDFExportOrchestrator', () => {
134171 return controllers ;
135172 }
136173
137- it ( 'loads the selected widget data with PDF-adjusted dates and builds the PDF' , async ( ) => {
174+ it ( 'should pass the resolved dashboard, help center, and privacy policy URLs to DashboardReport' , async ( ) => {
175+ const reportElement = await renderAndCaptureReport ( ) ;
176+
177+ expect ( reportElement . props . dashboardURL ) . toBe (
178+ registry . select ( CORE_SITE ) . getGoLinkURL ( 'dashboard' )
179+ ) ;
180+ expect ( reportElement . props . helpCenterURL ) . toBe (
181+ 'https://sitekit.withgoogle.com/support/?doc=get-support'
182+ ) ;
183+ expect ( reportElement . props . privacyPolicyURL ) . toBe (
184+ 'https://policies.google.com/privacy'
185+ ) ;
186+ } ) ;
187+
188+ it ( 'should build each URL via getGoLinkURL with the expected handler key' , async ( ) => {
189+ const reportElement = await renderAndCaptureReport ( ) ;
190+
191+ expect ( reportElement . props . dashboardURL ) . toBe (
192+ `${ ADMIN_URL } index.php?action=googlesitekit_go&to=dashboard`
193+ ) ;
194+ expect ( reportElement . props . helpCenterURL ) . toBe (
195+ 'https://sitekit.withgoogle.com/support/?doc=get-support'
196+ ) ;
197+ expect ( reportElement . props . privacyPolicyURL ) . toBe (
198+ 'https://policies.google.com/privacy'
199+ ) ;
200+ } ) ;
201+
202+ it ( 'should load the selected widget data with PDF-adjusted dates and build the PDF' , async ( ) => {
138203 const getData : jest . Mock = jest . fn ( ( ) =>
139204 Promise . resolve ( { data : { totalUsers : 100 } } )
140205 ) ;
@@ -161,7 +226,7 @@ describe( 'PDFExportOrchestrator', () => {
161226 expect ( pdf ) . toHaveBeenCalledTimes ( 1 ) ;
162227 } ) ;
163228
164- it ( 'transitions to error and does not build a PDF when the only widget fails' , async ( ) => {
229+ it ( 'should transition to error and not build a PDF when the only widget fails' , async ( ) => {
165230 const getData = jest . fn ( ( ) =>
166231 Promise . reject ( new Error ( 'report failed' ) )
167232 ) ;
@@ -180,7 +245,7 @@ describe( 'PDFExportOrchestrator', () => {
180245 expect ( pdf ) . not . toHaveBeenCalled ( ) ;
181246 } ) ;
182247
183- it ( 'isolates a failing widget when another widget succeeds' , async ( ) => {
248+ it ( 'should isolate a failing widget when another widget succeeds' , async ( ) => {
184249 const failing = jest . fn ( ( ) =>
185250 Promise . reject ( new Error ( 'report failed' ) )
186251 ) ;
@@ -205,7 +270,7 @@ describe( 'PDFExportOrchestrator', () => {
205270 expect ( pdf ) . toHaveBeenCalledTimes ( 1 ) ;
206271 } ) ;
207272
208- it ( 'passes the email reporting golink URL to the report document' , async ( ) => {
273+ it ( 'should pass the email reporting golink URL to the report document' , async ( ) => {
209274 const getData : jest . Mock = jest . fn ( ( ) =>
210275 Promise . resolve ( { data : { totalUsers : 100 } } )
211276 ) ;
@@ -227,7 +292,7 @@ describe( 'PDFExportOrchestrator', () => {
227292 ) ;
228293 } ) ;
229294
230- it ( 'registers the PDF fonts before rendering the document' , async ( ) => {
295+ it ( 'should register the PDF fonts before rendering the document' , async ( ) => {
231296 const getData : jest . Mock = jest . fn ( ( ) =>
232297 Promise . resolve ( { data : { totalUsers : 100 } } )
233298 ) ;
@@ -249,7 +314,7 @@ describe( 'PDFExportOrchestrator', () => {
249314 ) . toBeLessThan ( ( pdf as jest . Mock ) . mock . invocationCallOrder [ 0 ] ) ;
250315 } ) ;
251316
252- it ( 'transitions to error and does not build a PDF when font registration fails' , async ( ) => {
317+ it ( 'should transition to error and not build a PDF when font registration fails' , async ( ) => {
253318 jest . mocked ( registerPDFFonts ) . mockImplementationOnce ( ( ) => {
254319 throw new Error ( 'font registration failed' ) ;
255320 } ) ;
0 commit comments