feat(setup): post-link redirects to SPA so first-setup matches live UI
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
Twig configure page replaced with a redirect: SetupController's index,
register, login, and the legacy /configure route all post-link redirect
to /?setup=<deviceId> for unconfigured devices. The SPA's HomeView
auto-opens its existing settings sheet for that id, with the same
controls everyone uses for live edits — themed to the user's choice,
pre-populated from the device record.
Fixes Matt's report:
- "every 6 hours" lost on save: the configure form posted
rotation_interval_hours but the controller read
rotation_interval_minutes, so the value silently defaulted to
1440 every time. Now the SPA's PATCH flow handles it correctly.
- "old settings still there in live settings": SPA settings sheet
pre-populates from the device's current state via onEdit.
- "uniqueness window in setup but not live settings": removed
from the (now-deleted) Twig form; both surfaces are consistent.
- "color scheme didn't match account": SPA respects the user's
theme natively (data-theme on <html>), so the first-setup screen
looks like the rest of the app.
Also adds a "Sign out of pictureFrame" link at the bottom of the
per-frame settings sheet (the existing /settings tab still has the
primary one). Easy escape hatch from a deeply-nested settings flow.
Tests:
- SetupControllerTest: S-03/04/05/06/08 updated for new redirect
targets, S-CLAIM-03 updated.
- HomeView.test.ts: useRoute now mockable per-test, two new cases
pinning the ?setup=<id> auto-open and its absence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,19 +40,22 @@ class SetupControllerTest extends AppWebTestCase
|
||||
$this->assertResponseRedirects('/');
|
||||
}
|
||||
|
||||
// S-03: authenticated with no existing device (new link) → redirects to configure
|
||||
public function test_setup_index_authenticated_new_device_redirects_to_configure(): void
|
||||
// S-03: authenticated with no existing device → links and redirects to
|
||||
// /?setup=<id> so the SPA opens its settings sheet for first-time setup.
|
||||
public function test_setup_index_authenticated_new_device_redirects_to_spa_setup(): void
|
||||
{
|
||||
$user = $this->createUser('setup03@example.com');
|
||||
$this->loginAs($user);
|
||||
|
||||
$this->client->request('GET', '/setup/' . self::MAC);
|
||||
|
||||
$this->assertResponseRedirects('/setup/' . self::MAC . '/configure');
|
||||
$this->assertResponseRedirects();
|
||||
$location = $this->client->getResponse()->headers->get('Location');
|
||||
$this->assertStringContainsString('/?setup=', $location, 'redirects to SPA with setup query');
|
||||
}
|
||||
|
||||
// S-04: POST /setup/{mac}/register with valid data → creates user, links device, redirects to configure
|
||||
public function test_setup_register_creates_user_and_redirects_to_configure(): void
|
||||
// S-04: POST /register links + redirects to SPA with ?setup=<id>.
|
||||
public function test_setup_register_creates_user_and_redirects_to_spa_setup(): void
|
||||
{
|
||||
$this->client->request('POST', '/setup/' . self::MAC . '/register', [
|
||||
'registration_form' => [
|
||||
@@ -64,11 +67,13 @@ class SetupControllerTest extends AppWebTestCase
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertResponseRedirects('/setup/' . self::MAC . '/configure');
|
||||
$this->assertResponseRedirects();
|
||||
$this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
|
||||
}
|
||||
|
||||
// S-05: POST /setup/{mac}/configure with valid name → saves device, redirects to spa
|
||||
public function test_setup_configure_saves_name_and_redirects_to_spa(): void
|
||||
// S-05: legacy /configure POST is now a redirect to the SPA — the
|
||||
// first-time settings UI lives in the live PWA, not Twig.
|
||||
public function test_legacy_configure_post_redirects_to_spa_setup(): void
|
||||
{
|
||||
$user = $this->createUser('setup05@example.com');
|
||||
|
||||
@@ -78,25 +83,18 @@ class SetupControllerTest extends AppWebTestCase
|
||||
$this->em()->persist($device);
|
||||
$this->em()->flush();
|
||||
|
||||
$deviceId = $device->getId();
|
||||
|
||||
$this->loginAs($user);
|
||||
// Old form POST — controller doesn't process the body, just redirects.
|
||||
$this->client->request('POST', '/setup/' . self::MAC . '/configure', [
|
||||
'name' => 'Kitchen Frame',
|
||||
'orientation' => 'landscape',
|
||||
'rotation_interval_minutes' => '1440',
|
||||
'uniqueness_window' => '10',
|
||||
'name' => 'Kitchen Frame',
|
||||
]);
|
||||
|
||||
$this->assertResponseRedirects('/');
|
||||
|
||||
$this->em()->clear();
|
||||
$saved = $this->em()->find(Device::class, $deviceId);
|
||||
$this->assertSame('Kitchen Frame', $saved->getName());
|
||||
$this->assertResponseRedirects();
|
||||
$this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
|
||||
}
|
||||
|
||||
// S-06: POST /setup/{mac}/login with valid credentials → redirects to configure
|
||||
public function test_login_valid_credentials_redirects_to_configure(): void
|
||||
// S-06: /login → links + redirects to SPA setup.
|
||||
public function test_login_valid_credentials_redirects_to_spa_setup(): void
|
||||
{
|
||||
$this->createUser('setuplogin@example.com', 'testpass');
|
||||
|
||||
@@ -105,7 +103,8 @@ class SetupControllerTest extends AppWebTestCase
|
||||
'_password' => 'testpass',
|
||||
]);
|
||||
|
||||
$this->assertResponseRedirects('/setup/' . self::MAC . '/configure');
|
||||
$this->assertResponseRedirects();
|
||||
$this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
|
||||
}
|
||||
|
||||
// S-07: POST /setup/{mac}/login with wrong password → redirects to index
|
||||
@@ -121,8 +120,9 @@ class SetupControllerTest extends AppWebTestCase
|
||||
$this->assertResponseRedirects('/setup/' . self::MAC);
|
||||
}
|
||||
|
||||
// S-08: authenticated GET /setup/{mac}/configure → 200
|
||||
public function test_configure_get_renders_form(): void
|
||||
// S-08: GET /setup/{mac}/configure also redirects (legacy, kept for
|
||||
// any in-flight bookmarks).
|
||||
public function test_legacy_configure_get_redirects_to_spa(): void
|
||||
{
|
||||
$user = $this->createUser('setupconfigget@example.com');
|
||||
|
||||
@@ -135,28 +135,10 @@ class SetupControllerTest extends AppWebTestCase
|
||||
$this->loginAs($user);
|
||||
$this->client->request('GET', '/setup/' . self::MAC . '/configure');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertResponseRedirects();
|
||||
$this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
|
||||
}
|
||||
|
||||
// S-09: POST /setup/{mac}/configure with empty name → 200 (re-renders form with error)
|
||||
public function test_configure_post_empty_name_renders_error(): void
|
||||
{
|
||||
$user = $this->createUser('setupconfigerr@example.com');
|
||||
|
||||
$device = new Device();
|
||||
$device->setMac(self::MAC);
|
||||
$device->setUser($user);
|
||||
$this->em()->persist($device);
|
||||
$this->em()->flush();
|
||||
|
||||
$this->loginAs($user);
|
||||
$this->client->request('POST', '/setup/' . self::MAC . '/configure', [
|
||||
'name' => '',
|
||||
'orientation' => 'landscape',
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
// S-10: POST /setup/{mac}/register with invalid form data → re-renders registration page
|
||||
public function test_setup_register_invalid_form_renders_page(): void
|
||||
@@ -260,7 +242,8 @@ class SetupControllerTest extends AppWebTestCase
|
||||
],
|
||||
'claim_device' => '1',
|
||||
]);
|
||||
$this->assertResponseRedirects('/setup/' . self::MAC . '/configure');
|
||||
$this->assertResponseRedirects();
|
||||
$this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
|
||||
|
||||
$this->em()->clear();
|
||||
$reloaded = $this->em()->find(Device::class, $deviceId);
|
||||
|
||||
Reference in New Issue
Block a user