Require a name and birth year for every student
Both fields are marked in their labels the same way a required registration question is, and enforced on the server whichever form they arrive from: GuardianService::createChild() and updateChild() now refuse a blank name or an unusable birth year, and the signup form checks the same rule up front, before it creates a single user, so a bad block never leaves a half-registered family behind. normaliseBirthYear() became public and static so both paths share one definition of what a usable year is. The signup form cannot lean on the browser here. Its child blocks are hidden until the parent/guardian box is ticked, and a `required` field inside a hidden container makes the whole form unsubmittable with no control the user can reach to fix — the same trap the guardian's own question panel already sidesteps by disabling rather than hiding. So register.js puts `required` on and takes it off along with the block itself, and the server is what makes the rule hold with JavaScript off. The profile screen has no such problem: its forms are always visible, so the attribute is static there. One behaviour change beyond the requirement: a child block with anything typed into it is now reported back instead of dropped. Previously any block without a name was silently discarded, which would now mean losing a birth year the guardian had filled in. A wholly untouched spare block — the one the form always renders for "add another" — is still ignored. Verified the required-toggling in a headless browser: unticked submits, ticked blocks an empty block, a cloned block inherits the requirement, and re-unticking leaves nothing behind to block a non-guardian signup. Closes #148 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -116,20 +116,35 @@ class GuardianServiceTest extends TestCase
|
||||
|
||||
Functions\expect('wp_delete_user')->once()->with(42);
|
||||
|
||||
self::assertInstanceOf(\WP_Error::class, $this->service->createChild(5, 'Ada'));
|
||||
self::assertInstanceOf(\WP_Error::class, $this->service->createChild(5, 'Ada', '2015'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unusableBirthYears
|
||||
*/
|
||||
public function testCreateChildClearsAnUnusableBirthYear(string $submitted): void
|
||||
public function testCreateChildRefusesAnUnusableBirthYear(string $submitted): void
|
||||
{
|
||||
Functions\when('wp_insert_user')->justReturn(42);
|
||||
$this->guardians->shouldReceive('insert')->once()->andReturn(7);
|
||||
// Refused before anything is written, so no orphan user is left behind.
|
||||
Functions\expect('wp_insert_user')->never();
|
||||
$this->guardians->shouldNotReceive('insert');
|
||||
|
||||
$this->service->createChild(5, 'Ada', $submitted);
|
||||
$result = $this->service->createChild(5, 'Ada', $submitted);
|
||||
|
||||
self::assertArrayNotHasKey(GuardianService::META_BIRTH_YEAR, $this->meta[42] ?? []);
|
||||
self::assertInstanceOf(\WP_Error::class, $result);
|
||||
self::assertSame('missing_birth_year', $result->get_error_code());
|
||||
self::assertArrayNotHasKey(42, $this->meta);
|
||||
}
|
||||
|
||||
/** @dataProvider unusableBirthYears */
|
||||
public function testUpdateChildRefusesAnUnusableBirthYear(string $submitted): void
|
||||
{
|
||||
$this->guardians->shouldReceive('isGuardianOf')->with(5, 42)->andReturn(true);
|
||||
Functions\expect('wp_update_user')->never();
|
||||
|
||||
$result = $this->service->updateChild(5, 42, 'Ada', $submitted);
|
||||
|
||||
self::assertInstanceOf(\WP_Error::class, $result);
|
||||
self::assertSame('missing_birth_year', $result->get_error_code());
|
||||
}
|
||||
|
||||
/** @return array<string, array{string}> */
|
||||
@@ -142,6 +157,7 @@ class GuardianServiceTest extends TestCase
|
||||
'too many digits' => ['20155'],
|
||||
'before 1900' => ['1899'],
|
||||
'later than today' => ['2027'],
|
||||
'left blank' => [''],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -230,9 +246,8 @@ class GuardianServiceTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Saving a child drops the legacy full date. Without that, clearing the birth
|
||||
* year on a child who predates the change would leave the old date behind for
|
||||
* the fallback above to resurrect on the next read.
|
||||
* Saving a child drops the legacy full date, so the fallback above can never
|
||||
* outrank a year the guardian has since corrected by hand.
|
||||
*/
|
||||
public function testSavingAChildClearsTheLegacyDateOfBirth(): void
|
||||
{
|
||||
@@ -241,15 +256,16 @@ class GuardianServiceTest extends TestCase
|
||||
$this->guardians->shouldReceive('isGuardianOf')->with(5, 42)->andReturn(true);
|
||||
Functions\when('wp_update_user')->justReturn(42);
|
||||
|
||||
self::assertNull($this->service->updateChild(5, 42, 'Ada L', ''));
|
||||
self::assertNull($this->service->updateChild(5, 42, 'Ada L', '2016'));
|
||||
|
||||
self::assertArrayNotHasKey(GuardianService::META_DOB, $this->meta[42] ?? []);
|
||||
self::assertArrayNotHasKey(GuardianService::META_BIRTH_YEAR, $this->meta[42] ?? []);
|
||||
self::assertSame('2016', $this->meta[42][GuardianService::META_BIRTH_YEAR]);
|
||||
|
||||
// The corrected year is what is read back, not the year of the old date.
|
||||
$this->guardians->shouldReceive('findByGuardian')->with(5)->andReturn([new GuardianLink(5, 42)]);
|
||||
Functions\when('get_userdata')->justReturn($this->user(42, 'Ada', 'Lovelace'));
|
||||
|
||||
self::assertSame('', $this->service->children(5)[0]['birth_year']);
|
||||
self::assertSame('2016', $this->service->children(5)[0]['birth_year']);
|
||||
}
|
||||
|
||||
public function testBookableStudentsIsJustTheUserWithoutChildren(): void
|
||||
|
||||
Reference in New Issue
Block a user