diff --git a/tests/Feature/Http/Controllers/Subscribers/SubscribeTest.php b/tests/Feature/Http/Controllers/Subscribers/SubscribeTest.php
index d8561832..227c1004 100644
--- a/tests/Feature/Http/Controllers/Subscribers/SubscribeTest.php
+++ b/tests/Feature/Http/Controllers/Subscribers/SubscribeTest.php
@@ -24,7 +24,6 @@
it('shows the subscribe page', function () {
get(route('cachet.subscribers.create'))
- ->assertOk()
->assertSee(__('cachet::subscriber.status_page.subscribe.heading'))
->assertSee('data-page="subscribe"', escape: false)
->assertSee('data-component="subscribe"', escape: false);
@@ -181,7 +180,6 @@
$subscriber = Subscriber::factory()->verified()->create();
get($subscriber->unsubscribeUrl())
- ->assertOk()
->assertSee(__('cachet::subscriber.status_page.unsubscribe.heading'))
->assertSee($subscriber->email)
->assertSee('data-page="unsubscribe"', escape: false)
@@ -192,16 +190,15 @@
it('renders unsubscribe hooks around the confirmation', function () {
$subscriber = Subscriber::factory()->verified()->create();
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_UNSUBSCRIBE_BEFORE, fn () => 'unsubscribe-before-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_UNSUBSCRIBE_AFTER, fn () => 'unsubscribe-after-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_UNSUBSCRIBE_BEFORE, fn () => 'unsubscribe-before-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_UNSUBSCRIBE_AFTER, fn () => 'unsubscribe-after-hook');
get($subscriber->unsubscribeUrl())
- ->assertOk()
- ->assertSeeInOrder([
+ ->assertSeeTextInOrder([
'unsubscribe-before-hook',
- 'data-slot="content"',
+ __('cachet::subscriber.status_page.unsubscribe.heading'),
'unsubscribe-after-hook',
- ], escape: false);
+ ]);
});
it('unsubscribes a subscriber once confirmed', function () {
diff --git a/tests/Feature/StatusPage/IncidentPageTest.php b/tests/Feature/StatusPage/IncidentPageTest.php
index c64089e9..7706ffb4 100644
--- a/tests/Feature/StatusPage/IncidentPageTest.php
+++ b/tests/Feature/StatusPage/IncidentPageTest.php
@@ -4,17 +4,30 @@
use Cachet\Models\Component;
use Cachet\Models\Incident;
-it('shows the affected components on the incident page', function () {
+it('renders incident details with stable theme selectors', function () {
$component = Component::factory()->create(['name' => 'API']);
- $incident = Incident::factory()->create();
+ $incident = Incident::factory()->create(['name' => 'API connectivity']);
$incident->components()->attach($component, [
'component_status' => ComponentStatusEnum::performance_issues->value,
]);
$this->get(route('cachet.status-page.incident', $incident))
- ->assertOk()
+ ->assertSeeText('API connectivity')
->assertSee(__('cachet::incident.affected_components_header'))
- ->assertSee('API');
+ ->assertSee('API')
+ ->assertSee([
+ 'data-page="incident"',
+ 'data-component="affected-components"',
+ 'data-component="incident"',
+ 'data-component="incident-update"',
+ 'data-component="incident-update-status"',
+ 'data-component="badge"',
+ 'data-component="timestamp"',
+ 'data-component="page-navigation"',
+ 'data-slot="main"',
+ 'data-slot="indicator"',
+ 'data-slot="message"',
+ ], escape: false);
});
it('does not show the affected components box when none are attached', function () {
@@ -31,39 +44,6 @@
]);
$this->get(route('cachet.status-page.incident', $incident))
- ->assertOk()
- ->assertSee('investigating', escape: false)
+ ->assertSeeText('investigating')
->assertDontSee('', escape: false);
});
-
-it('uses the incident name as the page heading', function () {
- $incident = Incident::factory()->create(['name' => 'API connectivity']);
-
- $page = $this->get(route('cachet.status-page.incident', $incident))
- ->assertOk()
- ->getContent();
-
- expect($page)->toMatch('/
]*>\s*API connectivity\s*<\/h1>/');
-});
-
-it('renders stable incident page attributes', function () {
- $component = Component::factory()->create();
- $incident = Incident::factory()->create();
- $incident->components()->attach($component, [
- 'component_status' => ComponentStatusEnum::performance_issues->value,
- ]);
-
- $this->get(route('cachet.status-page.incident', $incident))
- ->assertOk()
- ->assertSee('data-page="incident"', escape: false)
- ->assertSee('data-component="affected-components"', escape: false)
- ->assertSee('data-component="incident"', escape: false)
- ->assertSee('data-component="incident-update"', escape: false)
- ->assertSee('data-component="incident-update-status"', escape: false)
- ->assertSee('data-component="badge"', escape: false)
- ->assertSee('data-component="timestamp"', escape: false)
- ->assertSee('data-component="page-navigation"', escape: false)
- ->assertSee('data-slot="main"', escape: false)
- ->assertSee('data-slot="indicator"', escape: false)
- ->assertSee('data-slot="message"', escape: false);
-});
diff --git a/tests/Feature/StatusPage/SchedulePageTest.php b/tests/Feature/StatusPage/SchedulePageTest.php
index cb276a2c..1bddd2f3 100644
--- a/tests/Feature/StatusPage/SchedulePageTest.php
+++ b/tests/Feature/StatusPage/SchedulePageTest.php
@@ -8,7 +8,7 @@
use function Pest\Laravel\get;
-it('renders the schedule page', function () {
+it('renders schedule details with stable theme selectors', function () {
$schedule = Schedule::factory()->create([
'name' => 'Database maintenance',
'message' => 'We will be upgrading the database.',
@@ -22,9 +22,18 @@
$schedule->updates()->save($update);
get(route('cachet.status-page.schedule', ['schedule' => $schedule]))
- ->assertOk()
->assertSee('Database maintenance')
- ->assertSee('Maintenance window confirmed.');
+ ->assertSee('Maintenance window confirmed.')
+ ->assertSee([
+ 'data-page="schedule"',
+ 'data-component="schedule"',
+ 'data-component="schedule-update"',
+ 'data-component="badge"',
+ 'data-component="timestamp"',
+ 'data-component="page-navigation"',
+ 'data-slot="main"',
+ 'data-slot="message"',
+ ], escape: false);
});
it('links to schedules from the status page', function () {
@@ -38,32 +47,3 @@
it('returns not found for a missing schedule', function () {
get(route('cachet.status-page.schedule', ['schedule' => 999]))->assertNotFound();
});
-
-it('uses the maintenance name as the page heading', function () {
- $schedule = Schedule::factory()->create(['name' => 'Database maintenance']);
-
- $page = get(route('cachet.status-page.schedule', ['schedule' => $schedule]))
- ->assertOk()
- ->getContent();
-
- expect($page)->toMatch('/]*>\s*Database maintenance\s*<\/h1>/');
-});
-
-it('renders stable maintenance page attributes', function () {
- $schedule = Schedule::factory()->create();
- $schedule->updates()->save(new Update([
- 'message' => 'Maintenance is underway.',
- 'status' => IncidentStatusEnum::unknown,
- ]));
-
- get(route('cachet.status-page.schedule', ['schedule' => $schedule]))
- ->assertOk()
- ->assertSee('data-page="schedule"', escape: false)
- ->assertSee('data-component="schedule"', escape: false)
- ->assertSee('data-component="schedule-update"', escape: false)
- ->assertSee('data-component="badge"', escape: false)
- ->assertSee('data-component="timestamp"', escape: false)
- ->assertSee('data-component="page-navigation"', escape: false)
- ->assertSee('data-slot="main"', escape: false)
- ->assertSee('data-slot="message"', escape: false);
-});
diff --git a/tests/Feature/StatusPage/StatusPageTest.php b/tests/Feature/StatusPage/StatusPageTest.php
index f0b6503d..bc61c680 100644
--- a/tests/Feature/StatusPage/StatusPageTest.php
+++ b/tests/Feature/StatusPage/StatusPageTest.php
@@ -63,7 +63,7 @@
->assertNotFound();
});
-it('can hide the site name and about content without changing status page metadata', function () {
+it('hides the site name and about content when disabled', function () {
$settings = app(AppSettings::class);
$settings->name = 'Acme Status';
$settings->about = 'A private production system.';
@@ -77,10 +77,6 @@
expect($body)
->not->toContain('Acme Status')
->not->toContain('A private production system.');
-
- $response
- ->assertSee('Acme Status', escape: false)
- ->assertSee('', escape: false);
});
it('shows the site name once when it is enabled', function () {
@@ -96,54 +92,53 @@
expect(substr_count($body, 'Acme Status'))->toBe(1);
});
-it('uses a logical heading hierarchy for components', function () {
- $group = ComponentGroup::factory()->create(['name' => 'Core services']);
- Component::factory()->create(['name' => 'Public API']);
- Component::factory()->create(['name' => 'Core API', 'component_group_id' => $group->id]);
-
- $page = $this->get(route('cachet.status-page'))
- ->assertOk()
- ->getContent();
-
- expect($page)
- ->toMatch('/]*>\s*Core services\s*<\\/h2>/')
- ->toMatch('/]*>\s*Core API\s*<\\/h3>/')
- ->toMatch('/]*>\s*Public API\s*<\\/h2>/');
-});
-
it('renders the components after hook after each component', function () {
- Component::factory()->create(['name' => 'Public API']);
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_COMPONENTS_AFTER, fn () => 'components-after-hook');
+ Component::factory()->create(['name' => 'Alpha API', 'order' => 1]);
+ Component::factory()->create(['name' => 'Bravo API', 'order' => 2]);
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_COMPONENTS_AFTER, fn () => 'components-after-hook');
$this->get(route('cachet.status-page'))
- ->assertOk()
- ->assertSee('components-after-hook');
+ ->assertSeeTextInOrder([
+ 'Alpha API',
+ 'components-after-hook',
+ 'Bravo API',
+ 'components-after-hook',
+ ]);
});
-it('renders the declared banner metrics and footer hooks', function () {
+it('renders status page hooks around their content', function () {
+ Component::factory()->create(['status' => ComponentStatusEnum::operational]);
Metric::factory()->create([
+ 'name' => 'Response time',
'visible' => ResourceVisibilityEnum::guest,
'display_chart' => true,
'show_when_empty' => true,
]);
Cache::forget('cachet::metrics.guests');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_BANNER, fn () => 'banner-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_METRICS_BEFORE, fn () => 'metrics-before-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_METRICS_AFTER, fn () => 'metrics-after-hook');
- CachetView::registerRenderHook(RenderHook::FOOTER, fn () => 'footer-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_BANNER, fn () => 'banner-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_STATUS_SUMMARY_BEFORE, fn () => 'summary-before-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_STATUS_SUMMARY_AFTER, fn () => 'summary-after-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_METRICS_BEFORE, fn () => 'metrics-before-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_METRICS_AFTER, fn () => 'metrics-after-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_INCIDENT_TIMELINE_BEFORE, fn () => 'timeline-before-hook');
+ CachetView::registerRenderHook(RenderHook::STATUS_PAGE_INCIDENT_TIMELINE_AFTER, fn () => 'timeline-after-hook');
+ CachetView::registerRenderHook(RenderHook::FOOTER, fn () => 'footer-hook');
$this->get(route('cachet.status-page'))
- ->assertOk()
- ->assertSeeInOrder([
- 'data-component="header"',
+ ->assertSeeTextInOrder([
'banner-hook',
+ 'summary-before-hook',
+ __('cachet::system_status.operational'),
+ 'summary-after-hook',
'metrics-before-hook',
- 'data-component="metrics"',
+ 'Response time',
'metrics-after-hook',
- 'data-component="footer"',
+ 'timeline-before-hook',
+ __('cachet::incident.no_incidents_reported'),
+ 'timeline-after-hook',
'footer-hook',
- ], escape: false);
+ ]);
});
it('renders a footer hook without built-in footer content', function () {
@@ -152,33 +147,14 @@
$settings->show_timezone = false;
$settings->save();
- CachetView::registerRenderHook(RenderHook::FOOTER, fn () => 'footer-hook');
+ CachetView::registerRenderHook(RenderHook::FOOTER, fn () => 'footer-hook');
$this->get(route('cachet.status-page'))
- ->assertOk()
->assertSee('data-component="footer"', escape: false)
- ->assertSee('footer-hook');
+ ->assertSeeText('footer-hook');
});
-it('renders status summary and incident timeline hooks', function () {
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_STATUS_SUMMARY_BEFORE, fn () => 'summary-before-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_STATUS_SUMMARY_AFTER, fn () => 'summary-after-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_INCIDENT_TIMELINE_BEFORE, fn () => 'timeline-before-hook');
- CachetView::registerRenderHook(RenderHook::STATUS_PAGE_INCIDENT_TIMELINE_AFTER, fn () => 'timeline-after-hook');
-
- $this->get(route('cachet.status-page'))
- ->assertOk()
- ->assertSeeInOrder([
- 'summary-before-hook',
- 'data-component="status-summary"',
- 'summary-after-hook',
- 'timeline-before-hook',
- 'data-component="incident-timeline"',
- 'timeline-after-hook',
- ], escape: false);
-});
-
-it('renders stable theme attributes on the status page', function () {
+it('exposes stable theme selectors on the status page', function () {
$settings = app(AppSettings::class);
$settings->about = 'Service status and uptime.';
$settings->show_about = true;
@@ -196,56 +172,67 @@
Cache::forget('cachet::metrics.guests');
$this->get(route('cachet.status-page'))
- ->assertOk()
- ->assertSee('data-page="status"', escape: false)
- ->assertSee('data-component="header"', escape: false)
- ->assertSee('data-component="status-overview"', escape: false)
- ->assertSee('data-component="status-summary"', escape: false)
- ->assertSee('data-component="component-list"', escape: false)
- ->assertSee('data-component="component-group"', escape: false)
- ->assertSee('data-component="component"', escape: false)
- ->assertSee('data-component="about"', escape: false)
- ->assertSee('data-component="metrics"', escape: false)
- ->assertSee('data-component="metric"', escape: false)
- ->assertSee('data-component="schedules"', escape: false)
- ->assertSee('data-component="schedule"', escape: false)
- ->assertSee('data-component="incident-timeline"', escape: false)
- ->assertSee('data-component="incident-day"', escape: false)
- ->assertSee('data-component="incident"', escape: false)
- ->assertSee('data-component="incident-update"', escape: false)
- ->assertSee('data-component="incident-update-status"', escape: false)
- ->assertSee('data-component="badge"', escape: false)
- ->assertSee('data-component="timestamp"', escape: false)
- ->assertSee('data-component="logo"', escape: false)
- ->assertSee('data-component="footer"', escape: false)
- ->assertSee('data-component-group-id="'.$group->getKey().'"', escape: false)
- ->assertSee('data-component-id="'.$component->getKey().'"', escape: false)
- ->assertSee('data-metric-id="'.$metric->getKey().'"', escape: false)
- ->assertSee('data-schedule-id="'.$schedule->getKey().'"', escape: false)
- ->assertSee('data-incident-id="'.$incident->getKey().'"', escape: false)
- ->assertSee('data-update-id="reported"', escape: false)
- ->assertSee('data-slot="main"', escape: false)
- ->assertSee('data-slot="title"', escape: false)
- ->assertSee('data-slot="status"', escape: false)
- ->assertSee('data-slot="indicator"', escape: false)
- ->assertSee('data-slot="content"', escape: false);
+ ->assertSee([
+ 'data-page="status"',
+ 'data-component="header"',
+ 'data-component="status-overview"',
+ 'data-component="status-summary"',
+ 'data-component="component-list"',
+ 'data-component="component-group"',
+ 'data-component="component"',
+ 'data-component="about"',
+ 'data-component="metrics"',
+ 'data-component="metric"',
+ 'data-component="schedules"',
+ 'data-component="schedule"',
+ 'data-component="incident-timeline"',
+ 'data-component="incident-day"',
+ 'data-component="incident"',
+ 'data-component="incident-update"',
+ 'data-component="incident-update-status"',
+ 'data-component="badge"',
+ 'data-component="timestamp"',
+ 'data-component="logo"',
+ 'data-component="footer"',
+ 'data-component-group-id="'.$group->getKey().'"',
+ 'data-component-id="'.$component->getKey().'"',
+ 'data-metric-id="'.$metric->getKey().'"',
+ 'data-schedule-id="'.$schedule->getKey().'"',
+ 'data-incident-id="'.$incident->getKey().'"',
+ 'data-update-id="reported"',
+ 'data-slot="main"',
+ 'data-slot="title"',
+ 'data-slot="status"',
+ 'data-slot="indicator"',
+ 'data-slot="content"',
+ ], escape: false);
});
-it('gives status page controls accessible names', function () {
+it('uses accessible component headings and form labels', function () {
+ $group = ComponentGroup::factory()->create(['name' => 'Core services']);
Component::factory()->create([
'name' => 'Public API',
'description' => 'The public API.',
]);
+ Component::factory()->create([
+ 'name' => 'Core API',
+ 'component_group_id' => $group->id,
+ ]);
- $page = $this->get(route('cachet.status-page'))
- ->assertOk()
+ $response = $this->get(route('cachet.status-page'))
->assertSee('aria-label="'.__('cachet::component.description_label', ['component' => 'Public API']).'"', escape: false)
- ->assertSee('aria-label="'.__('cachet::incident.timeline.date_range_label').'"', escape: false)
- ->getContent();
-
- expect($page)
- ->toMatch('/