create(['email' => 'gekuendigt@example.test', 'email_verified_at' => now()]); $customer = Customer::factory()->create([ 'email' => 'gekuendigt@example.test', 'user_id' => $user->id, 'status' => 'active', ]); $order = Order::factory()->create(['customer_id' => $customer->id, 'plan' => 'team']); $instance = Instance::factory()->create([ 'customer_id' => $customer->id, 'order_id' => $order->id, 'plan' => 'team', 'status' => $status, 'subdomain' => 'gekuendigt', 'cancel_requested_at' => now()->subDays(2), 'service_ends_at' => $endetIn, ]); return [$user, $instance->refresh()]; } /** * Ein Kunde mit einer laufenden Instanz, die niemand gekündigt hat — * dieselbe Form wie gekuendigteInstanz(), nur ohne Kündigung. * * @return array{0: User, 1: Instance} */ function laufendeInstanz(): array { $user = User::factory()->create(['email' => 'laeuft@example.test', 'email_verified_at' => now()]); $customer = Customer::factory()->create([ 'email' => 'laeuft@example.test', 'user_id' => $user->id, 'status' => 'active', ]); $order = Order::factory()->create(['customer_id' => $customer->id, 'plan' => 'team']); $instance = Instance::factory()->create([ 'customer_id' => $customer->id, 'order_id' => $order->id, 'plan' => 'team', 'status' => 'active', 'subdomain' => 'laeuft', ]); return [$user, $instance->refresh()]; } it('zeigt nach einer Kuendigung, wie lange der Zugang noch steht', function () { [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); Livewire::actingAs($user)->test(Dashboard::class) ->assertSee(__('dashboard.ending.title')) ->assertSee($instance->service_ends_at->local()->isoFormat('LL')); }); it('zeigt den Streifen nicht, solange nicht gekuendigt wurde', function () { [$user] = laufendeInstanz(); Livewire::actingAs($user)->test(Dashboard::class) ->assertDontSee(__('dashboard.ending.title')); }); it('zeigt ihn auch nach dem Laufzeitende nicht mehr', function () { // Danach ist der Zugang zu, und ein Streifen, der eine Restzeit nennt, // wäre schlicht falsch. Was dann gilt, sagt der Kasten aus der Prüfung // darunter — bis zur Fix-Welle behauptete dieser Kommentar das, und die // Seite sagte das Gegenteil. [$user] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended'); Livewire::actingAs($user)->test(Dashboard::class) ->assertDontSee(__('dashboard.ending.title')); }); it('sagt nach dem Laufzeitende, dass die Laufzeit beendet ist', function () { // K2, zweite Hälfte: Dashboard::render() holt eine `ended`-Instanz nicht // mehr, und der Kunde fiel damit in denselben Zweig wie jemand, der noch // nie etwas bestellt hat — „Ihre Cloud wird eingerichtet." samt „Paket // buchen", am Tag, an dem ihm EndInstanceService die Adresse eingezogen // hat. Das ist kein Kunde ohne Paket, sondern einer, dessen Paket vorbei // ist, und die Seite muss das sagen, samt dem Datum. [$user, $instance] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended'); Livewire::actingAs($user)->test(Dashboard::class) ->assertSee(__('dashboard.ended.label')) ->assertSee($instance->service_ends_at->local()->isoFormat('LL')) ->assertDontSee(__('dashboard.title_pending')) ->assertDontSee(__('dashboard.no_instance_label')); }); it('zeigt einem Kunden ohne jede Instanz weiterhin den leeren Zustand', function () { // Die Gegenprobe zur Prüfung darüber: der beendete Fall darf den ehrlichen // Leerzustand nicht verschlucken. Wer nie etwas bestellt hat, bekommt // weiterhin „Noch keine Instanz" — und keine Rede von einer Laufzeit, die // es nie gab. $user = User::factory()->create(['email' => 'ohne@example.test', 'email_verified_at' => now()]); Customer::factory()->create([ 'email' => 'ohne@example.test', 'user_id' => $user->id, 'status' => 'active', ]); Livewire::actingAs($user)->test(Dashboard::class) ->assertSee(__('dashboard.no_instance_label')) ->assertDontSee(__('dashboard.ended.label')); }); it('nennt keine Restzeit, die schon abgelaufen ist', function () { // Der Fall zwischen den Stühlen: das Laufzeitende ist vorbei, aber der // stündliche Lauf hat die Instanz noch nicht auf `ended` gestellt. Eine // Zeile "noch -3 Stunden" ist die Sorte Fehler, die niemand meldet und // jeder sieht. // // Korrektur gegenüber dem Zettel: `not->toContain('-')` über das ganze // Dokument trägt nicht — ein Datum und eine CSS-Klasse enthalten ebenso // Bindestriche, und die Prüfung wäre aus dem falschen Grund rot oder // grün. Sie zielt stattdessen auf die Restzeit selbst: der Streifen darf // gar nicht erst erscheinen, wenn das Laufzeitende vorbei ist — unabhängig // davon, ob der Status das schon nachvollzogen hat. Genau das ist die // Prüfung, die eine Implementierung ertappt, die sich allein auf den // Status verlässt und das Datum ignoriert. [$user] = gekuendigteInstanz(endetIn: now()->subHours(3)); Livewire::actingAs($user)->test(Dashboard::class) ->assertDontSee(__('dashboard.ending.title')); }); it('nennt die Restzeit in ganzen Tagen, solange mehr als ein Tag bleibt', function () { // Nicht im Zettel geprüft, aber ausdrücklich gefordert: "noch 12 Tage" // liest sich, "noch 11 Tage 7 Stunden" nicht — abgerundet auf ganze Tage. [$user] = gekuendigteInstanz(endetIn: now()->addDays(3)->addHours(5)); Livewire::actingAs($user)->test(Dashboard::class) ->assertSee(trans_choice('dashboard.ending.remaining_days', 3)); }); it('zaehlt am letzten Tag in Stunden, nicht mehr in Tagen', function () { [$user] = gekuendigteInstanz(endetIn: now()->addHours(5)); Livewire::actingAs($user)->test(Dashboard::class) ->assertSee(trans_choice('dashboard.ending.remaining_hours', 5)); }); /* * Die drei Zustände am Streifen, je einer pro Prüfung. * * Vorher stand hier EINE Prüfung, und sie konnte nicht fehlschlagen: * `x-ui.switch` rendert beide Wörter und überlässt dem CSS, welches sichtbar * ist — `assertSee('Kein Export gewünscht')` war deshalb bei `true`, bei * `false` UND bei `null` grün. Genau deshalb kam K1 durch: `(bool) null` ist * `false`, der Schalter behauptete einem nie gefragten Menschen seine eigene * Antwort, und nichts hielt die drei Zustände auseinander. * * Die Auswahlfelder tragen ihren Zustand dagegen im Markup (`checked`), also * prüft das hier wirklich das, was der Kunde sieht. */ it('zeigt am Streifen ein Ja als Ja', function () { [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); $instance->update(['export_wish' => true]); Livewire::actingAs($user)->test(Dashboard::class) ->assertSeeHtml('id="export-wish-yes" checked') ->assertDontSeeHtml('id="export-wish-no" checked'); }); it('zeigt am Streifen ein Nein als Nein', function () { [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); $instance->update(['export_wish' => false]); Livewire::actingAs($user)->test(Dashboard::class) ->assertSeeHtml('id="export-wish-no" checked') ->assertDontSeeHtml('id="export-wish-yes" checked'); }); it('behauptet keine Antwort, wenn niemand gefragt wurde', function () { // Der Schaden, gegen den der Kopfkommentar der Wanderung geschrieben // wurde: eine vor dieser Ausrollung gekündigte Instanz trägt `null`, und // der Streifen sagte darunter „Kein Export gewünscht" — eine Aussage über // einen Wunsch, den dieser Mensch nie geäußert hat, dargestellt als seine // eigene Antwort. Keine Auswahl ist markiert, und der Satz daneben fragt. [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); expect($instance->export_wish)->toBeNull(); Livewire::actingAs($user)->test(Dashboard::class) ->assertDontSeeHtml('id="export-wish-yes" checked') ->assertDontSeeHtml('id="export-wish-no" checked') ->assertSee(__('dashboard.ending.export_open')); }); it('laesst die Antwort am Streifen aendern', function () { [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); $instance->update(['export_wish' => false]); Livewire::actingAs($user)->test(Dashboard::class) ->call('setExportWish', true); expect($instance->fresh()->export_wish)->toBeTrue(); }); it('laesst die Antwort nach dem Laufzeitende nicht mehr aendern', function () { // Aufgabe 4: danach ist die Instanz `ended`, die Adresse eingezogen, und // ob ein Export vorbereitet wurde, ist entschieden. Ein Aufruf, der dort // noch etwas verspricht, wäre die nächste Attrappe — geprüft direkt gegen // die Methode, nicht nur gegen den (dann ohnehin nicht mehr sichtbaren) // Schalter im Streifen: setExportWish() ist über /livewire/update auch am // Formular vorbei erreichbar. [$user, $instance] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended'); $instance->update(['export_wish' => false]); Livewire::actingAs($user)->test(Dashboard::class) ->call('setExportWish', true); expect($instance->fresh()->export_wish)->toBeFalse(); });