Add an account block showing who is signed in
CI / Tests (PHP 8.2) (pull_request) Successful in 42s
CI / Tests (PHP 8.1) (pull_request) Successful in 52s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / PHPStan (pull_request) Successful in 2m55s

[us_account], or the Account block: the signed-in visitor's name, their
email, a Sign out link, and — only when the account books for someone
besides itself — the students it books for. A parent's first question on
seeing "signed in as Grace" is whether this is the account their children's
lessons are on.

Two decisions worth naming.

Signed out with no login page chosen, the block renders nothing. Its whole
subject is the person signed in, which a stranger is not, and a bare "you
are not signed in" in a site header is noise with no way to act on it. With
a login page chosen it offers a Sign in link instead. The editor preview is
populated regardless, so the block is never an invisible box to the person
placing it.

Signing out returns to the chosen login page, or to the current page when
there is none. A block meant for a header should not also navigate someone
somewhere when they use it; the login page wins when configured, because the
page they were on may well be members-only.

The name comes from UserName::format(), so the block never exposes a
username the way display_name can.

Also brings docs/features/editor-blocks.md back in step: it still described
"four shortcodes" and had never listed the family block.

Closes #142

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-07-29 22:31:28 -03:00
co-authored by Claude Opus 5
parent f0149042cc
commit 46cee7a454
13 changed files with 430 additions and 7 deletions
+158
View File
@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace Unsupervised\Schedular\Tests\Unit\Auth;
use Brain\Monkey\Functions;
use Mockery;
use Unsupervised\Schedular\Auth\AccountPage;
use Unsupervised\Schedular\Guardian\GuardianService;
use Unsupervised\Schedular\Tests\Unit\TestCase;
class AccountPageTest extends TestCase
{
private GuardianService&Mockery\MockInterface $guardians;
private AccountPage $page;
protected function setUp(): void
{
parent::setUp();
$this->guardians = Mockery::mock(GuardianService::class);
$this->page = new AccountPage($this->guardians);
Functions\when('is_user_logged_in')->justReturn(true);
Functions\when('get_current_user_id')->justReturn(5);
Functions\when('wp_enqueue_style')->justReturn(null);
Functions\when('get_permalink')->alias(
static fn (int $id = 0): string => $id > 0
? 'https://studio.test/sign-in/'
: 'https://studio.test/current/'
);
Functions\when('wp_logout_url')->alias(
static fn (string $redirect): string => 'https://studio.test/wp-login.php?action=logout&redirect_to=' . rawurlencode($redirect)
);
Functions\when('wp_get_current_user')->justReturn($this->user('Grace', 'Hopper', '[email protected]'));
}
private function user(string $first, string $last, string $email): \WP_User
{
$user = Mockery::mock(\WP_User::class);
$user->ID = 5;
$user->first_name = $first;
$user->last_name = $last;
$user->nickname = '';
$user->user_email = $email;
return $user;
}
/** @param list<array{id: int, name: string, is_self: bool}> $students */
private function bookable(array $students): void
{
$this->guardians->shouldReceive('bookableStudents')->with(5)->andReturn($students);
}
public function testShowsTheSignedInNameAndEmail(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
$html = $this->page->render([]);
self::assertStringContainsString('Grace Hopper', $html);
self::assertStringContainsString('[email protected]', $html);
self::assertStringContainsString('Sign out', $html);
}
public function testNamesTheStudentsTheAccountBooksFor(): void
{
$this->bookable([
['id' => 42, 'name' => 'Ada', 'is_self' => false],
['id' => 43, 'name' => 'Alan', 'is_self' => false],
['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true],
]);
self::assertStringContainsString('Booking for Ada, Alan', $this->page->render([]));
}
/**
* An account that only books for itself has nothing to add — "Booking for
* Grace Hopper" under "Grace Hopper" is noise.
*/
public function testSaysNothingAboutStudentsOnAPlainAccount(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringNotContainsString('Booking for', $this->page->render([]));
}
public function testSigningOutReturnsToTheConfiguredLoginPage(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/sign-in/'),
$this->page->render(['loginPageId' => 9])
);
}
/**
* With no page chosen, signing out from a header link should leave the
* visitor where they were rather than navigating them somewhere.
*/
public function testSigningOutReturnsToTheCurrentPageWhenNoLoginPageIsSet(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/current/'),
$this->page->render([])
);
}
public function testTheShortcodeAttributeNameIsAccepted(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/sign-in/'),
$this->page->render(['login_page_id' => 9])
);
}
/**
* A block whose whole job is "you are signed in as X" has nothing to say to
* a stranger, and a bare notice in a site header cannot be acted on.
*/
public function testRendersNothingForASignedOutVisitorWithNoLoginPage(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
$this->guardians->shouldNotReceive('bookableStudents');
self::assertSame('', $this->page->render([]));
}
public function testOffersASignInLinkToASignedOutVisitorWhenAPageIsChosen(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
$this->guardians->shouldNotReceive('bookableStudents');
$html = $this->page->render(['loginPageId' => 9]);
self::assertStringContainsString('https://studio.test/sign-in/', $html);
self::assertStringContainsString('Sign in', $html);
self::assertStringNotContainsString('Sign out', $html);
}
/**
* A page can be deleted after it has been chosen in the block, which
* get_permalink() reports as false.
*/
public function testTreatsADeletedLoginPageAsNoneChosen(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
Functions\when('get_permalink')->justReturn(false);
self::assertSame('', $this->page->render(['loginPageId' => 9]));
}
}