diff --git a/includes/Traits/Exclude_Current.php b/includes/Traits/Exclude_Current.php index 81c5fdf..d9ff053 100644 --- a/includes/Traits/Exclude_Current.php +++ b/includes/Traits/Exclude_Current.php @@ -20,23 +20,35 @@ public function process_exclude_current(): void { /** * Helper to generate the array * - * @param mixed $to_exclude The value to be excluded. + * @param int $exclude_current_post The value to be excluded. * * @return array The ids to exclude */ - public function get_exclude_ids( $to_exclude ) { + public function get_exclude_ids( $exclude_current_post ) { // If there are already posts to be excluded, we need to add to them. - $exclude_ids = $this->custom_args['post__not_in'] ?? array(); - - if ( $this->is_post_id( $to_exclude ) ) { - array_push( $exclude_ids, intval( $to_exclude ) ); + $exclude_ids = $this->custom_args['post__not_in'] ?? array(); + $post_to_exclude = 0; + if ( true !== $exclude_current_post && is_numeric( $exclude_current_post ) && $exclude_current_post >= 1 ) { + $post_to_exclude = intval( $exclude_current_post ); } else { - // This is usually when this was set on a template. - global $post; - if ( $post ) { - array_push( $exclude_ids, $post->ID ); + // Try to get the queried object ID (frontend context) + if ( function_exists( 'get_queried_object_id' ) ) { + $post_to_exclude = get_queried_object_id(); + } + + // Fallback to global $post (editor, unit tests, etc.) + if ( ! $post_to_exclude ) { + global $post; + if ( $post && isset( $post->ID ) ) { + $post_to_exclude = $post->ID; + } } } + + if ( $post_to_exclude > 0 ) { + array_push( $exclude_ids, intval( $post_to_exclude ) ); + } + return $exclude_ids; } } diff --git a/src/components/post-exclude-controls.js b/src/components/post-exclude-controls.js index 16e431a..f494bea 100644 --- a/src/components/post-exclude-controls.js +++ b/src/components/post-exclude-controls.js @@ -91,9 +91,15 @@ const ExcludeCurrentPostControl = ( { const isDisabled = () => { // If the user is not an admin, they cannot edit template anyway - if ( ! isAdmin ) { + if ( ! isAdmin || ! currentPost ) { return false; } + + // Only disable if we're editing a template AND it's in the list + if ( currentPost.type !== 'wp_template' ) { + return false; + } + const templatesToExclude = [ 'archive', 'search' ]; const { show_on_front: showOnFront, // What is the front page set to show? Options: 'posts' or 'page' @@ -102,10 +108,7 @@ const ExcludeCurrentPostControl = ( { ...templatesToExclude, ...( showOnFront === 'posts' ? [ 'home', 'front-page' ] : [] ), ]; - return ( - currentPost.type === 'wp_template' && - disabledTemplates.includes( currentPost.slug ) - ); + return disabledTemplates.includes( currentPost.slug ); }; return ( @@ -118,7 +121,7 @@ const ExcludeCurrentPostControl = ( { setAttributes( { query: { ...attributes.query, - exclude_current: value ? currentPost.id : 0, + exclude_current: value, }, } ); } } diff --git a/tests/e2e/Playground.ts b/tests/e2e/Playground.ts index 3ca4c75..41522ad 100644 --- a/tests/e2e/Playground.ts +++ b/tests/e2e/Playground.ts @@ -30,7 +30,7 @@ export class Playground { }, ], blueprint, - port: 8889, + port: 9876, quiet: true, } ); this.handler = this.cliServer.requestHandler; diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 5192de2..c016f8f 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig( { use: { /* Collect trace when retrying the failed test. */ trace: 'on-first-retry', - baseURL: 'http://127.0.0.1:8889/', + baseURL: 'http://127.0.0.1:9876/', }, /* Configure projects for major browsers */ diff --git a/tests/e2e/tests/README-exclude-current-post.md b/tests/e2e/tests/README-exclude-current-post.md new file mode 100644 index 0000000..400fbf2 --- /dev/null +++ b/tests/e2e/tests/README-exclude-current-post.md @@ -0,0 +1,59 @@ +# Exclude Current Post E2E Tests + +This test suite covers the "Exclude Current Post" control functionality in the Advanced Query Loop block. + +## Test Coverage + +### ✅ Tests for Regular Posts + +1. **Initial state** - Verifies the control is visible and unchecked by default +2. **Toggle on** - Verifies toggling the control on stores `true` in block attributes +3. **Toggle off** - Verifies toggling the control off stores `false` in block attributes +4. **Not disabled** - Verifies the control is enabled in regular posts + +## Running the Tests + +```bash +# Run all exclude current post tests +npm run test:e2e -- tests/exclude-current-post.spec.ts + +# Run with UI +npm run test:e2e:ui -- tests/exclude-current-post.spec.ts +``` + +## Future Test Additions + +The following test scenarios were planned but require additional setup/configuration: + +### Templates +- Should be disabled in archive template +- Should be disabled in search template +- Should be disabled in home/front-page templates (when show_on_front is 'posts') +- Should be enabled in single template +- Should work in single template and store boolean value + +### Synced Patterns +- Should be visible and functional in a synced pattern +- Synced pattern with exclude current should work when inserted in a post + +These tests require: +- Proper theme configuration in the test environment +- Site Editor navigation that works reliably with Playground +- Pattern creation workflow that's compatible with the test environment + +## Test Structure + +All tests follow the same pattern: +1. Initialize Playground with blueprint +2. Visit post editor +3. Insert AQL block with custom query +4. Interact with "Exclude Current Post" control +5. Assert expected behavior +6. Clean up Playground instance + +## Notes + +- Tests use WordPress Playground via `@wp-playground/cli` for isolated testing +- Each test gets a fresh WordPress instance +- The `insertAQL` utility handles block insertion and variation selection +- Tests verify both UI state and block attributes diff --git a/tests/e2e/tests/exclude-current-post.spec.ts b/tests/e2e/tests/exclude-current-post.spec.ts new file mode 100644 index 0000000..c91a6a1 --- /dev/null +++ b/tests/e2e/tests/exclude-current-post.spec.ts @@ -0,0 +1,290 @@ +/** + * Import our custom test fixtures. + */ +import { test, expect } from '../aql-fixtures'; + +/** + * Internal dependencies. + */ +import { insertAQL } from '../utils'; + +test.describe( 'Exclude Current Post', () => { + test.beforeEach( async ( { page, editor, playground, admin } ) => { + await playground.init( { page, editor } ); + await admin.visitAdminPage( 'post-new.php' ); + + await editor.setPreferences( 'core/edit-post', { + welcomeGuide: false, + fullscreenMode: false, + } ); + await insertAQL( { editor, page } ); + } ); + + test.afterEach( async ( { playground } ) => { + await playground.cleanUp(); + } ); + + test( 'Initial state - should be visible and unchecked', async ( { + page, + editor, + } ) => { + await expect( + page.getByRole( 'checkbox', { + name: 'Exclude Current Post', + } ) + ).toBeVisible(); + + await expect( + page.getByRole( 'checkbox', { + name: 'Exclude Current Post', + } ) + ).not.toBeChecked(); + + const blocks = await editor.getBlocks(); + + expect( blocks[ 0 ].attributes.query.exclude_current ).toBeUndefined(); + } ); + + test( 'Should toggle on and store true', async ( { page, editor } ) => { + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + await expect( + page.getByRole( 'checkbox', { + name: 'Exclude Current Post', + } ) + ).toBeChecked(); + + const blocks = await editor.getBlocks(); + + expect( blocks[ 0 ].attributes.query.exclude_current ).toEqual( true ); + } ); + + test( 'Should toggle off and store false', async ( { page, editor } ) => { + // Toggle on first + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + // Then toggle off + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + await expect( + page.getByRole( 'checkbox', { + name: 'Exclude Current Post', + } ) + ).not.toBeChecked(); + + const blocks = await editor.getBlocks(); + + expect( blocks[ 0 ].attributes.query.exclude_current ).toEqual( false ); + } ); + + test( 'Should not be disabled in a regular post', async ( { page } ) => { + await expect( + page.getByRole( 'checkbox', { + name: 'Exclude Current Post', + } ) + ).toBeEnabled(); + } ); +} ); + +test.describe( 'Exclude Current Post - Frontend Rendering', () => { + test.beforeEach( async ( { page, editor, playground, admin } ) => { + await playground.init( { page, editor } ); + } ); + + test.afterEach( async ( { playground } ) => { + await playground.cleanUp(); + } ); + + test( 'Should exclude current post from query results on frontend', async ( { + page, + editor, + admin, + } ) => { + test.setTimeout( 60000 ); + + // Create one test post - enough to verify exclusion without slowing CI. + await admin.createNewPost( { title: 'Test Post Alpha' } ); + await editor.publishPost(); + + // Create the main post with AQL block + await admin.createNewPost( { title: 'Main Post with AQL' } ); + + await editor.setPreferences( 'core/edit-post', { + welcomeGuide: false, + fullscreenMode: false, + } ); + + // Insert AQL block + await insertAQL( { editor, page } ); + + // Enable "Exclude Current Post" + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + // Verify it's enabled + await expect( + page.getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + ).toBeChecked(); + + // Publish the post + await editor.publishPost(); + + // Get the post URL from the publish panel + const postUrl = await page + .locator( '.post-publish-panel__postpublish-buttons a' ) + .first() + .getAttribute( 'href' ); + + expect( postUrl ).toBeTruthy(); + + // Navigate to the frontend + await page.goto( postUrl! ); + + // Wait for the page to load + await page.waitForLoadState( 'networkidle' ); + + // The AQL block is the first query loop inserted into the post content. + // The theme may also render its own query loops (e.g. a "More posts" + // section), so we scope to the first .wp-block-query to isolate our block. + const aqlQueryLoop = page.locator( '.wp-block-query' ).first(); + await expect( aqlQueryLoop ).toBeVisible(); + + const postTitlesInAQL = aqlQueryLoop.locator( '.wp-block-post-title' ); + const aqlTitles = await postTitlesInAQL.allTextContents(); + + // The AQL block should NOT include the current post in its results. + expect( aqlTitles ).not.toContain( 'Main Post with AQL' ); + + // Other posts should still appear. + expect( aqlTitles ).toContain( 'Test Post Alpha' ); + } ); + + test( 'Should include current post when exclude_current is false', async ( { + page, + editor, + admin, + } ) => { + test.setTimeout( 60000 ); + + // Create one test post - enough to verify inclusion without slowing CI. + await admin.createNewPost( { title: 'Test Post One' } ); + await editor.publishPost(); + + // Create main post with AQL block + await admin.createNewPost( { title: 'Main Post Without Exclusion' } ); + + await editor.setPreferences( 'core/edit-post', { + welcomeGuide: false, + fullscreenMode: false, + } ); + + // Insert AQL block + await insertAQL( { editor, page } ); + + // Do NOT enable "Exclude Current Post" - leave it unchecked + + // Verify it's NOT checked + await expect( + page.getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + ).not.toBeChecked(); + + // Publish the post + await editor.publishPost(); + + // Get the post URL + const postUrl = await page + .locator( '.post-publish-panel__postpublish-buttons a' ) + .first() + .getAttribute( 'href' ); + + expect( postUrl ).toBeTruthy(); + + // Navigate to frontend + await page.goto( postUrl! ); + await page.waitForLoadState( 'networkidle' ); + + // Get all post titles in the query loop + const postLinksInLoop = page.locator( + '.wp-block-query .wp-block-post-title a' + ); + + await expect( postLinksInLoop.first() ).toBeVisible(); + + const displayedTitles = await postLinksInLoop.allTextContents(); + + // Verify "Main Post Without Exclusion" IS in the displayed titles + expect( displayedTitles ).toContain( 'Main Post Without Exclusion' ); + + // Verify test post is also displayed + expect( displayedTitles ).toContain( 'Test Post One' ); + } ); + + test( 'Should work correctly when toggled on then off', async ( { + page, + editor, + admin, + } ) => { + test.setTimeout( 60000 ); + + // Create test posts + await admin.createNewPost( { title: 'Another Test Post' } ); + await editor.publishPost(); + + // Create main post + await admin.createNewPost( { title: 'Toggle Test Post' } ); + + await editor.setPreferences( 'core/edit-post', { + welcomeGuide: false, + fullscreenMode: false, + } ); + + // Insert AQL block + await insertAQL( { editor, page } ); + + // Toggle ON + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + // Toggle OFF + await page + .getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + .click(); + + // Verify it's unchecked + await expect( + page.getByRole( 'checkbox', { name: 'Exclude Current Post' } ) + ).not.toBeChecked(); + + // Publish + await editor.publishPost(); + + // Get URL and navigate to frontend + const postUrl = await page + .locator( '.post-publish-panel__postpublish-buttons a' ) + .first() + .getAttribute( 'href' ); + + await page.goto( postUrl! ); + await page.waitForLoadState( 'networkidle' ); + + // Get displayed titles + const postLinksInLoop = page.locator( + '.wp-block-query .wp-block-post-title a' + ); + + await expect( postLinksInLoop.first() ).toBeVisible(); + + const displayedTitles = await postLinksInLoop.allTextContents(); + + // Since exclude_current is OFF, the post should be included + expect( displayedTitles ).toContain( 'Toggle Test Post' ); + } ); +} );