diff --git a/src/Component/Post/BlockAttributes/BlockAttributes.php b/src/Component/Post/BlockAttributes/BlockAttributes.php index 2c760cec..f41276b3 100644 --- a/src/Component/Post/BlockAttributes/BlockAttributes.php +++ b/src/Component/Post/BlockAttributes/BlockAttributes.php @@ -13,15 +13,12 @@ namespace Beyondwords\Wordpress\Component\Post\BlockAttributes; -use Beyondwords\Wordpress\Component\Post\PostContentUtils; -use Beyondwords\Wordpress\Component\Post\PostMetaUtils; -use Beyondwords\Wordpress\Component\Settings\Fields\PlayerUI\PlayerUI; - /** * BlockAttributes * * @since 3.7.0 - * @since 4.0.0 Renamed from BlockAudioAttribute to BlockAttributes to support multiple attributes + * @since 4.0.0 Renamed from BlockAudioAttribute to BlockAttributes to support multiple attributes. + * @since 6.0.0 Stop adding beyondwordsMarker attribute to blocks. */ class BlockAttributes { @@ -29,13 +26,12 @@ class BlockAttributes * Init. * * @since 4.0.0 - * @since 6.0.0 Make static. + * @since 6.0.0 Make static and remove renderBlock registration. */ public static function init() { add_filter('register_block_type_args', [self::class, 'registerAudioAttribute']); add_filter('register_block_type_args', [self::class, 'registerMarkerAttribute']); - add_filter('render_block', [self::class, 'renderBlock'], 10, 2); } /** @@ -63,6 +59,8 @@ public static function registerAudioAttribute($args) /** * Register "Segment marker" attribute for Gutenberg blocks. * + * @deprecated This attribute is no longer used as of 6.0.0, but kept for backward compatibility. + * * @since 6.0.0 Make static. */ public static function registerMarkerAttribute($args) @@ -81,45 +79,4 @@ public static function registerMarkerAttribute($args) return $args; } - - /** - * Render block as HTML. - * - * Performs some checks and then attempts to add data-beyondwords-marker - * attribute to the root element of Gutenberg blocks. - * - * @since 4.0.0 - * @since 4.2.2 Rename method to renderBlock. - * @since 6.0.0 Make static and update for Magic Embed. - * - * @param string $blockContent The block content (HTML). - * @param string $block The full block, including name and attributes. - * - * @return string Block Content (HTML). - */ - public static function renderBlock($blockContent, $block) - { - // Skip adding marker if player UI is disabled - if (get_option(PlayerUI::OPTION_NAME) === PlayerUI::DISABLED) { - return $blockContent; - } - - $postId = get_the_ID(); - - if (! $postId) { - return $blockContent; - } - - // Skip adding marker if no content exists - if (! PostMetaUtils::hasContent($postId)) { - return $blockContent; - } - - $marker = $block['attrs']['beyondwordsMarker'] ?? ''; - - return PostContentUtils::addMarkerAttribute( - $blockContent, - $marker - ); - } } diff --git a/src/Component/Post/BlockAttributes/addAttributes.js b/src/Component/Post/BlockAttributes/addAttributes.js index e79a4960..fe43983b 100644 --- a/src/Component/Post/BlockAttributes/addAttributes.js +++ b/src/Component/Post/BlockAttributes/addAttributes.js @@ -4,20 +4,27 @@ import { addFilter } from '@wordpress/hooks'; /** - * External dependencies + * Internal dependencies */ -import getBlockMarkerAttribute from './helpers/getBlockMarkerAttribute'; +import { isBeyondwordsSupportedBlock } from './isBeyondwordsSupportedBlock'; /** * Register custom block attributes for BeyondWords. * * @since 4.0.4 Remove settings.attributes undefined check, to match official docs. + * @since 6.0.1 Skip internal/UI blocks to prevent breaking the block inserter. * * @param {Object} settings Settings for the block. + * @param {string} name Block name. * * @return {Object} settings Modified settings. */ -function addAttributes( settings ) { +function addAttributes( settings, name ) { + // Only add attributes to content blocks + if ( ! isBeyondwordsSupportedBlock( name ) ) { + return settings; + } + return { ...settings, attributes: { @@ -39,27 +46,3 @@ addFilter( 'beyondwords/beyondwords-block-attributes', addAttributes ); - -/** - * Set a unique BeyondWords marker for each block that doesn't already have one. - * - * @param {Object} attributes Attributes for the block. - * - * @return {Object} attributes Modified attributes. - */ -function setMarkerAttribute( attributes ) { - const marker = getBlockMarkerAttribute( attributes ); - - attributes = { - ...attributes, - beyondwordsMarker: marker, - }; - - return attributes; -} - -addFilter( - 'blocks.getBlockAttributes', - 'beyondwords/set-marker-attribute', - setMarkerAttribute -); diff --git a/src/Component/Post/BlockAttributes/addControls.js b/src/Component/Post/BlockAttributes/addControls.js index c277ffce..53172b3e 100644 --- a/src/Component/Post/BlockAttributes/addControls.js +++ b/src/Component/Post/BlockAttributes/addControls.js @@ -6,28 +6,23 @@ import { InspectorControls, BlockControls } from '@wordpress/block-editor'; import { PanelBody, PanelRow, - TextControl, ToggleControl, ToolbarButton, ToolbarGroup, } from '@wordpress/components'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { useEffect } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; -/** - * External dependencies - */ -import getBlockMarkerAttribute from './helpers/getBlockMarkerAttribute'; - /** * Internal dependencies */ -import BlockAttributesCheck from './check'; +import { isBeyondwordsSupportedBlock } from './isBeyondwordsSupportedBlock'; /** * Add BeyondWords controls to Gutenberg Blocks. * + * @since 6.0.1 Skip internal/UI blocks to prevent breaking the block inserter. + * * @param {Function} BlockEdit Block edit component. * * @return {Function} BlockEdit Modified block edit component. @@ -35,15 +30,16 @@ import BlockAttributesCheck from './check'; const withBeyondwordsBlockControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { - const { attributes, setAttributes } = props; + const { name } = props; - useEffect( () => { - setAttributes( { - beyondwordsMarker: getBlockMarkerAttribute( attributes ), - } ); - }, [] ); + // Skip blocks that shouldn't have controls + // Do this check BEFORE accessing attributes to avoid unnecessary processing + if ( ! isBeyondwordsSupportedBlock( name ) ) { + return ; + } - const { beyondwordsAudio, beyondwordsMarker } = attributes; + const { attributes, setAttributes } = props; + const { beyondwordsAudio } = attributes; const icon = !! beyondwordsAudio ? 'controls-volumeon' @@ -57,56 +53,41 @@ const withBeyondwordsBlockControls = createHigherOrderComponent( ? __( 'Audio processing enabled', 'speechkit' ) : __( 'Audio processing disabled', 'speechkit' ); - const toggleBeyondwordsAudio = () => + const toggleBeyondwordsAudio = () => { setAttributes( { beyondwordsAudio: ! beyondwordsAudio } ); + }; return ( <> - - - - - - - { !! beyondwordsAudio && ( - - - - ) } - - - - - - + + + - - - + + + + + + + + + ); }; diff --git a/src/Component/Post/BlockAttributes/helpers/getBlockMarkerAttribute.js b/src/Component/Post/BlockAttributes/helpers/getBlockMarkerAttribute.js deleted file mode 100644 index 85195750..00000000 --- a/src/Component/Post/BlockAttributes/helpers/getBlockMarkerAttribute.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * WordPress Dependencies - */ -import { select } from '@wordpress/data'; - -/** - * External dependencies - */ -import { v4 as uuidv4 } from 'uuid'; - -/** - * Get a beyondwordsMarker attribute for a block. - * - * Using the "Duplicate" button in the Block toolbar duplicates the marker - * attribute too, so we attempt to handle this by getting all the markers in the - * current Post and assinging new UUIDs to markers that already exist. - * - * @since 4.0.0 - * - * @param {Object} attributes Attributes for the block. - * - * @return {string} marker The block marker (segment marker in BeyondWords API). - */ -const getBlockMarkerAttribute = ( attributes ) => { - const { beyondwordsMarker } = attributes; - - if ( ! beyondwordsMarker ) { - return uuidv4(); - } - - const existingMarkers = getExistingBlockMarkers(); - - if ( countInArray( existingMarkers, beyondwordsMarker ) > 1 ) { - // Return a new UUID if this marker is a duplicate - return uuidv4(); - } - - // Return the existing marker only if it is not a duplicate - return beyondwordsMarker; -}; - -/** - * Get all existing Block markers for the currently-edited post. - * - * If using `getBlocks()` proves to be too respource-intensive then further work - * will be required to optimise this. - * - * @since 4.0.0 - * - * @return {string[]} markers The block markers for the current Post. - */ -const getExistingBlockMarkers = () => { - // Get all Blocks in current Post - const blocks = select( 'core/block-editor' ).getBlocks(); - - // Return all non-empty markers of the Blocks - return blocks - .map( ( block ) => block?.attributes?.beyondwordsMarker ) - .filter( ( marker ) => marker ); -}; - -/** - * Count the number of times an item is in an array. - * - * @param array - * @param item - * @since 4.0.0 - * @since 4.4.0 Ensure param is array - * - * @return {number} count The number of times the item occurs. - */ -function countInArray( array, item ) { - if ( ! Array.isArray( array ) ) { - return 0; - } - - let count = 0; - - for ( let i = 0; i < array.length; i++ ) { - if ( array[ i ] === item ) { - count++; - } - } - - return count; -} - -export default getBlockMarkerAttribute; diff --git a/src/Component/Post/BlockAttributes/isBeyondwordsSupportedBlock.js b/src/Component/Post/BlockAttributes/isBeyondwordsSupportedBlock.js new file mode 100644 index 00000000..502fee8f --- /dev/null +++ b/src/Component/Post/BlockAttributes/isBeyondwordsSupportedBlock.js @@ -0,0 +1,45 @@ +/** + * Check if a block is supported by BeyondWords. + * Only content blocks that can be read aloud should have BeyondWords attributes and controls. + * + * @param {string} name Block name. + * @return {boolean} Whether the block is supported by BeyondWords. + */ +export function isBeyondwordsSupportedBlock( name ) { + // Skip blocks without a name + if ( ! name ) { + return false; + } + + // Skip internal/UI blocks + if ( name.startsWith( '__' ) ) { + return false; + } + + // Skip reusable blocks and template parts (these are containers) + if ( + name.startsWith( 'core/block' ) || + name.startsWith( 'core/template' ) + ) { + return false; + } + + // Skip editor UI blocks + const excludedBlocks = [ + 'core/freeform', // Classic editor + 'core/legacy-widget', + 'core/widget-area', + 'core/navigation', + 'core/navigation-link', + 'core/navigation-submenu', + 'core/site-logo', + 'core/site-title', + 'core/site-tagline', + ]; + + if ( excludedBlocks.includes( name ) ) { + return false; + } + + return true; +} diff --git a/src/Component/Post/PostContentUtils.php b/src/Component/Post/PostContentUtils.php index 26074503..e92bea55 100755 --- a/src/Component/Post/PostContentUtils.php +++ b/src/Component/Post/PostContentUtils.php @@ -84,6 +84,7 @@ public static function getPostBody(int|\WP_Post $post): string|null } // Apply the_content filters to handle shortcodes etc + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Applying core WordPress filter $content = apply_filters('the_content', $content); // Trim to remove trailing newlines – common for WordPress content @@ -146,6 +147,7 @@ public static function getPostSummary(int|\WP_Post $post): string|null // Escape characters $summary = htmlentities($post->post_excerpt, ENT_QUOTES | ENT_XHTML); // Apply WordPress filters + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Applying core WordPress filter $summary = apply_filters('get_the_excerpt', $summary); // Convert line breaks into paragraphs $summary = trim(wpautop($summary)); @@ -154,59 +156,6 @@ public static function getPostSummary(int|\WP_Post $post): string|null return $summary; } - /** - * Get the segments for the audio content, ready to be sent to the BeyondWords API. - * - * @codeCoverageIgnore - * THIS METHOD IS CURRENTLY NOT IN USE. Segments cannot currently include HTML - * formatting tags such as and so we do not pass segments, we pass - * a HTML string as the body param instead. - * - * @param int|\WP_Post $post The WordPress post ID, or post object. - * - * @since 4.0.0 - */ - public static function getSegments(int|\WP_Post $post): array - { - if (! has_blocks($post)) { - return []; - } - - $titleSegment = (object) [ - 'section' => 'title', - 'text' => get_the_title($post), - ]; - - $summarySegment = (object) [ - 'section' => 'summary', - 'text' => PostContentUtils::getPostSummary($post), - ]; - - $blocks = PostContentUtils::getAudioEnabledBlocks($post); - - $bodySegments = array_map(function ($block) { - $marker = null; - - if (isset($block['attrs']) && isset($block['attrs']['beyondwordsMarker'])) { - $marker = $block['attrs']['beyondwordsMarker']; - } - - return (object) [ - 'section' => 'body', - 'marker' => $marker, - 'text' => trim(render_block($block)), - ]; - }, $blocks); - - // Merge title, summary and body segments - $segments = array_values(array_merge([$titleSegment], [$summarySegment], $bodySegments)); - - // Remove any segments with empty text - $segments = array_values(array_filter($segments, fn($segment) => ! empty($segment::text))); - - return $segments; - } - /** * Get the post content without blocks which have been filtered. * @@ -219,6 +168,7 @@ public static function getSegments(int|\WP_Post $post): array * * @since 3.8.0 * @since 4.0.0 Replace for loop with array_reduce + * @since 6.0.0 Remove beyondwordsMarker attribute from rendered blocks. * * @return string The post body without excluded blocks. */ @@ -234,12 +184,7 @@ public static function getContentWithoutExcludedBlocks(int|\WP_Post $post): stri $blocks = PostContentUtils::getAudioEnabledBlocks($post); foreach ($blocks as $block) { - $marker = $block['attrs']['beyondwordsMarker'] ?? ''; - - $output .= PostContentUtils::addMarkerAttribute( - render_block($block), - $marker - ); + $output .= render_block($block); } return $output; @@ -452,131 +397,4 @@ public static function getAuthorName(int $postId): string return get_the_author_meta('display_name', $authorId); } - - /** - * Add data-beyondwords-marker attribute to the root elements in a HTML - * string (typically the rendered HTML of a single block). - * - * Checks to see whether we can use WP_HTML_Tag_Processor, or whether we - * fall back to using DOMDocument to add the marker. - * - * @since 4.2.2 - * - * @param string $html HTML. - * @param string $marker Marker UUID. - * - * @return string HTML. - */ - public static function addMarkerAttribute(string $html, string $marker): string - { - if (! $marker) { - return $html; - } - - // Prefer WP_HTML_Tag_Processor, introduced in WordPress 6.2 - if (class_exists('WP_HTML_Tag_Processor')) { - return PostContentUtils::addMarkerAttributeWithHTMLTagProcessor($html, $marker); - } else { - return PostContentUtils::addMarkerAttributeWithDOMDocument($html, $marker); - } - } - - /** - * Add data-beyondwords-marker attribute to the root elements in a HTML - * string using WP_HTML_Tag_Processor. - * - * @since 4.0.0 - * @since 4.2.2 Moved from src/Component/Post/BlockAttributes/BlockAttributes.php - * to src/Component/Post/PostContentUtils.php - * @since 4.7.0 Prevent empty data-beyondwords-marker attributes. - * - * @param string $html HTML. - * @param string $marker Marker UUID. - * - * @return string HTML. - */ - public static function addMarkerAttributeWithHTMLTagProcessor(string $html, string $marker): string - { - if (! $marker) { - return $html; - } - - // https://github.com/WordPress/gutenberg/pull/42485 - $tags = new \WP_HTML_Tag_Processor($html); - - if ($tags->next_tag()) { - $tags->set_attribute('data-beyondwords-marker', $marker); - } - - return strval($tags); - } - - /** - * Add data-beyondwords-marker attribute to the root elements in a HTML - * string using DOMDocument. - * - * This is a fallback, since WP_HTML_Tag_Processor was only shipped with - * WordPress 6.2 on 19 April 2023. - * - * https://make.wordpress.org/core/2022/10/13/whats-new-in-gutenberg-14-3-12-october/ - * - * Note: It is not ideal to do all the $bodyElement/$fullHtml processing - * in this method, but without it DOMDocument does not work as expected if - * there is more than 1 root element. The approach here has been taken from - * some historic Gutenberg code before they implemented WP_HTML_Tag_Processor: - * - * https://github.com/WordPress/gutenberg/blob/6671cef1179412a2bbd4969cbbc82705c7f69bac/lib/block-supports/index.php - * - * @since 4.0.0 - * @since 4.2.2 Moved from src/Component/Post/BlockAttributes/BlockAttributes.php - * to src/Component/Post/PostContentUtils.php - * @since 4.7.0 Prevent empty data-beyondwords-marker attributes. - * - * @param string $html HTML. - * @param string $marker Marker UUID. - * - * @return string HTML. - */ - public static function addMarkerAttributeWithDOMDocument(string $html, string $marker): string - { - if (! $marker) { - return $html; - } - - $dom = new \DOMDocument('1.0', 'utf-8'); - - $wrappedHtml = - '' - . $html - . ''; - - $success = $dom->loadHTML($wrappedHtml, LIBXML_HTML_NODEFDTD | LIBXML_COMPACT); - - if (! $success) { - return $html; - } - - // Structure is like ``, so body is the `lastChild` of our document. - $bodyElement = $dom->documentElement->lastChild; - - $xpath = new \DOMXPath($dom); - $blockRoot = $xpath->query('./*', $bodyElement)[0]; - - if (empty($blockRoot)) { - return $html; - } - - $blockRoot->setAttribute('data-beyondwords-marker', $marker); - - // Avoid using `$dom->saveHtml( $node )` because the node results may not produce consistent - // whitespace. Saving the root HTML `$dom->saveHtml()` prevents this behavior. - $fullHtml = $dom->saveHtml(); - - // Find the open/close tags. The open tag needs to be adjusted so we get inside the tag - // and not the tag itself. - $start = strpos($fullHtml, '', 0) + strlen(''); - $end = strpos($fullHtml, '', $start); - - return trim(substr($fullHtml, $start, $end - $start)); - } } diff --git a/tests/cypress/e2e/block-editor/segment-markers.cy.js b/tests/cypress/e2e/block-editor/segment-markers.cy.js deleted file mode 100644 index 61bc0489..00000000 --- a/tests/cypress/e2e/block-editor/segment-markers.cy.js +++ /dev/null @@ -1,355 +0,0 @@ -/* global Cypress, cy, beforeEach, context, expect, it */ - -context( 'Block Editor: Segment markers', () => { - beforeEach( () => { - cy.login(); - } ); - - const markerRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - - const postTypes = require( '../../../../tests/fixtures/post-types.json' ); - - const testCases = [ - { - id: 1, - // eslint-disable-next-line max-len - text: 'Latin symbols: á, é, í, ó, ú, ü, ñ, ¡, !, ¿, ?, Ä, ä, Ö, ö, Ü, ü, ẞ, ß, Æ, æ, Ø, ø, Å, å', - }, - { id: 2, text: 'Kanji: 任天堂' }, - { id: 3, text: 'Katana: イリノイ州シカゴにて' }, - { - id: 4, - // eslint-disable-next-line max-len - text: 'Mathematical symbols: αβγδεζηθικλμνξοπρσςτυφχψω ΓΔΘΛΞΠΣΦΨΩ ∫∑∏−±∞≈∝=≡≠≤≥×·⋅÷∂′″∇‰°∴∅ ∈∉∩∪⊂⊃⊆⊇¬∧∨∃∀⇒⇔→↔↑↓ℵ', - }, - ]; - - // Test priority post types - postTypes - .filter( ( x ) => x.priority ) - .forEach( ( postType ) => { - it( `A ${ postType.name } without audio should not have segment markers`, () => { - cy.createPost( { - postType, - title: `I can add a ${ postType.name } without segment markers`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.uncheckGenerateAudio( postType ); - - // Add paragraphs - cy.addParagraphBlock( 'One.' ); - cy.addParagraphBlock( 'Two.' ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'not.exist' ); - cy.hasNoBeyondwordsWindowObject(); - - cy.contains( 'p', 'One.' ).should( - 'not.have.attr', - 'data-beyondwords-marker' - ); - cy.contains( 'p', 'Two.' ).should( - 'not.have.attr', - 'data-beyondwords-marker' - ); - } ); - - it( `can add a ${ postType.name } with segment markers`, () => { - cy.createPost( { - postType, - title: `I can add a ${ postType.name } with segment markers`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.checkGenerateAudio( postType ); - - /** - * Ensure the marker is persistent (it DOES NOT change while typing) - */ - cy.get( '.wp-block-post-content p:last-of-type' ).click(); - // Type a letter - cy.get( 'body' ).type( `O` ); - // Check the marker - cy.contains( 'label', 'Segment marker' ) - .siblings( 'input' ) - .first() - .invoke( 'val' ) - .then( ( originalMarker ) => { - // Type another letter - cy.get( 'body' ).type( `K` ).wait( 200 ); - // Get marker value again and check it hasn't changed - cy.contains( 'label', 'Segment marker' ) - .siblings( 'input' ) - .first() - .invoke( 'val' ) - .should( 'equal', originalMarker ); - cy.get( 'body' ).type( `{enter}` ).wait( 200 ); - } ); - - /** - * Various test cases check we handle UTF-8 correctly - */ - testCases.forEach( ( testCase ) => { - // Add paragraph - cy.addParagraphBlock( testCase.text ); - - // Grab assigned marker from UI input - cy.contains( 'label', 'Segment marker' ) - .siblings( 'input' ) - .first() - .invoke( 'val' ) - .should( 'match', markerRegex ) // Check regex - .as( `marker${ testCase.id }` ); - } ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'exist' ); - cy.hasPlayerInstances( 1 ); - - testCases.forEach( ( testCase ) => { - cy.get( `@marker${ testCase.id }` ).then( ( marker ) => { - cy.contains( 'p', testCase.text ) - .invoke( 'attr', 'data-beyondwords-marker' ) - .should( 'not.be.empty' ); // @todo check marker - } ); - } ); - - cy.task( 'deactivatePlugin', 'speechkit' ); - cy.reload(); - - // Check content on page again, after deactivating the plugin - testCases.forEach( ( testCase ) => { - cy.contains( 'p', testCase.text ) // Text should be an exact UTF-8 match - .should( 'not.have.attr', 'data-beyondwords-marker' ); - } ); - - cy.task( 'activatePlugin', 'speechkit' ); - } ); - - it( `assigns unique markers for duplicated blocks in a ${ postType.name }`, () => { - cy.createPost( { - postType, - title: `I see unique markers for duplicated blocks in a ${ postType.name }`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.checkGenerateAudio( postType ); - - // Add paragraph - cy.addParagraphBlock( 'Test.' ); - - // Grab assigned marker from UI input - cy.contains( 'label', 'Segment marker' ) - .siblings( 'input' ) - .first() - .invoke( 'val' ) - .as( 'marker1' ); - - // Add first paragraph - cy.get( '.editor-post-title' ).click(); - cy.contains( 'p.wp-block-paragraph', 'Test.' ).click(); - - // Duplicate paragraph - cy.get( '.block-editor-block-settings-menu' ).click(); - cy.contains( - '.components-menu-item__item', - 'Duplicate' - ).click(); - - cy.get( 'p:contains(Test.)' ).should( 'have.length', 2 ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'exist' ); - cy.hasPlayerInstances( 1 ); - - cy.get( '.entry-content p:not(:empty)' ) - .should( 'have.length', 2 ) - .mapInvoke( 'getAttribute', 'data-beyondwords-marker' ) - .then( ( markers ) => { - // Markers must be unique - const unique = Cypress._.uniq( markers ); - expect( - unique, - 'all markers are unique' - ).to.have.length( markers.length ); - - // All markers must be UUIDs - expect( markers[ 0 ] ).to.match( markerRegex ); - expect( markers[ 1 ] ).to.match( markerRegex ); - } ); - } ); - - it( 'assigns markers when blocks are added programatically', () => { - cy.createPost( { - title: `I see markers when blocks are added programatically`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.checkGenerateAudio( postType ); - - // Add paragraph - cy.createBlockProgramatically( 'core/paragraph', { - content: 'One.', - } ); - - // Add paragraph - cy.createBlockProgramatically( 'core/paragraph', { - content: 'Two.', - } ); - - cy.get( 'p:contains(One.)' ).should( 'have.length', 1 ); - cy.get( 'p:contains(Two.)' ).should( 'have.length', 1 ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'exist' ); - cy.hasPlayerInstances( 1 ); - - cy.get( '.entry-content p:not(:empty)' ) - .should( 'have.length', 2 ) - .mapInvoke( 'getAttribute', 'data-beyondwords-marker' ) - .then( ( markers ) => { - // Markers must be unique - const unique = Cypress._.uniq( markers ); - expect( - unique, - 'all markers are unique' - ).to.have.length( markers.length ); - - // All markers must be UUIDs - expect( markers[ 0 ] ).to.match( markerRegex ); - expect( markers[ 1 ] ).to.match( markerRegex ); - } ); - } ); - - // So far unable to write tests for pasted content, all attempts have failed :( - it( 'assigns markers when content is pasted', () => { - cy.createPost( { - title: `I see markers for pasted content`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.checkGenerateAudio( postType ); - - // Click "+ block" button - cy.get( - '.block-editor-default-block-appender__content' - ).click(); - - cy.get( '.wp-block.is-selected' ).paste( 'One.\n\nTwo.' ); - - cy.get( 'p:contains(One.)' ).should( 'have.length', 1 ); - cy.get( 'p:contains(Two.)' ).should( 'have.length', 1 ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'exist' ); - cy.hasPlayerInstances( 1 ); - - cy.get( '.entry-content p:not(:empty)' ) - .should( 'have.length', 2 ) - .mapInvoke( 'getAttribute', 'data-beyondwords-marker' ) - .then( ( markers ) => { - // Markers must be unique - const unique = Cypress._.uniq( markers ); - expect( - unique, - 'all markers are unique' - ).to.have.length( markers.length ); - - // All markers must be UUIDs - expect( markers[ 0 ] ).to.match( markerRegex ); - expect( markers[ 1 ] ).to.match( markerRegex ); - } ); - } ); - } ); - - it( `makes existing duplicate segment markers unique`, () => { - cy.createPost( { - title: `I see existing duplicate markers are replaced with unique markers`, - } ); - - // cy.closeWelcomeToBlockEditorTips() - cy.openBeyondwordsEditorPanel(); - - cy.getBlockEditorCheckbox( 'Generate audio' ).check(); - - // Add paragraph - cy.createBlockProgramatically( 'core/paragraph', { - content: 'One.', - attributes: { - beyondwordsMarker: '[DUPLICATE MARKER]', - }, - } ); - - // Add paragraph - cy.createBlockProgramatically( 'core/paragraph', { - content: 'Two.', - attributes: { - beyondwordsMarker: '[DUPLICATE MARKER]', - }, - } ); - - // Add paragraph - cy.createBlockProgramatically( 'core/paragraph', { - content: 'Three.', - attributes: { - beyondwordsMarker: '[DUPLICATE MARKER]', - }, - } ); - - cy.publishWithConfirmation(); - - // "View post" - cy.viewPostViaSnackbar(); - - cy.getPlayerScriptTag().should( 'exist' ); - cy.hasPlayerInstances( 1 ); - - cy.get( '.entry-content p:not(:empty)' ) - .should( 'have.length', 3 ) - .mapInvoke( 'getAttribute', 'data-beyondwords-marker' ) - .then( ( markers ) => { - // Markers must be unique - const unique = Cypress._.uniq( markers ); - expect( unique, 'all markers are unique' ).to.have.length( - markers.length - ); - - // All markers must be UUIDs - expect( markers[ 0 ] ).to.match( markerRegex ); - expect( markers[ 1 ] ).to.match( markerRegex ); - expect( markers[ 2 ] ).to.match( markerRegex ); - } ); - } ); -} ); diff --git a/tests/phpunit/Component/Post/BlockAttributes/BlockAttributesTest.php b/tests/phpunit/Component/Post/BlockAttributes/BlockAttributesTest.php index 1870959b..bdea5ced 100644 --- a/tests/phpunit/Component/Post/BlockAttributes/BlockAttributesTest.php +++ b/tests/phpunit/Component/Post/BlockAttributes/BlockAttributesTest.php @@ -1,15 +1,9 @@ assertEquals(10, has_action('register_block_type_args', array(BlockAttributes::class, 'registerAudioAttribute'))); $this->assertEquals(10, has_action('register_block_type_args', array(BlockAttributes::class, 'registerMarkerAttribute'))); - $this->assertEquals(10, has_action('render_block', array(BlockAttributes::class, 'renderBlock'))); } /** @@ -185,113 +178,4 @@ public function registerMarkerAttributeProvider($args) { ], ]; } - - /** - * @test - */ - public function renderBlockWithUiDisabled() - { - update_option(PlayerUI::OPTION_NAME, PlayerUI::DISABLED); - - $this->assertSame( - '

Test

', - BlockAttributes::renderBlock('

Test

', [ - 'attrs' => [ - 'beyondwordsMarker' => 'foo', - ] - ]) - ); - - delete_option(PlayerUI::OPTION_NAME); - } - - /** - * @test - */ - public function renderBlockWithoutCustomFields() - { - $postId = self::factory()->post->create([ - 'post_title' => 'BlockAttributesTest::renderBlockWithoutCustomFields', - 'post_type' => 'post', - ]); - - $this->go_to(get_permalink($postId)); - global $post; - setup_postdata($post); - - $this->assertSame( - '

Test

', - BlockAttributes::renderBlock('

Test

', [ - 'attrs' => [ - 'beyondwordsMarker' => 'foo', - ] - ]) - ); - - wp_reset_postdata(); - - wp_delete_post($postId, true); - } - - /** - * @test - */ - public function renderBlockWithoutMarkerAttribute() - { - $postId = self::factory()->post->create([ - 'post_title' => 'BlockAttributesTest::renderBlockWithoutMarkerAttribute', - 'meta_input' => [ - 'beyondwords_project_id' => BEYONDWORDS_TESTS_PROJECT_ID, - 'beyondwords_content_id' => BEYONDWORDS_TESTS_CONTENT_ID, - ], - ]); - - $this->go_to(get_permalink($postId)); - global $post; - setup_postdata($post); - - $this->assertSame( - '

Test

', - BlockAttributes::renderBlock('

Test

', [ - 'attrs' => [ - 'foo' => 'bar', - ] - ]) - ); - - wp_reset_postdata(); - - wp_delete_post($postId, true); - } - - /** - * @test - */ - public function renderBlockWithMarkerAttribute() - { - $postId = self::factory()->post->create([ - 'post_title' => 'BlockAttributesTest::renderBlockWithMarkerAttribute', - 'meta_input' => [ - 'beyondwords_project_id' => BEYONDWORDS_TESTS_PROJECT_ID, - 'beyondwords_content_id' => BEYONDWORDS_TESTS_CONTENT_ID, - ], - ]); - - $this->go_to(get_permalink($postId)); - global $post; - setup_postdata($post); - - $this->assertSame( - '

Test

', - BlockAttributes::renderBlock('

Test

', [ - 'attrs' => [ - 'beyondwordsMarker' => 'baz', - ] - ]) - ); - - wp_reset_postdata(); - - wp_delete_post($postId, true); - } } diff --git a/tests/phpunit/Core/Player/Renderer/AmpTest.php b/tests/phpunit/Core/Player/Renderer/AmpTest.php index 514a1183..475a0a9d 100644 --- a/tests/phpunit/Core/Player/Renderer/AmpTest.php +++ b/tests/phpunit/Core/Player/Renderer/AmpTest.php @@ -5,9 +5,11 @@ use \Symfony\Component\DomCrawler\Crawler; /** - * Class Amp + * Test the Amp player renderer. * - * Renders the AMP-compatible BeyondWords player. + * Note that we are are not testing Amp::check() here due to limitations + * with mocking the amp_is_request() function in the current test environment. + * The Amp::check() method is covered by integration tests when the AMP plugin is active. */ class AmpTest extends TestCase { @@ -29,47 +31,6 @@ public function tearDown(): void parent::tearDown(); } - /** - * @test - */ - public function check() - { - $this->markTestSkipped( - 'This test requires mocking amp_is_request() in a separate process, ' . - 'which conflicts with the current Xdebug configuration in the test environment. ' . - 'The Amp::check() method is covered by integration tests when the AMP plugin is active.' - ); - - // Note: Original test code is preserved below but not executed: - // - // Load stub to define amp_is_request() function - // require_once __DIR__ . '/../../../Stubs/amp_is_request_true.php'; - // - // $this->assertTrue(\amp_is_request()); - // - // // Test 1: Post without BeyondWords meta should return false - // $post = self::factory()->post->create_and_get([ - // 'post_title' => 'Amp::check::1', - // ]); - // - // $this->assertFalse(Amp::check($post)); - // - // wp_delete_post($post->ID, true); - // - // // Test 2: Post with BeyondWords content should return true - // $post = self::factory()->post->create_and_get([ - // 'post_title' => 'Amp::check::2', - // 'meta_input' => [ - // 'beyondwords_project_id' => BEYONDWORDS_TESTS_PROJECT_ID, - // 'beyondwords_podcast_id' => BEYONDWORDS_TESTS_CONTENT_ID, - // ], - // ]); - // - // $this->assertTrue(Amp::check($post)); - // - // wp_delete_post($post->ID, true); - } - /** * @test */ diff --git a/tests/phpunit/Core/PostContentUtilsTest.php b/tests/phpunit/Core/PostContentUtilsTest.php index 98d8939c..9f0a4f6d 100644 --- a/tests/phpunit/Core/PostContentUtilsTest.php +++ b/tests/phpunit/Core/PostContentUtilsTest.php @@ -140,14 +140,14 @@ public function getContentWithoutExcludedBlocksProvider() '

Previous two paragraphs were empty.

'; $withBlocksExpect = '

No marker.

' . - '

Has marker.

' . + '

Has marker.

' . + '

' . '

' . - '

' . '

Previous two paragraphs were empty.

'; - $withoutBlocks = "

One

\n\n

\n\n

Three

\n\n"; + $withoutBlocks = "

One

\n\n

\n\n

Three

\n\n"; - $withoutBlocksExpect = "

One

\n\n

\n\n

Three

"; + $withoutBlocksExpect = "

One

\n\n

\n\n

Three

"; return [ 'Content with blocks' => [ $withBlocks, $withBlocksExpect ], @@ -614,95 +614,4 @@ public function getAuthorName() wp_delete_post($post->ID, true); } - - /** - * @test - * @dataProvider addMarkerAttributeWithHTMLTagProcessorProvider - */ - public function addMarkerAttributeWithHTMLTagProcessor($html, $marker, $expect) { - $result = PostContentUtils::addMarkerAttributeWithHTMLTagProcessor($html, $marker); - - $this->assertSame($expect, trim($result)); - } - - public function addMarkerAttributeWithHTMLTagProcessorProvider($args) { - return [ - 'No HTML' => [ - 'html' => '', - 'marker' => 'foo', - 'expect' => '', - ], - 'No marker' => [ - 'html' => '

Text

', - 'marker' => '', - 'expect' => '

Text

', - ], - 'Paragraph' => [ - 'html' => '

Text

', - 'marker' => 'foo', - 'expect' => '

Text

', - ], - 'Empty paragraph' => [ - 'html' => '

', - 'marker' => 'foo', - 'expect' => '

', - ], - 'Existing attributes' => [ - 'html' => '

Text

', - 'marker' => 'foo', - 'expect' => '

Text

', - ], - 'Multiple root elements' => [ - 'html' => "
One
\n
Two
", - 'marker' => 'foo', - 'expect' => "
One
\n
Two
", - ], - ]; - } - - /** - * @test - * @dataProvider addMarkerAttributeWithDOMDocumentProvider - */ - public function addMarkerAttributeWithDOMDocument($html, $marker, $expect) - { - $result = PostContentUtils::addMarkerAttributeWithDOMDocument($html, $marker); - - $this->assertSame($expect, trim($result)); - } - - public function addMarkerAttributeWithDOMDocumentProvider($args) { - return [ - 'No HTML' => [ - 'html' => '', - 'marker' => 'foo', - 'expect' => '', - ], - 'No marker' => [ - 'html' => '

Text

', - 'marker' => '', - 'expect' => '

Text

', - ], - 'Paragraph' => [ - 'html' => '

Text

', - 'marker' => 'foo', - 'expect' => '

Text

', - ], - 'Empty paragraph' => [ - 'html' => '

', - 'marker' => 'foo', - 'expect' => '

', - ], - 'Existing attributes' => [ - 'html' => '

Text

', - 'marker' => 'foo', - 'expect' => '

Text

', - ], - 'Multiple root elements' => [ - 'html' => "
One
\n
Two
", - 'marker' => 'foo', - 'expect' => "
One
\n
Two
", - ], - ]; - } }