Skip to content

Commit

Permalink
Reader Recent Feed: Refetch subscriptions from empty state (#97030)
Browse files Browse the repository at this point in the history
* Extract subscription logic and refresh subscriptions when the count changes.

* Clean up hook for readability.

* Rename hook, simplify return, and add JSDoc comment.

* Rename file and add tests.
  • Loading branch information
allilevine authored Dec 4, 2024
1 parent 2285468 commit 0fce49b
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 25 deletions.
27 changes: 2 additions & 25 deletions client/reader/following/main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import config from '@automattic/calypso-config';
import { SubscriptionManager } from '@automattic/data-stores';
import clsx from 'clsx';
import { translate } from 'i18n-calypso';
import { useMemo } from 'react';
import AsyncLoad from 'calypso/components/async-load';
import BloganuaryHeader from 'calypso/components/bloganuary-header';
import NavigationHeader from 'calypso/components/navigation-header';
Expand All @@ -12,35 +10,14 @@ import SuggestionProvider from 'calypso/reader/search-stream/suggestion-provider
import ReaderStream, { WIDE_DISPLAY_CUTOFF } from 'calypso/reader/stream';
import Recent from '../recent';
import ReaderStreamSidebar from './reader-stream-sidebar';
import { useSiteSubscriptions } from './use-site-subscriptions';
import { useFollowingView } from './view-preference';
import ViewToggle from './view-toggle';
import './style.scss';

function FollowingStream( { ...props } ) {
const { currentView } = useFollowingView();
const { data: subscriptionsCount, isLoading: isLoadingCount } =
SubscriptionManager.useSubscriptionsCountQuery();
const { data: siteSubscriptions, isLoading: isLoadingSiteSubscriptions } =
SubscriptionManager.useSiteSubscriptionsQuery();

const isLoading = isLoadingCount || isLoadingSiteSubscriptions;

const hasNonSelfSubscriptions = useMemo( () => {
if ( ! subscriptionsCount?.blogs || subscriptionsCount?.blogs === 0 ) {
return false;
}

// If we have site subscriptions data, filter out self-owned blogs.
if ( siteSubscriptions?.subscriptions ) {
const nonSelfSubscriptions = siteSubscriptions.subscriptions.filter(
( sub ) => ! sub.is_owner
);
return nonSelfSubscriptions.length > 0;
}

return subscriptionsCount.blogs > 0;
}, [ subscriptionsCount, siteSubscriptions ] );

const { isLoading, hasNonSelfSubscriptions } = useSiteSubscriptions();
const viewToggle = config.isEnabled( 'reader/recent-feed-overhaul' ) ? <ViewToggle /> : null;

if ( ! isLoading && ! hasNonSelfSubscriptions ) {
Expand Down
120 changes: 120 additions & 0 deletions client/reader/following/test/use-site-subscriptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @jest-environment jsdom
*/
import { SubscriptionManager } from '@automattic/data-stores';
import { renderHook } from '@testing-library/react';
import { useSiteSubscriptions } from '../use-site-subscriptions';

jest.mock( '@automattic/data-stores', () => ( {
SubscriptionManager: {
useSubscriptionsCountQuery: jest.fn(),
useSiteSubscriptionsQuery: jest.fn(),
},
} ) );

describe( 'useSiteSubscriptions', () => {
beforeEach( () => {
jest.clearAllMocks();
} );

it( 'should return loading state when either query is loading', () => {
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: null,
isLoading: true,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: null,
isLoading: false,
refetch: jest.fn(),
} );

const { result } = renderHook( () => useSiteSubscriptions() );

expect( result.current.isLoading ).toBe( true );
} );

it( 'should return false for hasNonSelfSubscriptions when blog count is 0', () => {
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: { blogs: 0 },
isLoading: false,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: null,
isLoading: false,
refetch: jest.fn(),
} );

const { result } = renderHook( () => useSiteSubscriptions() );

expect( result.current.hasNonSelfSubscriptions ).toBe( false );
} );

it( 'should filter out self-owned blogs when calculating hasNonSelfSubscriptions', () => {
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: { blogs: 2 },
isLoading: false,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: {
subscriptions: [ { is_owner: true }, { is_owner: false } ],
},
isLoading: false,
refetch: jest.fn(),
} );

const { result } = renderHook( () => useSiteSubscriptions() );

expect( result.current.hasNonSelfSubscriptions ).toBe( true );
} );

it( 'should return false for hasNonSelfSubscriptions when all subscriptions are self-owned', () => {
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: { blogs: 2 },
isLoading: false,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: {
subscriptions: [ { is_owner: true }, { is_owner: true } ],
},
isLoading: false,
refetch: jest.fn(),
} );

const { result } = renderHook( () => useSiteSubscriptions() );

expect( result.current.hasNonSelfSubscriptions ).toBe( false );
} );

it( 'should return true for hasNonSelfSubscriptions when blog count > 0 but no subscription data yet', () => {
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: { blogs: 1 },
isLoading: false,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: null,
isLoading: false,
refetch: jest.fn(),
} );

const { result } = renderHook( () => useSiteSubscriptions() );

expect( result.current.hasNonSelfSubscriptions ).toBe( true );
} );

it( 'should call refetch when blog count changes from 0 to positive', () => {
const refetchMock = jest.fn();
( SubscriptionManager.useSubscriptionsCountQuery as jest.Mock ).mockReturnValue( {
data: { blogs: 1 },
isLoading: false,
} );
( SubscriptionManager.useSiteSubscriptionsQuery as jest.Mock ).mockReturnValue( {
data: null,
isLoading: false,
refetch: refetchMock,
} );

renderHook( () => useSiteSubscriptions() );

expect( refetchMock ).toHaveBeenCalled();
} );
} );
50 changes: 50 additions & 0 deletions client/reader/following/use-site-subscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { SubscriptionManager } from '@automattic/data-stores';
import { useMemo, useEffect } from 'react';

/**
* Custom hook to manage site subscriptions data.
* Fetches and tracks subscription counts and site subscription details,
* filtering out self-owned blogs to determine if the user has any external subscriptions.
* @returns {Object} An object containing:
* - isLoading: boolean indicating if subscription data is being loaded
* - hasNonSelfSubscriptions: boolean indicating if user has any subscriptions to non-self-owned blogs
*/
export function useSiteSubscriptions() {
const { data: subscriptionsCount, isLoading: isLoadingCount } =
SubscriptionManager.useSubscriptionsCountQuery();
const {
data: siteSubscriptions,
isLoading: isLoadingSiteSubscriptions,
refetch: refetchSiteSubscriptions,
} = SubscriptionManager.useSiteSubscriptionsQuery();

const isLoading = isLoadingCount || isLoadingSiteSubscriptions;
const blogCount = subscriptionsCount?.blogs ?? 0;

const hasNonSelfSubscriptions = useMemo( () => {
if ( blogCount === 0 ) {
return false;
}

// If we have site subscriptions data, filter out self-owned blogs.
if ( siteSubscriptions?.subscriptions ) {
const nonSelfSubscriptions = siteSubscriptions.subscriptions.filter(
( sub ) => ! sub.is_owner
);
return nonSelfSubscriptions.length > 0;
}

return true;
}, [ blogCount, siteSubscriptions ] );

useEffect( () => {
if ( blogCount > 0 ) {
refetchSiteSubscriptions();
}
}, [ refetchSiteSubscriptions, blogCount ] );

return {
isLoading,
hasNonSelfSubscriptions,
};
}

0 comments on commit 0fce49b

Please sign in to comment.