Improve live recordings UI, upload flow, and default Flux theme.
Refresh list status over Reverb with polling fallback, clarify queued vs processing, streamline upload empty states, and seed an admin login.
This commit is contained in:
@@ -24,12 +24,24 @@ class DemoUserSeederTest extends TestCase
|
||||
$this->assertTrue(Hash::check('password', $user->password));
|
||||
}
|
||||
|
||||
public function test_seeder_creates_admin_user(): void
|
||||
{
|
||||
$this->seed(DatabaseSeeder::class);
|
||||
|
||||
$user = User::query()->where('email', 'admin@example.com')->first();
|
||||
|
||||
$this->assertNotNull($user);
|
||||
$this->assertSame('Admin', $user->name);
|
||||
$this->assertTrue(Hash::check('password', $user->password));
|
||||
}
|
||||
|
||||
public function test_seeder_is_idempotent(): void
|
||||
{
|
||||
$this->seed(DatabaseSeeder::class);
|
||||
$this->seed(DatabaseSeeder::class);
|
||||
|
||||
$this->assertSame(1, User::query()->where('email', 'demo@example.com')->count());
|
||||
$this->assertSame(1, User::query()->where('email', 'admin@example.com')->count());
|
||||
}
|
||||
|
||||
public function test_seeder_assigns_orphaned_recordings_to_demo_user(): void
|
||||
|
||||
@@ -70,22 +70,14 @@ class RecordingDuplicateUploadTest extends TestCase
|
||||
Bus::assertDispatched(TranscribeRecording::class, 2);
|
||||
}
|
||||
|
||||
public function test_upload_page_mentions_duplicate_skipping(): void
|
||||
public function test_upload_page_shows_dropzone(): void
|
||||
{
|
||||
Recording::query()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'title' => 'Existing',
|
||||
'original_filename' => 'note.mp3',
|
||||
'file_path' => 'recordings/note.mp3',
|
||||
'file_size_bytes' => 2048,
|
||||
'content_hash' => str_repeat('a', 64),
|
||||
'transcription_status' => 'done',
|
||||
]);
|
||||
|
||||
$this->get(route('recordings.create'))
|
||||
->assertOk()
|
||||
->assertSeeLivewire('upload-recordings')
|
||||
->assertSee('Duplicate files', false);
|
||||
->assertSee('Drop audio files here or click to browse')
|
||||
->assertDontSee('Title (optional)')
|
||||
->assertDontSee('Uploads start as soon as you drop');
|
||||
}
|
||||
|
||||
public function test_duplicate_filename_and_size_is_skipped_without_content_hash(): void
|
||||
|
||||
@@ -59,16 +59,16 @@ class RecordingUploadTest extends TestCase
|
||||
$file = UploadedFile::fake()->create('meeting.mp3', 500, 'audio/mpeg');
|
||||
|
||||
Livewire::test(UploadRecordings::class)
|
||||
->set('title', 'Team meeting')
|
||||
->set('audio', [$file])
|
||||
->assertRedirect(route('recordings.show', Recording::query()->first()));
|
||||
|
||||
$recording = Recording::query()->first();
|
||||
|
||||
$this->assertNotNull($recording);
|
||||
$this->assertSame('Team meeting', $recording->title);
|
||||
$this->assertSame('meeting', $recording->title);
|
||||
$this->assertSame('pending', $recording->transcription_status);
|
||||
$this->assertSame('local', $recording->transcription_driver);
|
||||
$this->assertNull($recording->transcription_percent);
|
||||
$this->assertNotNull($recording->transcription_started_at);
|
||||
Storage::disk('local')->assertExists($recording->file_path);
|
||||
Bus::assertDispatched(TranscribeRecording::class);
|
||||
@@ -132,11 +132,11 @@ class RecordingUploadTest extends TestCase
|
||||
['talk.m4a', 'audio/mp4', 'm4a-bytes'],
|
||||
] as [$name, $mime, $contents]) {
|
||||
Livewire::test(UploadRecordings::class)
|
||||
->set('title', $name)
|
||||
->set('audio', [UploadedFile::fake()->createWithContent($name, $contents)])
|
||||
->assertRedirect();
|
||||
|
||||
$recording = Recording::query()->where('title', $name)->first();
|
||||
$expectedTitle = pathinfo($name, PATHINFO_FILENAME);
|
||||
$recording = Recording::query()->where('title', $expectedTitle)->first();
|
||||
|
||||
$this->assertNotNull($recording, "Failed uploading {$name}");
|
||||
Storage::disk('local')->assertExists($recording->file_path);
|
||||
|
||||
@@ -38,6 +38,19 @@ class IndexTest extends TestCase
|
||||
->assertSee('Pocket note');
|
||||
}
|
||||
|
||||
public function test_empty_index_shows_upload_dropzone(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('recordings.index'))
|
||||
->assertOk()
|
||||
->assertSeeLivewire('upload-recordings')
|
||||
->assertSee('Drop audio files here or click to browse')
|
||||
->assertDontSee('No recordings yet.')
|
||||
->assertDontSee('Upload your first MP3');
|
||||
}
|
||||
|
||||
public function test_search_filters_recordings(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
@@ -90,6 +103,77 @@ class IndexTest extends TestCase
|
||||
Bus::assertDispatched(TranscribeRecording::class, 1);
|
||||
}
|
||||
|
||||
public function test_index_polls_while_transcriptions_are_active(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'In progress',
|
||||
'original_filename' => 'active.mp3',
|
||||
'file_path' => 'recordings/active.mp3',
|
||||
'file_size_bytes' => 100,
|
||||
'transcription_status' => 'processing',
|
||||
'transcription_progress' => 'Transcribing locally…',
|
||||
'transcription_percent' => 40,
|
||||
'transcription_driver' => 'local',
|
||||
'transcription_started_at' => now(),
|
||||
]);
|
||||
|
||||
Livewire::test(Index::class)
|
||||
->assertSee('wire:poll', false)
|
||||
->assertSee('Transcribing locally…')
|
||||
->assertSee('40%')
|
||||
->assertSeeHtml('bg-amber-400');
|
||||
}
|
||||
|
||||
public function test_queued_recordings_do_not_show_fake_percent(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'Waiting in line',
|
||||
'original_filename' => 'queued.mp3',
|
||||
'file_path' => 'recordings/queued.mp3',
|
||||
'file_size_bytes' => 100,
|
||||
'transcription_status' => 'pending',
|
||||
'transcription_progress' => 'Queued — waiting to start…',
|
||||
'transcription_percent' => null,
|
||||
'transcription_driver' => 'local',
|
||||
'transcription_started_at' => now(),
|
||||
]);
|
||||
|
||||
Livewire::test(Index::class)
|
||||
->assertSee('Waiting in line')
|
||||
->assertSee('Queued')
|
||||
->assertSee('Queued — waiting to start…')
|
||||
->assertDontSee('5%')
|
||||
->assertSeeHtml('bg-zinc-400/15')
|
||||
->assertDontSeeHtml('bg-amber-400');
|
||||
}
|
||||
|
||||
public function test_index_does_not_poll_when_all_transcriptions_are_idle(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'Finished',
|
||||
'original_filename' => 'done.mp3',
|
||||
'file_path' => 'recordings/done.mp3',
|
||||
'file_size_bytes' => 100,
|
||||
'transcription_status' => 'done',
|
||||
'transcript' => 'all done',
|
||||
]);
|
||||
|
||||
Livewire::test(Index::class)
|
||||
->assertDontSee('wire:poll', false);
|
||||
}
|
||||
|
||||
public function test_user_can_delete_recording_from_index(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
@@ -116,6 +200,57 @@ class IndexTest extends TestCase
|
||||
Storage::disk('local')->assertMissing('recordings/delete-me.mp3');
|
||||
}
|
||||
|
||||
public function test_user_can_delete_all_recordings_from_index(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
Storage::disk('local')->put('recordings/one.mp3', 'one');
|
||||
Storage::disk('local')->put('recordings/two.mp3', 'two');
|
||||
Storage::disk('local')->put('recordings/other.mp3', 'other');
|
||||
|
||||
$user = User::factory()->create();
|
||||
$other = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'Mine one',
|
||||
'original_filename' => 'one.mp3',
|
||||
'file_path' => 'recordings/one.mp3',
|
||||
'file_size_bytes' => 3,
|
||||
'transcription_status' => 'done',
|
||||
]);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $user->id,
|
||||
'title' => 'Mine two',
|
||||
'original_filename' => 'two.mp3',
|
||||
'file_path' => 'recordings/two.mp3',
|
||||
'file_size_bytes' => 3,
|
||||
'transcription_status' => 'pending',
|
||||
]);
|
||||
|
||||
Recording::query()->create([
|
||||
'user_id' => $other->id,
|
||||
'title' => 'Someone else',
|
||||
'original_filename' => 'other.mp3',
|
||||
'file_path' => 'recordings/other.mp3',
|
||||
'file_size_bytes' => 5,
|
||||
'transcription_status' => 'done',
|
||||
]);
|
||||
|
||||
Livewire::test(Index::class)
|
||||
->assertSee('Delete all')
|
||||
->call('deleteAll')
|
||||
->assertDontSee('Mine one')
|
||||
->assertDontSee('Mine two');
|
||||
|
||||
$this->assertDatabaseMissing('recordings', ['user_id' => $user->id]);
|
||||
$this->assertDatabaseHas('recordings', ['user_id' => $other->id, 'title' => 'Someone else']);
|
||||
Storage::disk('local')->assertMissing('recordings/one.mp3');
|
||||
Storage::disk('local')->assertMissing('recordings/two.mp3');
|
||||
Storage::disk('local')->assertExists('recordings/other.mp3');
|
||||
}
|
||||
|
||||
public function test_user_cannot_delete_another_users_recording_from_index(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
|
||||
@@ -158,5 +158,6 @@ class TranscriptionBroadcastTest extends TestCase
|
||||
|
||||
$this->assertSame('RecordingTranscriptionUpdated', $event->broadcastAs());
|
||||
$this->assertSame('private-recording.'.$recording->id, $event->broadcastOn()[0]->name);
|
||||
$this->assertSame('private-user.'.$this->user->id.'.recordings', $event->broadcastOn()[1]->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +35,13 @@ class UploadRecordingsLivewireTest extends TestCase
|
||||
$file = UploadedFile::fake()->create('meeting.mp3', 500, 'audio/mpeg');
|
||||
|
||||
Livewire::test(UploadRecordings::class)
|
||||
->set('title', 'Team meeting')
|
||||
->set('audio', [$file])
|
||||
->assertRedirect(route('recordings.show', Recording::query()->first()));
|
||||
|
||||
$recording = Recording::query()->first();
|
||||
|
||||
$this->assertNotNull($recording);
|
||||
$this->assertSame('Team meeting', $recording->title);
|
||||
$this->assertSame('meeting', $recording->title);
|
||||
$this->assertSame('pending', $recording->transcription_status);
|
||||
Storage::disk('local')->assertExists($recording->file_path);
|
||||
Bus::assertDispatched(TranscribeRecording::class);
|
||||
|
||||
Reference in New Issue
Block a user