Add browser tests for the Material interface
Upload drop zone, copying a share link, unlocking a download on a phone, sorting and deleting on the admin dashboard, and the system theme with the Appearance picker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
c4a17b65c8
commit
bc69066ead
@@ -11,6 +11,9 @@
|
||||
<testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Browser">
|
||||
<directory>tests/Browser</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Share;
|
||||
use App\Models\User;
|
||||
use App\Services\ShareService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* A page of SealShare, once it can be used: loaded, with Alpine and Livewire started.
|
||||
*/
|
||||
function ready(mixed $page): mixed
|
||||
{
|
||||
return $page->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
// Sessions have to outlive a request here: a sign-in, a verified share password.
|
||||
config(['session.driver' => 'file']);
|
||||
Storage::fake('shares');
|
||||
});
|
||||
|
||||
test('files dragged over the drop zone turn its shape into a burst', function () {
|
||||
$page = ready(visit('/upload'));
|
||||
|
||||
$burst = "getComputedStyle(document.querySelectorAll('[data-test=drop-zone] span.absolute')[1]).opacity";
|
||||
|
||||
$page->assertScript("{$burst} === '0'");
|
||||
|
||||
$page->script("window.eval(\"document.querySelector('[data-test=drop-zone]').dispatchEvent(new DragEvent('dragover', { bubbles: true, cancelable: true }))\")");
|
||||
|
||||
$page->assertScript("{$burst} === '1'")
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
// Pest's in-process server does not store a multipart upload, so the upload itself is covered by
|
||||
// FileUploadTest; this picks up where it ends, on the page the upload leads to.
|
||||
test('a new share\'s link can be copied from the page the upload leads to', function () {
|
||||
$share = app(ShareService::class)->createShare(
|
||||
[['file' => UploadedFile::fake()->create('contract.pdf', 80), 'relativePath' => null]],
|
||||
[],
|
||||
);
|
||||
|
||||
$page = ready(visit(route('share.created', $share, false)));
|
||||
|
||||
$page->assertSee('Share Created!')
|
||||
->assertScript("document.querySelector('[data-test=\"share-link\"]').value.includes('/s/')");
|
||||
|
||||
$page->script("window.eval(\"Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: async (text) => { window.copied = text } } })\")");
|
||||
|
||||
$page->click('[data-field-copy]')
|
||||
->assertScript("typeof window.copied === 'string' && window.copied.includes('/s/')")
|
||||
->assertSee('Copied to the clipboard');
|
||||
});
|
||||
|
||||
test('a recipient on a phone unlocks a password-protected share and sees its files', function () {
|
||||
$share = app(ShareService::class)->createShare(
|
||||
[['file' => UploadedFile::fake()->create('holiday-photos.zip', 120), 'relativePath' => null]],
|
||||
['password' => 'correct horse'],
|
||||
);
|
||||
|
||||
$page = ready(visit(route('share.download', $share, false))->resize(393, 852));
|
||||
|
||||
$page->assertSee('Password Required')
|
||||
->assertScript('document.documentElement.scrollWidth <= window.innerWidth')
|
||||
->assertNoJavaScriptErrors()
|
||||
->type('input[type="password"]', 'correct horse')
|
||||
->press('Unlock')
|
||||
->assertSee('Shared Files')
|
||||
->assertSee('holiday-photos.zip')
|
||||
->assertScript("document.querySelectorAll('[popover]').length === 0")
|
||||
->assertScript('document.documentElement.scrollWidth <= window.innerWidth');
|
||||
});
|
||||
|
||||
test('an admin sorts the shares table and deletes a share through its dialog', function () {
|
||||
$admin = User::factory()->admin()->create();
|
||||
Share::factory()->create(['token' => 'aaaaaaaaaaaaaaaa', 'download_count' => 9]);
|
||||
$doomed = Share::factory()->create(['token' => 'zzzzzzzzzzzzzzzz', 'download_count' => 1]);
|
||||
|
||||
$this->actingAs($admin);
|
||||
|
||||
$page = ready(visit('/admin/dashboard'));
|
||||
|
||||
$page->click('th button:has-text("Downloads")')
|
||||
->assertScript("document.querySelector('tbody tr td').textContent.trim() === 'zzzzzzzzzzzzzzzz'");
|
||||
|
||||
$page->click("[data-test=\"delete-share-{$doomed->id}\"]")
|
||||
->assertScript("[...document.querySelectorAll('dialog')].some((dialog) => dialog.open)")
|
||||
->click('[data-test="confirm-delete-share"]')
|
||||
->assertScript("! [...document.querySelectorAll('dialog')].some((dialog) => dialog.open)")
|
||||
->assertDontSee('zzzzzzzzzzzzzzzz');
|
||||
|
||||
expect(Share::query()->find($doomed->id))->toBeNull();
|
||||
});
|
||||
|
||||
test('a first visit follows the system theme, and Appearance switches it', function () {
|
||||
ready(visit('/upload')->inDarkMode())
|
||||
->assertScript("document.documentElement.dataset.theme === 'dark'")
|
||||
->assertScript("document.documentElement.dataset.themeChoice === 'system'");
|
||||
|
||||
$this->actingAs(User::factory()->create());
|
||||
|
||||
$page = ready(visit('/settings/appearance')->inDarkMode());
|
||||
|
||||
$page->click('[data-theme-option="light"]')
|
||||
->assertScript("document.documentElement.dataset.theme === 'light'")
|
||||
->assertScript("localStorage.getItem('sealshare-theme') === 'light'");
|
||||
});
|
||||
+1
-1
@@ -21,7 +21,7 @@ pest()->extend(TestCase::class)
|
||||
// EnsureSetupComplete middleware redirects to /setup unless an admin exists.
|
||||
User::factory()->admin()->create(['email' => 'admin-setup@test.com']);
|
||||
})
|
||||
->in('Feature');
|
||||
->in('Feature', 'Browser');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user