mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-22 11:33:24 +00:00
d7d7acce85
A message worth showing was only on the dashboard, which means somebody who works in Files and Clients all day never meets it. It now also sits behind an icon next to the notification bell, and that is on every page. **One shared prop, not two.** "The same message in both places" is the requirement, and two props would have drifted the first time anybody edited one — so the hook moved out of DashboardController into HandleInertiaRequests, and the dashboard reads the same shared value the header does. The band and the dropdown also share the component that renders the words, for the same reason: the reliable way to keep two renderings identical is not to have two. Renamed with it. ResolvingDashboardCallout was accurate for about an hour and became a lie the moment it appeared somewhere else; it is ResolvingAnnouncement now, and the prop is `announcement`. Free to rename because nothing has shipped yet — the only other reference was cloud-modules', by string, updated alongside. The icon follows UpdateAvailableIcon beside it: absent entirely when there is nothing to say rather than a dead control, and a plain dot instead of a count, because there is only ever one of these and a "1" would invite somebody to look for the second. Two tests worth naming. One asserts the message reaches a page that is not the dashboard, which is the whole point of the addition. The other asserts a client is shown nothing even from a listener that sets it unconditionally — a client's header carries the bell too, and staff messages must not reach it however careless the listener.
131 lines
5.2 KiB
PHP
131 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Platform\Announcements\Events\ResolvingAnnouncement;
|
|
use App\Modules\Platform\Navigation\Events\ResolvingNavigationLinks;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Inertia\Testing\AssertableInertia;
|
|
|
|
beforeEach(function () {
|
|
$this->admin = User::factory()->create();
|
|
});
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Two seams a package fills, and core does not
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Both are dispatched unconditionally, and with nothing listening the
|
|
| documented default holds — no links, no callout. That is what makes them
|
|
| safe to add to a community installation that will never have a listener.
|
|
*/
|
|
|
|
test('with nothing listening the dashboard is exactly what it was', function () {
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('announcement', null),
|
|
);
|
|
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('extra_nav_links', []),
|
|
);
|
|
});
|
|
|
|
test('a listener can put a message in front of staff', function () {
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
$event->show('Heads up', 'Something worth reading.', 'Do the thing', 'https://example.test/', 'warning');
|
|
});
|
|
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page
|
|
->where('announcement.title', 'Heads up')
|
|
->where('announcement.action_url', 'https://example.test/')
|
|
->where('announcement.tone', 'warning'),
|
|
);
|
|
});
|
|
|
|
test('a listener can add a sidebar link', function () {
|
|
Event::listen(ResolvingNavigationLinks::class, function (ResolvingNavigationLinks $event): void {
|
|
$event->add('Somewhere else', 'https://example.test/', external: true);
|
|
});
|
|
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page
|
|
->where('extra_nav_links.0.title', 'Somewhere else')
|
|
->where('extra_nav_links.0.external', true),
|
|
);
|
|
});
|
|
|
|
// The sidebar is the administration area. A client's portal shows their
|
|
// own files and nothing about the installation, so these must not reach
|
|
// them however careless a listener is.
|
|
test('a client gets no contributed links, even from a listener that adds unconditionally', function () {
|
|
Event::listen(ResolvingNavigationLinks::class, function (ResolvingNavigationLinks $event): void {
|
|
$event->add('Staff only really', 'https://example.test/');
|
|
});
|
|
|
|
$client = User::factory()->client()->create();
|
|
|
|
$this->actingAs($client)->get('/my-files')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('extra_nav_links', []),
|
|
);
|
|
});
|
|
|
|
// One band. A dashboard that can accumulate banners accumulates them, and
|
|
// the second is what teaches people to skip the first.
|
|
test('the first listener to set a callout keeps it', function () {
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
$event->show('First', 'Set first.');
|
|
});
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
$event->show('Second', 'Should not win.');
|
|
});
|
|
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('announcement.title', 'First'),
|
|
);
|
|
});
|
|
|
|
test('an unknown tone falls back rather than rendering unstyled', function () {
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
$event->show('T', 'B', tone: 'chartreuse');
|
|
});
|
|
|
|
$this->actingAs($this->admin)->get('/dashboard')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('announcement.tone', 'info'),
|
|
);
|
|
});
|
|
|
|
|
|
// The header icon and the dashboard band read one shared prop, so a
|
|
// message reaches somebody who never opens the dashboard. Two props would
|
|
// have drifted the first time anybody edited one.
|
|
test('the same message is available away from the dashboard', function () {
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
$event->show('Everywhere', 'Not only on the dashboard.');
|
|
});
|
|
|
|
$this->actingAs($this->admin)->get('/system/settings/general')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('announcement.title', 'Everywhere'),
|
|
);
|
|
});
|
|
|
|
// A client's header carries the bell too. Nothing addressed to staff may
|
|
// appear there, however careless the listener.
|
|
test('a client is never shown one, even from a listener that sets it unconditionally', function () {
|
|
Event::listen(ResolvingAnnouncement::class, function (ResolvingAnnouncement $event): void {
|
|
if (! $event->isStaff) {
|
|
return;
|
|
}
|
|
|
|
$event->show('Staff only', 'Not for clients.');
|
|
});
|
|
|
|
$client = User::factory()->client()->create();
|
|
|
|
$this->actingAs($client)->get('/my-files')->assertInertia(
|
|
fn (AssertableInertia $page) => $page->where('announcement', null),
|
|
);
|
|
});
|