Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,82 @@
import { Icon, VisuallyHidden } from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
import { info } from '@wordpress/icons';
import { Connection } from '../../../social-store/types';
import clsx from 'clsx';
import { useConnectionPreviewData } from '../../../hooks/use-connection-preview-data';
import { PostPreview } from './post-preview';
import styles from './styles.module.scss';
import type { Connection } from '../../../social-store/types';

type PreviewSectionProps = {
connection: Connection;
};

/**
* Preview loading placeholder.
*
* @return Preview loading placeholder.
*/
function PreviewSkeleton() {
return (
<div className={ styles[ 'preview-skeleton' ] } role="status">
<VisuallyHidden>{ __( 'Loading post preview.', 'jetpack-publicize-pkg' ) }</VisuallyHidden>
<div className={ styles[ 'preview-skeleton-header' ] }>
<div className={ styles[ 'preview-skeleton-avatar' ] } />
<div className={ styles[ 'preview-skeleton-lines' ] }>
<div className={ styles[ 'preview-skeleton-line' ] } />
<div
className={ clsx(
styles[ 'preview-skeleton-line' ],
styles[ 'preview-skeleton-line-short' ]
) }
/>
</div>
</div>
<div className={ styles[ 'preview-skeleton-copy' ] }>
<div className={ styles[ 'preview-skeleton-line' ] } />
<div
className={ clsx(
styles[ 'preview-skeleton-line' ],
styles[ 'preview-skeleton-line-medium' ]
) }
/>
</div>
<div className={ styles[ 'preview-skeleton-media' ] } />
<div
className={ clsx(
styles[ 'preview-skeleton-line' ],
styles[ 'preview-skeleton-line-short' ]
) }
/>
</div>
);
}

/**
* Enabled preview area.
*
* @param {PreviewSectionProps} props - The component props.
* @return Enabled preview area.
*/
function EnabledPreview( { connection }: PreviewSectionProps ) {
const previewData = useConnectionPreviewData( connection );

return (
<>
<VisuallyHidden as="h2">
{ _x( 'Preview', 'Noun: Post preview section heading', 'jetpack-publicize-pkg' ) }
</VisuallyHidden>
<div className={ styles[ 'preview-wrapper' ] } aria-busy={ previewData.isLoading }>
{ previewData.isLoading ? (
<PreviewSkeleton />
) : (
<PostPreview connection={ connection } previewData={ previewData } />
) }
</div>
</>
);
}

/**
* Preview section component.
*
Expand All @@ -22,14 +90,7 @@ export function PreviewSection( { connection }: PreviewSectionProps ) {
className={ styles[ 'preview-section' ] }
>
{ connection.enabled ? (
<>
<VisuallyHidden as="h2">
{ _x( 'Preview', 'Noun: Post preview section heading', 'jetpack-publicize-pkg' ) }
</VisuallyHidden>
<div className={ styles[ 'preview-wrapper' ] }>
<PostPreview connection={ connection } />
</div>
</>
<EnabledPreview connection={ connection } />
) : (
<div className={ styles[ 'inactive-preview' ] }>
<Icon icon={ info } size={ 48 } />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
import { useConnectionPreviewData } from '../../../hooks/use-connection-preview-data';
import { Connection } from '../../../social-store/types';
import { InstagramNoMediaNotice } from '../../form/instagram-no-media-notice';
import type { ConnectionPreviewData } from '../../../hooks/use-connection-preview-data';
import type { Connection } from '../../../social-store/types';

export type PostPreviewProps = {
connection: Connection;
previewData: ConnectionPreviewData;
};

/**
Expand All @@ -44,7 +45,7 @@ function getCombinedText( title: string, excerpt: string ): string {
*
* @return - Post preview component.
*/
export function PostPreview( { connection }: PostPreviewProps ) {
export function PostPreview( { connection, previewData }: PostPreviewProps ) {
const user = useMemo(
() => ( {
displayName: connection.display_name,
Expand All @@ -54,8 +55,7 @@ export function PostPreview( { connection }: PostPreviewProps ) {
[ connection ]
);

const { image, media, title, description, url, excerpt, message } =
useConnectionPreviewData( connection );
const { image, media, title, description, url, excerpt, message } = previewData;

const commonProps = useMemo(
() => ( {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
@use "@automattic/jetpack-base-styles/gutenberg-base-styles" as gb;

@keyframes preview-loading-pulse {

0% {
opacity: 0.56;
}

50% {
opacity: 1;
}

100% {
opacity: 0.56;
}
}

.preview-section {
background: gb.$gray-100;
padding: 1rem;
Expand All @@ -24,4 +39,62 @@
align-items: center;
justify-content: center;
}

.preview-skeleton {
inline-size: min(100%, 420px);
padding: 1rem;
border: 1px solid gb.$gray-200;
border-radius: 8px;
background: gb.$white;
display: flex;
flex-direction: column;
gap: 0.75rem;
}

.preview-skeleton-header {
display: flex;
align-items: center;
gap: 0.75rem;
}

.preview-skeleton-avatar,
.preview-skeleton-line,
.preview-skeleton-media {
background: gb.$gray-200;
animation: preview-loading-pulse 1.5s ease-in-out infinite;
}

.preview-skeleton-avatar {
inline-size: 40px;
block-size: 40px;
border-radius: 50%;
flex: 0 0 auto;
}

.preview-skeleton-lines,
.preview-skeleton-copy {
display: flex;
flex-direction: column;
gap: 0.5rem;
inline-size: 100%;
}

.preview-skeleton-line {
inline-size: 100%;
block-size: 12px;
border-radius: 999px;
}

.preview-skeleton-line-medium {
inline-size: 72%;
}

.preview-skeleton-line-short {
inline-size: 44%;
}

.preview-skeleton-media {
aspect-ratio: 16 / 9;
border-radius: 4px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { render, screen } from '@testing-library/react';
import { PreviewSection } from '../';
import { useConnectionPreviewData } from '../../../../hooks/use-connection-preview-data';
import type { ConnectionPreviewData } from '../../../../hooks/use-connection-preview-data';
import type { Connection } from '../../../../social-store/types';

jest.mock( '../../../../hooks/use-connection-preview-data', () => ( {
useConnectionPreviewData: jest.fn(),
} ) );

jest.mock( '../post-preview', () => ( {
PostPreview: ( { previewData }: { previewData: ConnectionPreviewData } ) => (
<div data-testid="post-preview">{ previewData.message }</div>
),
} ) );

jest.mock( '../styles.module.scss', () => ( {
'inactive-preview': 'inactive-preview',
'preview-section': 'preview-section',
'preview-skeleton': 'preview-skeleton',
'preview-skeleton-avatar': 'preview-skeleton-avatar',
'preview-skeleton-copy': 'preview-skeleton-copy',
'preview-skeleton-header': 'preview-skeleton-header',
'preview-skeleton-line': 'preview-skeleton-line',
'preview-skeleton-line-medium': 'preview-skeleton-line-medium',
'preview-skeleton-line-short': 'preview-skeleton-line-short',
'preview-skeleton-lines': 'preview-skeleton-lines',
'preview-skeleton-media': 'preview-skeleton-media',
'preview-wrapper': 'preview-wrapper',
} ) );

const mockUseConnectionPreviewData = useConnectionPreviewData as jest.MockedFunction<
typeof useConnectionPreviewData
>;

const connection: Connection = {
connection_id: '123',
display_name: 'Example Account',
enabled: true,
external_handle: '@example',
external_id: 'external-id',
profile_link: 'https://example.com',
profile_picture: 'https://example.com/avatar.jpg',
service_label: 'Facebook',
service_name: 'facebook',
shared: false,
status: 'ok',
wpcom_user_id: 1,
};

const previewData: ConnectionPreviewData = {
description: 'Description',
excerpt: 'Excerpt',
image: 'https://example.com/image.jpg',
isLoading: false,
media: [],
message: 'Rendered message',
siteTitle: 'Example Site',
title: 'Title',
url: 'https://example.com/post',
};

describe( 'PreviewSection', () => {
beforeEach( () => {
mockUseConnectionPreviewData.mockReturnValue( previewData );
} );

afterEach( () => {
jest.clearAllMocks();
} );

it( 'renders the post preview for enabled connections when preview data is ready', () => {
render( <PreviewSection connection={ connection } /> );

expect( screen.getByTestId( 'post-preview' ) ).toHaveTextContent( previewData.message );
expect( screen.queryByRole( 'status' ) ).not.toBeInTheDocument();
} );

it( 'renders one preview skeleton while preview data is loading', () => {
mockUseConnectionPreviewData.mockReturnValue( { ...previewData, isLoading: true } );

render( <PreviewSection connection={ connection } /> );

expect( screen.getByRole( 'status' ) ).toHaveTextContent( 'Loading post preview.' );
expect( screen.queryByTestId( 'post-preview' ) ).not.toBeInTheDocument();
} );

it( 'does not read preview data for inactive connections', () => {
render( <PreviewSection connection={ { ...connection, enabled: false } } /> );

expect( screen.getByText( "The post won't be shared to this account." ) ).toBeInTheDocument();
expect( mockUseConnectionPreviewData ).not.toHaveBeenCalled();
} );
} );
Loading
Loading