options([]); Functions\when('wp_get_associated_nav_menu_items')->justReturn([]); } /** @param array $options */ private function options(array $options): void { Functions\when('get_option')->alias(static fn (string $name, mixed $default = false) => $options[$name] ?? $default); } private function ability(ChangeLog $log): PostTrashAbility { Functions\when('get_post')->justReturn(new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_title' => 'Alt', 'post_status' => 'publish'])); Functions\when('current_user_can')->justReturn(true); $schema = SchemaFixture::managed(); return new PostTrashAbility($schema, $log); } public function testRequiresConfirmation(): void { Functions\expect('wp_trash_post')->never(); $result = $this->ability(FakeChangeLog::forbidden())->execute(['post_id' => 5]); self::assertSame('lit_confirm_required', $result->get_error_code()); } public function testMovesToTrashNeverDeletes(): void { Functions\expect('wp_trash_post')->once()->with(5)->andReturn(new WP_Post(['ID' => 5])); Functions\expect('wp_delete_post')->never(); $log = new FakeChangeLog(2); $result = $this->ability($log)->execute(['post_id' => 5, 'confirm' => true]); self::assertSame('trashed', $result['changed'][0]['action']); self::assertStringContainsString('Papierkorb', $result['summary']); $log->assertRecorded([ 'ability' => 'lit/post-trash', 'object_type' => 'page', 'object_id' => '5', 'summary' => '"Alt" in den Papierkorb gelegt', 'revision_id' => null, ]); } public function testRefusesWhenTheTrashIsDisabledInsteadOfDeleting(): void { Functions\when('get_post')->justReturn(new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_status' => 'publish'])); Functions\when('current_user_can')->justReturn(true); Functions\expect('wp_trash_post')->never(); $schema = SchemaFixture::managed(); $ability = new PostTrashAbility($schema, FakeChangeLog::forbidden(), static fn (): bool => false); $result = $ability->execute(['post_id' => 5, 'confirm' => true]); self::assertSame('lit_unsupported', $result->get_error_code()); } public function testAHookThrowingDuringTheWriteIsAPartialWrite(): void { Functions\when('wp_trash_post')->alias(static function (): void { throw new RuntimeException('save_post-Hook kaputt'); }); $log = FakeChangeLog::partialsOnly(); try { $this->ability($log)->execute(['post_id' => 5, 'confirm' => true]); self::fail('WriteFailed expected'); } catch (WriteFailed $e) { self::assertFalse($e->rolledBack); self::assertSame(5, $e->postId); self::assertStringContainsString('save_post-Hook kaputt', $e->getMessage()); self::assertSame('lit_partial_write', $e->toError()->get_error_code()); } $log->assertPartials(['ability' => 'lit/post-trash', 'object_type' => 'page', 'object_id' => '5']); } public function testAnAlreadyTrashedPostIsANoOp(): void { $ability = $this->ability(FakeChangeLog::forbidden()); Functions\when('get_post')->justReturn(new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_title' => 'Alt', 'post_status' => 'trash'])); Functions\expect('wp_trash_post')->never(); $result = $ability->execute(['post_id' => 5, 'confirm' => true]); self::assertSame([], $result['changed']); self::assertStringContainsString('bereits im Papierkorb', $result['summary']); } public function testWarnsWhenTrashingTheFrontPageOrThePostsPage(): void { $this->options(['show_on_front' => 'page', 'page_on_front' => '5', 'page_for_posts' => '9']); Functions\when('wp_trash_post')->justReturn(new WP_Post(['ID' => 5])); $log = new FakeChangeLog(3); $ability = $this->ability($log); $confirm = $ability->execute(['post_id' => 5]); $result = $ability->execute(['post_id' => 5, 'confirm' => true]); self::assertStringContainsString('Startseite', $confirm->get_error_message()); self::assertCount(1, $result['warnings']); self::assertStringContainsString('Startseite', $result['warnings'][0]); self::assertTrue($result['ok']); $this->options(['show_on_front' => 'page', 'page_on_front' => '9', 'page_for_posts' => '5']); self::assertStringContainsString('Beitragsseite', $ability->execute(['post_id' => 5, 'confirm' => true])['warnings'][0]); $this->options(['show_on_front' => 'posts', 'page_on_front' => '5']); self::assertSame([], $ability->execute(['post_id' => 5, 'confirm' => true])['warnings']); } public function testWarnsAndNamesTheMenusThatLinkThePost(): void { Functions\when('wp_get_associated_nav_menu_items')->alias( static fn (int $id, string $type): array => $id === 5 && $type === 'post_type' ? [40, 41] : [] ); Functions\when('wp_get_object_terms')->alias( static fn (array $ids, string $taxonomy, array $args): array => $ids === [40, 41] && $taxonomy === 'nav_menu' && $args === ['fields' => 'names'] ? ['Hauptmenü', 'Footer', 'Hauptmenü'] : [] ); Functions\when('wp_trash_post')->justReturn(new WP_Post(['ID' => 5])); $log = new FakeChangeLog(3); $result = $this->ability($log)->execute(['post_id' => 5, 'confirm' => true]); self::assertCount(1, $result['warnings']); self::assertStringContainsString('"Hauptmenü", "Footer"', $result['warnings'][0]); self::assertSame(1, substr_count($result['warnings'][0], 'Hauptmenü')); } public function testIsDestructiveAndIndirect(): void { $ability = $this->ability(FakeChangeLog::forbidden()); self::assertTrue($ability->annotations()['destructive']); self::assertFalse($ability->isDirect()); } }