diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..0c382f5 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,179 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Global hooks +|-------------------------------------------------------------------------- +|*/ + +uses() +->beforeAll( + function() { + // Make sure all required plugins are active + $required_plugins = array( + // 'akismet/akismet.php', + 'duplicate-post/duplicate-post.php', + 'gravityforms/gravityforms.php', + ); + + foreach ($required_plugins as $plugin) { + if (!is_plugin_active($plugin)) { + throw new Exception("Plugin `{$plugin}` is not installed/active"); + } + } + + // Chcek table exists + global $wpdb; + $table = NASHAAT_DB_TABLE; + $result = $wpdb->query("Select 1 from {$table} LIMIT 1"); + + if ($result === false) { + throw new Exception("Table `{$table}` doesn't exists"); + } + + wp_clear_auth_cookie(); + wp_set_current_user(1); + wp_set_auth_cookie(1); + }) +->afterAll(function() { + // Clear table + global $wpdb; + $table = NASHAAT_DB_TABLE; + $wpdb->query("TRUNCATE TABLE {$table}"); + + wp_clear_auth_cookie(); + wp_set_current_user(0); + wp_set_auth_cookie(0); + + })->in(__DIR__); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + + +/** + * Callback for toHaveProperties. + * + * This has been abstracted out so that it can be called recursively. + * + * @param iterable $incoming The incoming array + * @param iterable $expected The expected array + * @param string $message The message to display if the assertion fails + */ +function assert_object(mixed $incoming, iterable $expected, string $message): void { + + $incoming_array = is_object($incoming) && method_exists($incoming, 'toArray') ? $incoming->toArray() : (array) $incoming; + // $expected_array = is_object($expected) && method_exists($expected, 'toArray') ? $expected->toArray() : (array) $expected; + + foreach ($expected as $name => $value) { + // Check if the key from $expected exists in $incoming + $key = is_int($name) && (is_string($value) || is_int($value)) ? $value : $name; + $non_existent = $message; + if ($non_existent === '') { + $non_existent = "Failed asserting that `{$key}` exists"; + } + + if (array_is_list($incoming_array)) { + Assert::assertTrue(in_array($key, $incoming_array), $non_existent); + } else { + Assert::assertTrue(array_key_exists($key, $incoming_array), $non_existent); + } + + + $incoming_value = $incoming_array[$key]; + // if $value is an iterable, recurse + if (is_iterable($value)) { + assert_object($incoming_value, $value, $message); + + continue; + } + + // $name exists and it is not an int (not a numeric key) + // so we can check against $value + if (!is_int($name)) { + Assert::assertEquals($value, $incoming_value, $message); + } + } +} + +/** + * Extend expect to check one object against another. + * This is similar to `toHaveProperties` but it is recursive. + */ +expect()->extend('toIncludeProperties', function(iterable $expected, $message = '') { + + assert_object($this->value, $expected, $message); + + return $this; + }); + +// expect()->intercept('toHaveProperties', 'iterable', function(iterable $expected, bool $ignore_case = false) { +// // expect($this->value->id)->toBe($expected->id); +// foreach ($expected as $name => $value) { +// if (is_array($value)) { +// return $this->toHaveProperties($value); +// continue; +// } + +// return is_int($name) ? $this->toHaveProperty($value) : $this->toHaveProperty($name, $value); +// } + + +// echo $ignore_case; +// }); + +// expect()->pipe('toHaveProperties', function(Closure $next, iterable $expected) { +// foreach ($expected as $name => $value) { +// if (is_array($value)) { +// return $this->toHaveProperties($value); +// continue; +// } + +// return is_int($name) ? $this->toHaveProperty($value) : $this->toHaveProperty($name, $value); +// } + +// // if ($this->value instanceof Model) { +// // return expect($this->value->id)->toBe($expected->id); +// // } + +// // return $next(); // Run to the original, built-in expectation... +// }); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +/** + * + */ +function something() { + // .. +} \ No newline at end of file diff --git a/tests/Unit/CommentsTest.php b/tests/Unit/CommentsTest.php new file mode 100644 index 0000000..41b67cb --- /dev/null +++ b/tests/Unit/CommentsTest.php @@ -0,0 +1,71 @@ + '1', + 'comment_post_ID' => '1', + 'comment_author' => 'admin', + 'comment_date' => '2019-01-01 00:00:00', + 'user_id' => '1', + ); + + do_action('edit_comment', 1, $comment); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'edited', + 'context' => 'comment', + 'log_info' => array( + 'post_id' => '1', + 'post_type' => 'post', + 'id' => '1', + ), + )); + + assert_snapshot($data, 'comment-edited'); + + + $comment_object = new WP_Comment((object) $comment); + + do_action('transition_comment_status', 'approved', '', $comment_object); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'approved', + 'context' => 'comment', + 'log_info' => array( + 'post_id' => '1', + 'post_type' => 'post', + 'id' => '1', + ), + )); + assert_snapshot($data, 'comment-status'); + + do_action('transition_comment_status', 'unapproved', '', $comment_object); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'unapproved', + 'context' => 'comment', + 'log_info' => array( + 'post_id' => '1', + 'post_type' => 'post', + 'id' => '1', + ), + )); + assert_snapshot($data, 'comment-status'); + + // Since no action is taken, get_data should return + // the last action which is unapproved + do_action('transition_comment_status', 'same', 'same', $comment_object); + expect($data)->toIncludeProperties(array( + 'event' => 'unapproved', + 'context' => 'comment', + 'log_info' => array( + 'post_id' => '1', + 'post_type' => 'post', + 'id' => '1', + ), + )); + + assert_snapshot($data, 'comment-status'); + + + }); \ No newline at end of file diff --git a/tests/Unit/DuplicatePostTest.php b/tests/Unit/DuplicatePostTest.php new file mode 100644 index 0000000..aea925b --- /dev/null +++ b/tests/Unit/DuplicatePostTest.php @@ -0,0 +1,76 @@ +toIncludeProperties(array( + 'event' => 'cloned', + 'context' => 'duplicate_post', + 'log_info' => array( + 'new_post_id' => 2, + 'prev_post_id' => 1, + 'status' => 'draft', + 'prev_post_title' => 'test', + 'new_post_title' => 'Sample Page', + 'post_type' => 'Post', + ), + )); + }); + + +test('Duplicate plugin settings', function() { + // Required to set the settings for the pluging + do_action('admin_init'); + + // Action to update the settings + do_action('updated_option', 'duplicate_post_title_prefix', 'old_prefix', 'new_prefix'); + + // Action to log the changes to database + apply_filters('wp_redirect', ''); + + expect(get_data())->toIncludeProperties(array( + 'event' => 'updated', + 'context' => 'duplicate_post_settings', + 'log_info' => array( + 'duplicate_post_title_prefix' => array( + "prev" => "old_prefix", + "new" => "new_prefix", + ), + ), + )); + + + do_action('updated_option', 'duplicate_post_copyauthor', false, true); + do_action('updated_option', 'duplicate_post_copyexcerpt', true, false); + do_action('updated_option', 'duplicate_post_show_original_meta_box', false, true); + apply_filters('wp_redirect', ''); + + + expect(get_data())->toIncludeProperties(array( + 'event' => 'updated', + 'context' => 'duplicate_post_settings', + 'log_info' => array( + 'elements-to-copy' => array( + 'duplicate_post_copyauthor' => array( + "prev" => false, + "new" => true, + ), + 'duplicate_post_copyexcerpt' => array( + "prev" => true, + "new" => false, + ), + ), + 'show-original' => array( + 'duplicate_post_show_original_meta_box' => array( + "prev" => false, + "new" => true, + ), + ), + ), + )); + expect(true)->toBe(true); + }); \ No newline at end of file diff --git a/tests/Unit/GravityFormsTest.php b/tests/Unit/GravityFormsTest.php new file mode 100644 index 0000000..1eb87e8 --- /dev/null +++ b/tests/Unit/GravityFormsTest.php @@ -0,0 +1,31 @@ + 1, + 'title' => 'test', + ); + do_action('gform_after_save_form', $form, false); + do_action('gform_after_save_form', $form, true); + + + do_action('gform_post_form_duplicated', 1, 2); + + + // Log import export + do_action('check_admin_referer', 'test'); + do_action('check_admin_referer', 'gf_export_forms'); + + $_POST['export_forms']['gf_form_id'] = array(1, 2, 3); + do_action('check_admin_referer', 'gf_export_forms'); + + expect(true)->toBe(true); + }); \ No newline at end of file diff --git a/tests/Unit/MediaTest.php b/tests/Unit/MediaTest.php new file mode 100644 index 0000000..f78270c --- /dev/null +++ b/tests/Unit/MediaTest.php @@ -0,0 +1,64 @@ + "This is an attachment", + ), 'test.jpg', 1); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'added', + 'context' => 'media', + 'log_info' => array( + 'id' => $attachment_id, + 'title' => 'This is an attachment', + 'path', + ), + )); + + + assert_snapshot($data, 'media-added', array( + 'path' => pathinfo($data['log_info']['path'], PATHINFO_DIRNAME), + 'file' => pathinfo($data['log_info']['path'], PATHINFO_FILENAME), + )); + + + // Test data update + wp_update_post(array( + 'ID' => $attachment_id, + 'post_title' => 'This is the post title', + 'post_content' => 'This is the updated content', + 'post_excerpt' => 'This is the updated excerpt', + )); + + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'edited', + 'context' => 'media', + 'log_info' => array( + 'id' => $attachment_id, + 'changes' => array( + 'title' => array('This is an attachment', 'This is the post title'), + 'description' => array('0', '27'), + 'caption' => array('0', '27'), + ), + 'path', + ), + )); + + + // Test deleting + wp_delete_attachment($attachment_id, true); + expect(get_data())->toIncludeProperties(array( + 'event' => 'deleted', + 'context' => 'media', + 'log_info' => array( + 'id' => $attachment_id, + 'title' => 'This is the post title', + 'path', + ), + )); + + // @todo Test media manipulations + }); \ No newline at end of file diff --git a/tests/Unit/MenuTest.php b/tests/Unit/MenuTest.php new file mode 100644 index 0000000..5c417e2 --- /dev/null +++ b/tests/Unit/MenuTest.php @@ -0,0 +1,68 @@ +term_id); + } + $menu_id = wp_create_nav_menu('menu_test'); + if (is_wp_error($menu_id)) { + throw new Exception($menu_id->get_error_message()); + } + + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'created', + 'context' => 'menu', + 'log_info' => array( + 'name' => 'menu_test', + 'slug' => 'menu_test', + 'id' => $menu_id, + ), + )); + assert_snapshot($data, 'menu-created'); + + // menu update is not working + $menu_id = wp_update_nav_menu_object($menu_id, array( + 'menu-name' => 'testing2', + 'slug' => 'testing2', + )); + + if (is_wp_error($menu_id)) { + throw new Exception($menu_id->get_error_message()); + } + + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'updated', + 'context' => 'menu', + 'log_info' => array( + 'name' => 'testing2', + 'slug' => 'testing2', + 'id' => $menu_id, + ), + )); + + assert_snapshot($data, 'menu-updated'); + + // Test delete + $menu_id = wp_delete_nav_menu($menu_id); + if (is_wp_error($menu_id)) { + throw new Exception($menu_id->get_error_message()); + } + + + // @todo fix delete not being in callback in menu.class.php + // expect(get_data())->toIncludeProperties(array( + // 'event' => 'deleted', + // 'context' => 'menu', + // 'log_info' => array( + // 'name' => 'menu_test', + // 'slug' => 'menu_test', + // 'id' => $menu_id, + // ), + // )); + + }); \ No newline at end of file diff --git a/tests/Unit/OptionsTest.php b/tests/Unit/OptionsTest.php new file mode 100644 index 0000000..25a02db --- /dev/null +++ b/tests/Unit/OptionsTest.php @@ -0,0 +1,31 @@ +toIncludeProperties(array( + 'event' => 'updated', + 'context' => 'options', + 'log_info' => array( + 'option_name' => 'blogname', + 'new_value' => 'new_name', + 'old_value' => 'old_name', + ), + )); + assert_snapshot($data, 'option-updated'); + + do_action("updated_option", "blogdescription", "old_desc", "new_desc"); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'updated', + 'context' => 'options', + 'log_info' => array( + 'option_name' => 'blogdescription', + 'new_value' => 'new_desc', + 'old_value' => 'old_desc', + ), + )); + assert_snapshot($data, 'option-updated'); + }); \ No newline at end of file diff --git a/tests/Unit/PluginsTest.php b/tests/Unit/PluginsTest.php new file mode 100644 index 0000000..610350b --- /dev/null +++ b/tests/Unit/PluginsTest.php @@ -0,0 +1,29 @@ +toIncludeProperties(array( + 'event' => 'activated', + 'context' => 'plugin', + 'log_info' => array( + 'name' => 'Akismet Anti-Spam', + 'version' => '5.0.1', + ), + )); + assert_snapshot($data, 'plugin-status'); + + do_action("deactivated_plugin", 'akismet/akismet.php'); + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'deactivated', + 'context' => 'plugin', + 'log_info' => array( + 'name' => 'Akismet Anti-Spam', + 'version' => '5.0.1', + ), + )); + assert_snapshot($data, 'plugin-status'); + + // @todo delete and install plugin + }); \ No newline at end of file diff --git a/tests/Unit/TaxonomyTest.php b/tests/Unit/TaxonomyTest.php new file mode 100644 index 0000000..08bbec4 --- /dev/null +++ b/tests/Unit/TaxonomyTest.php @@ -0,0 +1,81 @@ +term_id, 'category'); + } + + $term = wp_create_term('new categeory', 'category'); + if (is_wp_error($term)) { + throw new Exception($term->get_error_message()); + } + + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'created', + 'context' => 'taxonomy', + 'log_info' => array( + 'name' => 'new categeory', + 'term_id' => $term['term_id'], + 'slug' => 'new-categeory', + 'type' => 'category', + ), + )); + + assert_snapshot($data, 'taxonomy-created', array('id' => $term['term_id'])); + + + // Test edit + // delete category if exists + $delete_term = get_term_by('slug', 'test-category', 'category'); + if ($delete_term) { + wp_delete_term($delete_term->term_id, 'category'); + } + $term = wp_update_term($term['term_id'], 'category', array( + 'name' => 'test category', + 'slug' => 'test-category', + )); + + if (is_wp_error($term)) { + throw new Exception($term->get_error_message()); + } + + $data = get_data(); + expect($data)->toIncludeProperties(array( + 'event' => 'edited', + 'context' => 'taxonomy', + 'log_info' => array( + 'name' => 'test category', + 'term_id' => $term['term_id'], + 'type' => 'category', + 'changes' => array( + 'name' => array('new categeory', 'test category'), + 'slug' => array('new-categeory', 'test-category'), + ), + ), + )); + assert_snapshot($data, 'taxonomy-edited', array('id' => $term['term_id'])); + + // Test delete + $delete_term = wp_delete_term($term['term_id'], 'category'); + if (is_wp_error($delete_term)) { + throw new Exception($delete_term->get_error_message()); + } + + $data = get_data(); + + expect($data)->toIncludeProperties(array( + 'event' => 'deleted', + 'context' => 'taxonomy', + 'log_info' => array( + 'name' => 'test category', + 'term_id' => $term['term_id'], + 'type' => 'category', + ), + )); + + assert_snapshot($data, 'taxonomy-deleted', array('id' => $term['term_id'])); + }); \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..5189c47 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,128 @@ + '1=1', + 'orderby' => 'id', + 'offset' => 0, + 'per_page' => 1, + 'order' => 'DESC', + ); + + $data = array_merge($default_data, $data); + + list( + 'where' => $where, + 'orderby' => $orderby, + 'offset' => $offset, + 'per_page' => $per_page, + 'order' => $order + ) = $data; + + + $results = $wpdb->get_results( + "SELECT * FROM {$table} WHERE {$where} + ORDER BY {$orderby} {$order} + LIMIT $offset, {$per_page};", + ARRAY_A + ); + + if (!$results) { + return array(); + } + // Loop through results and unserialize data + foreach ($results as $key => $result) { + $results[$key]['user_data'] = maybe_unserialize($result['user_data']); + $results[$key]['log_info'] = maybe_unserialize($result['log_info']); + } + + if (sizeof($results) === 1) { + return $results[0]; + } + return $results; +} + +/** + * Make sure that the snapshot is the same as the data + * + * @param array $data Data to create html from database + * @param string $file_name File name of the snapshot + * @param array $replace_map Array of strings to replace in the snapshot + */ +function assert_snapshot(array $data, string $file_name, array $replace_map = array()) { + list( + 'context' => $context, + 'log_info' => $log_info + ) = $data; + + // Check if the snapshot exists + $file_path = __DIR__ . "/snapshots/{$file_name}.html"; + if (!file_exists($file_path)) { + throw new Exception("Snapshot file {$file_path} does not exist"); + } + $file_content = file_get_contents($file_path); + + $html = apply_filters( + "render_log_info_$context", + $log_info, + $data['event'], + $data, + NashaatRenderLogInfo::class + ); + + if (count($replace_map) > 0) { + // First update the keys in the replace map to include @ + $replace_map_with_vars = array(); + foreach ($replace_map as $key => $value) { + $replace_map_with_vars["@{$key}"] = $value; + } + $file_content = strtr($file_content, $replace_map_with_vars); + } + + // Search for @keys in the snapshot and replace them with the values + // from the data + $file_content = preg_replace_callback( + '/@([a-z_0-9.]+)/', + function($matches) use ($data) { + + // . represents a nested array + $path = explode('.', $matches[1]); + + // Build the path to the value + $replce = $data['log_info']; + foreach ($path as $key) { + if (!isset($replce[$key])) { + return $matches[0]; + } + $replce = $replce[$key]; + } + + if (!empty($replce)) { + return strtr($matches[0], array( + $matches[0] => $replce, + )); + } + + return $matches[0]; + }, + $file_content + ); + + expect($html)->toBe($file_content); +} \ No newline at end of file diff --git a/tests/snapshots/comment-edited.html b/tests/snapshots/comment-edited.html new file mode 100644 index 0000000..d44d243 --- /dev/null +++ b/tests/snapshots/comment-edited.html @@ -0,0 +1 @@ +
ID: @id
Parent: @post_id
Author: admin
Post type: @post_type
\ No newline at end of file diff --git a/tests/snapshots/comment-status.html b/tests/snapshots/comment-status.html new file mode 100644 index 0000000..d44d243 --- /dev/null +++ b/tests/snapshots/comment-status.html @@ -0,0 +1 @@ +
ID: @id
Parent: @post_id
Author: admin
Post type: @post_type
\ No newline at end of file diff --git a/tests/snapshots/media-added.html b/tests/snapshots/media-added.html new file mode 100644 index 0000000..1f5b2dc --- /dev/null +++ b/tests/snapshots/media-added.html @@ -0,0 +1 @@ +
ID: @id
Title: @title
Path: @path
File: @file
\ No newline at end of file diff --git a/tests/snapshots/menu-created.html b/tests/snapshots/menu-created.html new file mode 100644 index 0000000..a597114 --- /dev/null +++ b/tests/snapshots/menu-created.html @@ -0,0 +1 @@ +
ID: @id
Name: @name
\ No newline at end of file diff --git a/tests/snapshots/menu-updated.html b/tests/snapshots/menu-updated.html new file mode 100644 index 0000000..a597114 --- /dev/null +++ b/tests/snapshots/menu-updated.html @@ -0,0 +1 @@ +
ID: @id
Name: @name
\ No newline at end of file diff --git a/tests/snapshots/option-updated.html b/tests/snapshots/option-updated.html new file mode 100644 index 0000000..df81c39 --- /dev/null +++ b/tests/snapshots/option-updated.html @@ -0,0 +1 @@ +
Option Name: @option_name
Previous: @old_value
New: @new_value
\ No newline at end of file diff --git a/tests/snapshots/plugin-status.html b/tests/snapshots/plugin-status.html new file mode 100644 index 0000000..c41bd37 --- /dev/null +++ b/tests/snapshots/plugin-status.html @@ -0,0 +1 @@ +
Name: @name
Version: @version
\ No newline at end of file diff --git a/tests/snapshots/taxonomy-created.html b/tests/snapshots/taxonomy-created.html new file mode 100644 index 0000000..f94790a --- /dev/null +++ b/tests/snapshots/taxonomy-created.html @@ -0,0 +1 @@ +
ID: @id
Name: @name
Type: @type
\ No newline at end of file diff --git a/tests/snapshots/taxonomy-deleted.html b/tests/snapshots/taxonomy-deleted.html new file mode 100644 index 0000000..919ab48 --- /dev/null +++ b/tests/snapshots/taxonomy-deleted.html @@ -0,0 +1 @@ +
ID: @id
Name: test category
Type: category
\ No newline at end of file diff --git a/tests/snapshots/taxonomy-edited.html b/tests/snapshots/taxonomy-edited.html new file mode 100644 index 0000000..1d077f0 --- /dev/null +++ b/tests/snapshots/taxonomy-edited.html @@ -0,0 +1 @@ +
ID: @id
Name: test category
Type: @type

Changes

- Slug
Previous: @changes.slug.0
New: @changes.slug.1
- Name
Previous: @changes.name.0
New: @changes.name.1
\ No newline at end of file