Skip to content

Commit 54344f8

Browse files
authored
Fix Following table error message for failed webfinger lookups (#2385)
1 parent fc46599 commit 54344f8

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: fixed
3+
4+
Fix Following table error message to display user input instead of empty string when webfinger lookup fails.

includes/wp-admin/table/class-following.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public function process_action() {
128128
$profile = Remote_Actors::normalize_identifier( $original );
129129
if ( ! $profile ) {
130130
/* translators: %s: Account profile that could not be followed */
131-
\add_settings_error( 'activitypub', 'followed', \sprintf( \__( 'Unable to follow account “%s”. Please verify the account exists and try again.', 'activitypub' ), \esc_html( $profile ) ) );
131+
\add_settings_error( 'activitypub', 'followed', \sprintf( \__( 'Unable to follow account “%s”. Please verify the account exists and try again.', 'activitypub' ), \esc_html( $original ) ) );
132132
$redirect_to = \add_query_arg( 'resource', $original, $redirect_to );
133133
break;
134134
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
5+
6+
test.describe( 'ActivityPub Following Table Admin UI', () => {
7+
test.beforeEach( async ( { admin, page } ) => {
8+
// Navigate to the Following page.
9+
await admin.visitAdminPage( 'users.php', 'page=activitypub-following-list' );
10+
11+
// Check if we got a permission error - if so, skip the test.
12+
const permissionError = page.locator( 'text=Sorry, you are not allowed to access this page' );
13+
const hasPermissionError = ( await permissionError.count() ) > 0;
14+
15+
test.skip( hasPermissionError, 'User does not have permission to access Following page' );
16+
} );
17+
18+
test( 'should load the Following page successfully', async ( { page } ) => {
19+
// Check that we're on the correct page.
20+
await expect( page.locator( 'h1.wp-heading-inline' ) ).toHaveText( 'Followings' );
21+
22+
// Verify the follow form is present.
23+
await expect( page.locator( '#activitypub-follow-form' ) ).toBeVisible();
24+
25+
// Verify the input field exists.
26+
await expect( page.locator( '#activitypub-profile' ) ).toBeVisible();
27+
28+
// Verify the submit button exists.
29+
await expect( page.locator( '#activitypub-follow-form input[type="submit"]' ) ).toBeVisible();
30+
} );
31+
32+
test( 'should display correct form labels and instructions', async ( { page } ) => {
33+
// Check the Follow heading.
34+
await expect( page.locator( '#col-left h2' ) ).toHaveText( 'Follow' );
35+
36+
// Verify instructions are present (check the first paragraph mentioning Fediverse).
37+
const instructionsParagraph = page
38+
.locator( '#col-left .form-wrap' )
39+
.locator( 'p' )
40+
.filter( { hasText: 'Fediverse' } )
41+
.first();
42+
await expect( instructionsParagraph ).toBeVisible();
43+
} );
44+
45+
test( 'should show error for invalid webfinger format', async ( { page } ) => {
46+
// Enter an invalid webfinger identifier (email-like format that will fail resolution).
47+
// This tests line 131 where normalize_identifier returns null for failed webfinger lookups.
48+
const invalidActor = '[email protected]';
49+
await page.locator( '#activitypub-profile' ).fill( invalidActor );
50+
51+
// Submit the form.
52+
await page.locator( '#activitypub-follow-form input[type="submit"]' ).click();
53+
54+
// Wait for page reload.
55+
await page.waitForLoadState( 'networkidle' );
56+
57+
// Check for error notice.
58+
const notice = page.locator( '.notice.notice-error' );
59+
await expect( notice ).toBeVisible();
60+
61+
// CRITICAL: Verify the error message shows what the user typed, not an empty string.
62+
// Bug: Without the fix at line 131, this would show 'Unable to follow account ""'.
63+
// Fix: Should show the original webfinger: "[email protected]".
64+
const errorText = await notice.textContent();
65+
expect( errorText ).toContain( 'Unable to follow account' );
66+
expect( errorText ).toContain( invalidActor );
67+
68+
// Verify the input field is populated with the invalid value.
69+
await expect( page.locator( '#activitypub-profile' ) ).toHaveValue( invalidActor );
70+
await expect( page.locator( '#activitypub-profile' ) ).toHaveClass( /highlight/ );
71+
} );
72+
73+
test( 'should show error for empty form submission', async ( { page } ) => {
74+
// Leave the form empty and submit.
75+
await page.locator( '#activitypub-follow-form input[type="submit"]' ).click();
76+
77+
// Wait for page reload.
78+
await page.waitForLoadState( 'networkidle' );
79+
80+
// Check for error notice.
81+
const notice = page.locator( '.notice.notice-error' );
82+
await expect( notice ).toBeVisible();
83+
await expect( notice ).toContainText( 'Unable to follow account' );
84+
} );
85+
86+
test( 'should display the followings table', async ( { page } ) => {
87+
// Check that the table exists.
88+
await expect( page.locator( 'table.wp-list-table' ) ).toBeVisible();
89+
90+
// Verify table headers.
91+
const headers = page.locator( 'table.wp-list-table thead th' );
92+
await expect( headers ).toContainText( [ 'Username', 'Name', 'Profile', 'Last updated' ] );
93+
} );
94+
95+
test( 'should display filter views', async ( { page } ) => {
96+
// Check that the view filters exist.
97+
await expect( page.locator( '.subsubsub' ) ).toBeVisible();
98+
99+
// Verify the All, Accepted, and Pending links exist.
100+
await expect( page.locator( '.subsubsub a' ).filter( { hasText: 'All' } ) ).toBeVisible();
101+
await expect( page.locator( '.subsubsub a' ).filter( { hasText: 'Accepted' } ) ).toBeVisible();
102+
await expect( page.locator( '.subsubsub a' ).filter( { hasText: 'Pending' } ) ).toBeVisible();
103+
} );
104+
105+
test( 'should display About Followings section', async ( { page } ) => {
106+
// Check for the informational section.
107+
await expect( page.locator( '.edit-term-notes strong' ) ).toHaveText( 'About Followings' );
108+
await expect( page.locator( '.edit-term-notes p' ) ).toContainText( 'Pending' );
109+
await expect( page.locator( '.edit-term-notes p' ) ).toContainText( 'ActivityPub protocol' );
110+
} );
111+
112+
test( 'should display no profiles message when empty', async ( { page } ) => {
113+
// This test checks the empty state message.
114+
const noItemsMessage = page.locator( '.no-items' );
115+
const tableRows = page.locator( 'table.wp-list-table tbody tr' );
116+
const rowCount = await tableRows.count();
117+
118+
// If there's only one row and it contains no-items message.
119+
if ( rowCount === 1 ) {
120+
const hasNoItems = ( await noItemsMessage.count() ) > 0;
121+
if ( hasNoItems ) {
122+
await expect( noItemsMessage ).toContainText( 'No profiles found' );
123+
}
124+
}
125+
} );
126+
127+
test( 'should preserve page structure after form submission', async ( { page } ) => {
128+
// Submit form with invalid data.
129+
await page.locator( '#activitypub-profile' ).fill( 'invalid' );
130+
await page.locator( '#activitypub-follow-form input[type="submit"]' ).click();
131+
132+
// Wait for page reload.
133+
await page.waitForLoadState( 'networkidle' );
134+
135+
// Verify page structure is intact.
136+
await expect( page.locator( 'h1.wp-heading-inline' ) ).toHaveText( 'Followings' );
137+
await expect( page.locator( '#activitypub-follow-form' ) ).toBeVisible();
138+
await expect( page.locator( 'table.wp-list-table' ) ).toBeVisible();
139+
} );
140+
} );

0 commit comments

Comments
 (0)