From 99147936e77c203bbdb3a42a5c263734878d6617 Mon Sep 17 00:00:00 2001
From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com>
Date: Tue, 4 Nov 2025 14:29:22 +0100
Subject: [PATCH 1/3] Add Jetpack connection status check for site sync
---
.../tests/reconcile-connected-sites.test.ts | 2 ++
.../use-connected-sites-operations.test.tsx | 4 +++
src/hooks/use-fetch-wpcom-sites/index.tsx | 4 +++
.../reconcile-connected-sites.tsx | 1 +
src/hooks/use-fetch-wpcom-sites/types.ts | 4 ++-
.../components/sync-sites-modal-selector.tsx | 25 +++++++++++++++----
src/modules/sync/tests/index.test.tsx | 12 +++++++++
7 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/src/hooks/tests/reconcile-connected-sites.test.ts b/src/hooks/tests/reconcile-connected-sites.test.ts
index 646a2fd719..1e808ee79d 100644
--- a/src/hooks/tests/reconcile-connected-sites.test.ts
+++ b/src/hooks/tests/reconcile-connected-sites.test.ts
@@ -14,6 +14,7 @@ describe( 'reconcileConnectedSites', () => {
syncSupport: 'already-connected',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
];
const freshWpComSites: SyncSite[] = [
@@ -27,6 +28,7 @@ describe( 'reconcileConnectedSites', () => {
syncSupport: 'unsupported',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
];
const result = reconcileConnectedSites( connectedSites, freshWpComSites );
diff --git a/src/hooks/tests/use-connected-sites-operations.test.tsx b/src/hooks/tests/use-connected-sites-operations.test.tsx
index ff18f1d1d5..21ab2ded9f 100644
--- a/src/hooks/tests/use-connected-sites-operations.test.tsx
+++ b/src/hooks/tests/use-connected-sites-operations.test.tsx
@@ -27,6 +27,7 @@ const mockConnectedWpcomSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
{
id: 7,
@@ -38,6 +39,7 @@ const mockConnectedWpcomSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
];
@@ -52,6 +54,7 @@ const mockSyncSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
{
id: 9,
@@ -63,6 +66,7 @@ const mockSyncSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
},
];
diff --git a/src/hooks/use-fetch-wpcom-sites/index.tsx b/src/hooks/use-fetch-wpcom-sites/index.tsx
index cc226545e3..765c2add44 100644
--- a/src/hooks/use-fetch-wpcom-sites/index.tsx
+++ b/src/hooks/use-fetch-wpcom-sites/index.tsx
@@ -92,6 +92,9 @@ export function getSyncSupport( site: SitesEndpointSite, connectedSiteIds: numbe
if ( needsTransfer( site ) ) {
return 'needs-transfer';
}
+ if ( ! site.jetpack ) {
+ return 'jetpack-disconnected';
+ }
if ( connectedSiteIds.some( ( id ) => id === site.ID ) ) {
return 'already-connected';
}
@@ -114,6 +117,7 @@ function transformSingleSiteResponse(
syncSupport,
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: !! site.jetpack,
};
}
diff --git a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
index 18dafaff4e..b17df17bf9 100644
--- a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
+++ b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
@@ -28,6 +28,7 @@ export const reconcileConnectedSites = (
isStaging: site.isStaging,
isPressable: site.isPressable,
environmentType: site.environmentType,
+ hasJetpack: site.hasJetpack,
};
}, [] );
diff --git a/src/hooks/use-fetch-wpcom-sites/types.ts b/src/hooks/use-fetch-wpcom-sites/types.ts
index e6e58d080f..d717b4e6da 100644
--- a/src/hooks/use-fetch-wpcom-sites/types.ts
+++ b/src/hooks/use-fetch-wpcom-sites/types.ts
@@ -5,7 +5,8 @@ export type SyncSupport =
| 'already-connected'
| 'needs-upgrade'
| 'deleted'
- | 'missing-permissions';
+ | 'missing-permissions'
+ | 'jetpack-disconnected';
export type SyncSite = {
id: number;
@@ -18,4 +19,5 @@ export type SyncSite = {
syncSupport: SyncSupport;
lastPullTimestamp: string | null;
lastPushTimestamp: string | null;
+ hasJetpack: boolean;
};
diff --git a/src/modules/sync/components/sync-sites-modal-selector.tsx b/src/modules/sync/components/sync-sites-modal-selector.tsx
index c06e4b1f8b..9fdae40d4b 100644
--- a/src/modules/sync/components/sync-sites-modal-selector.tsx
+++ b/src/modules/sync/components/sync-sites-modal-selector.tsx
@@ -155,10 +155,11 @@ const getSortedSites = ( sites: SyncSite[] ) => {
syncable: 1,
'already-connected': 2,
deleted: 3,
- 'missing-permissions': 4,
- 'needs-transfer': 5,
- 'needs-upgrade': 6,
- unsupported: 7,
+ 'jetpack-disconnected': 4,
+ 'missing-permissions': 5,
+ 'needs-transfer': 6,
+ 'needs-upgrade': 7,
+ unsupported: 8,
};
return [ ...sites ].sort( ( a, b ) => order[ a.syncSupport ] - order[ b.syncSupport ] );
@@ -199,15 +200,18 @@ function SiteItem( {
onClick: () => void;
} ) {
const { __ } = useI18n();
+ const locale = useI18nLocale();
const isAlreadyConnected = site.syncSupport === 'already-connected';
const isSyncable = site.syncSupport === 'syncable';
const isNeedsTransfer = site.syncSupport === 'needs-transfer';
const isMissingPermissions = site.syncSupport === 'missing-permissions';
const needsUpgrade = site.syncSupport === 'needs-upgrade';
const isDeleted = site.syncSupport === 'deleted';
+ const isJetpackDisconnected = site.syncSupport === 'jetpack-disconnected';
const isUnsupported = site.syncSupport === 'unsupported';
const isPressable = site.isPressable;
- const isDisabled = isDeleted || isUnsupported || needsUpgrade || isMissingPermissions;
+ const isDisabled =
+ isDeleted || isJetpackDisconnected || isUnsupported || needsUpgrade || isMissingPermissions;
return (
) }
+ { isJetpackDisconnected && (
+
+
+
+ ) }
{ isUnsupported && (
{ __( 'Unsupported site' ) }
) }
diff --git a/src/modules/sync/tests/index.test.tsx b/src/modules/sync/tests/index.test.tsx
index f37f38c4a2..3cb2b64b21 100644
--- a/src/modules/sync/tests/index.test.tsx
+++ b/src/modules/sync/tests/index.test.tsx
@@ -89,6 +89,7 @@ const fakeSyncSite = {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
+ hasJetpack: true,
};
describe( 'ContentTabSync', () => {
@@ -264,6 +265,7 @@ describe( 'ContentTabSync', () => {
url: 'https:/developer.wordpress.com/studio/',
isStaging: false,
syncSupport: 'already-connected',
+ hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
@@ -298,6 +300,7 @@ describe( 'ContentTabSync', () => {
url: 'https:/developer.wordpress.com/studio/',
isStaging: false,
syncSupport: 'already-connected',
+ hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
@@ -361,6 +364,7 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
};
const fakePressableStagingSite: SyncSite = {
id: 7,
@@ -373,6 +377,7 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
};
const fakePressableDevelopmentSite: SyncSite = {
id: 8,
@@ -385,6 +390,7 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
@@ -431,6 +437,7 @@ describe( 'ContentTabSync', () => {
isPressable: false,
lastPullTimestamp: null,
lastPushTimestamp: null,
+ hasJetpack: true,
};
setupConnectedSitesMocks( [ fakeSyncSite ], [ fakeSyncSite ] );
@@ -463,6 +470,7 @@ describe( 'ContentTabSync', () => {
syncSupport: 'already-connected',
isPressable: true,
environmentType: 'development',
+ hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -502,6 +510,7 @@ describe( 'ContentTabSync', () => {
syncSupport: 'already-connected',
isPressable: true,
environmentType: 'non-supported-environment-example-or-sandbox',
+ hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -538,6 +547,7 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
+ hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -583,6 +593,7 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
+ hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -691,6 +702,7 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
+ hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
From 3536e8ae1285721245c83971144b506ee225d9d8 Mon Sep 17 00:00:00 2001
From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com>
Date: Tue, 4 Nov 2025 14:40:23 +0100
Subject: [PATCH 2/3] Remove unnecessary `hasJetpack` prop
---
src/hooks/tests/get-sync-support.test.ts | 16 +++++++++++++---
.../tests/reconcile-connected-sites.test.ts | 2 --
.../use-connected-sites-operations.test.tsx | 4 ----
src/hooks/use-fetch-wpcom-sites/index.tsx | 1 -
.../reconcile-connected-sites.tsx | 1 -
src/hooks/use-fetch-wpcom-sites/types.ts | 1 -
src/modules/sync/tests/index.test.tsx | 12 ------------
7 files changed, 13 insertions(+), 24 deletions(-)
diff --git a/src/hooks/tests/get-sync-support.test.ts b/src/hooks/tests/get-sync-support.test.ts
index 2975280182..24d56bcb7d 100644
--- a/src/hooks/tests/get-sync-support.test.ts
+++ b/src/hooks/tests/get-sync-support.test.ts
@@ -81,17 +81,27 @@ describe( 'getSyncSupport', () => {
} );
it( 'returns "already-connected" if site ID is in connectedSiteIds', () => {
- const site = { ...baseSite, ID: 42, is_wpcom_atomic: true };
+ const site = { ...baseSite, ID: 42, is_wpcom_atomic: true, jetpack: true };
expect( getSyncSupport( site, [ 42 ] ) ).toBe( 'already-connected' );
} );
it( 'returns "syncable" for Atomic site', () => {
- const site = { ...baseSite, is_wpcom_atomic: true };
+ const site = { ...baseSite, is_wpcom_atomic: true, jetpack: true };
expect( getSyncSupport( site, [] ) ).toBe( 'syncable' );
} );
it( 'returns "syncable" for Pressable site', () => {
- const site = { ...baseSite, hosting_provider_guess: 'pressable' };
+ const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: true };
expect( getSyncSupport( site, [] ) ).toBe( 'syncable' );
} );
+
+ it( 'returns "jetpack-disconnected" for Pressable site without Jetpack', () => {
+ const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: false };
+ expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' );
+ } );
+
+ it( 'returns "jetpack-disconnected" for Atomic site without Jetpack', () => {
+ const site = { ...baseSite, is_wpcom_atomic: true, jetpack: false };
+ expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' );
+ } );
} );
diff --git a/src/hooks/tests/reconcile-connected-sites.test.ts b/src/hooks/tests/reconcile-connected-sites.test.ts
index 1e808ee79d..646a2fd719 100644
--- a/src/hooks/tests/reconcile-connected-sites.test.ts
+++ b/src/hooks/tests/reconcile-connected-sites.test.ts
@@ -14,7 +14,6 @@ describe( 'reconcileConnectedSites', () => {
syncSupport: 'already-connected',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
];
const freshWpComSites: SyncSite[] = [
@@ -28,7 +27,6 @@ describe( 'reconcileConnectedSites', () => {
syncSupport: 'unsupported',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
];
const result = reconcileConnectedSites( connectedSites, freshWpComSites );
diff --git a/src/hooks/tests/use-connected-sites-operations.test.tsx b/src/hooks/tests/use-connected-sites-operations.test.tsx
index 21ab2ded9f..ff18f1d1d5 100644
--- a/src/hooks/tests/use-connected-sites-operations.test.tsx
+++ b/src/hooks/tests/use-connected-sites-operations.test.tsx
@@ -27,7 +27,6 @@ const mockConnectedWpcomSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
{
id: 7,
@@ -39,7 +38,6 @@ const mockConnectedWpcomSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
];
@@ -54,7 +52,6 @@ const mockSyncSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
{
id: 9,
@@ -66,7 +63,6 @@ const mockSyncSites: SyncSite[] = [
syncSupport: 'syncable',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
},
];
diff --git a/src/hooks/use-fetch-wpcom-sites/index.tsx b/src/hooks/use-fetch-wpcom-sites/index.tsx
index 765c2add44..28c97b97e0 100644
--- a/src/hooks/use-fetch-wpcom-sites/index.tsx
+++ b/src/hooks/use-fetch-wpcom-sites/index.tsx
@@ -117,7 +117,6 @@ function transformSingleSiteResponse(
syncSupport,
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: !! site.jetpack,
};
}
diff --git a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
index b17df17bf9..18dafaff4e 100644
--- a/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
+++ b/src/hooks/use-fetch-wpcom-sites/reconcile-connected-sites.tsx
@@ -28,7 +28,6 @@ export const reconcileConnectedSites = (
isStaging: site.isStaging,
isPressable: site.isPressable,
environmentType: site.environmentType,
- hasJetpack: site.hasJetpack,
};
}, [] );
diff --git a/src/hooks/use-fetch-wpcom-sites/types.ts b/src/hooks/use-fetch-wpcom-sites/types.ts
index d717b4e6da..da83f7c66e 100644
--- a/src/hooks/use-fetch-wpcom-sites/types.ts
+++ b/src/hooks/use-fetch-wpcom-sites/types.ts
@@ -19,5 +19,4 @@ export type SyncSite = {
syncSupport: SyncSupport;
lastPullTimestamp: string | null;
lastPushTimestamp: string | null;
- hasJetpack: boolean;
};
diff --git a/src/modules/sync/tests/index.test.tsx b/src/modules/sync/tests/index.test.tsx
index 3cb2b64b21..f37f38c4a2 100644
--- a/src/modules/sync/tests/index.test.tsx
+++ b/src/modules/sync/tests/index.test.tsx
@@ -89,7 +89,6 @@ const fakeSyncSite = {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
- hasJetpack: true,
};
describe( 'ContentTabSync', () => {
@@ -265,7 +264,6 @@ describe( 'ContentTabSync', () => {
url: 'https:/developer.wordpress.com/studio/',
isStaging: false,
syncSupport: 'already-connected',
- hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
@@ -300,7 +298,6 @@ describe( 'ContentTabSync', () => {
url: 'https:/developer.wordpress.com/studio/',
isStaging: false,
syncSupport: 'already-connected',
- hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
@@ -364,7 +361,6 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
};
const fakePressableStagingSite: SyncSite = {
id: 7,
@@ -377,7 +373,6 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
};
const fakePressableDevelopmentSite: SyncSite = {
id: 8,
@@ -390,7 +385,6 @@ describe( 'ContentTabSync', () => {
localSiteId: 'site-id',
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
};
( useAuth as jest.Mock ).mockReturnValue( createAuthMock( true ) );
@@ -437,7 +431,6 @@ describe( 'ContentTabSync', () => {
isPressable: false,
lastPullTimestamp: null,
lastPushTimestamp: null,
- hasJetpack: true,
};
setupConnectedSitesMocks( [ fakeSyncSite ], [ fakeSyncSite ] );
@@ -470,7 +463,6 @@ describe( 'ContentTabSync', () => {
syncSupport: 'already-connected',
isPressable: true,
environmentType: 'development',
- hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -510,7 +502,6 @@ describe( 'ContentTabSync', () => {
syncSupport: 'already-connected',
isPressable: true,
environmentType: 'non-supported-environment-example-or-sandbox',
- hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -547,7 +538,6 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
- hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -593,7 +583,6 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
- hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
@@ -702,7 +691,6 @@ describe( 'ContentTabSync', () => {
name: 'My simple business site that needs a transfer',
url: 'https:/developer.wordpress.com/studio/',
syncSupport: 'already-connected',
- hasJetpack: true,
};
( useConnectedSitesData as jest.Mock ).mockReturnValue( {
connectedSites: [ fakeSyncSite ],
From b8c75555a863821b42525b538eb2ee0579839400 Mon Sep 17 00:00:00 2001
From: Ivan Ottinger <25105483+ivan-ottinger@users.noreply.github.com>
Date: Tue, 4 Nov 2025 14:44:03 +0100
Subject: [PATCH 3/3] Update test copy
---
src/hooks/tests/get-sync-support.test.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/hooks/tests/get-sync-support.test.ts b/src/hooks/tests/get-sync-support.test.ts
index 24d56bcb7d..c36a8fb61f 100644
--- a/src/hooks/tests/get-sync-support.test.ts
+++ b/src/hooks/tests/get-sync-support.test.ts
@@ -95,12 +95,12 @@ describe( 'getSyncSupport', () => {
expect( getSyncSupport( site, [] ) ).toBe( 'syncable' );
} );
- it( 'returns "jetpack-disconnected" for Pressable site without Jetpack', () => {
+ it( 'returns "jetpack-disconnected" for Pressable site with broken Jetpack connection', () => {
const site = { ...baseSite, hosting_provider_guess: 'pressable', jetpack: false };
expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' );
} );
- it( 'returns "jetpack-disconnected" for Atomic site without Jetpack', () => {
+ it( 'returns "jetpack-disconnected" for Atomic site with broken Jetpack connection', () => {
const site = { ...baseSite, is_wpcom_atomic: true, jetpack: false };
expect( getSyncSupport( site, [] ) ).toBe( 'jetpack-disconnected' );
} );