diff --git a/.github/workflows/phpcs_on_pull_request.yml b/.github/workflows/phpcs_on_pull_request.yml index 1bb9451..335cf1a 100644 --- a/.github/workflows/phpcs_on_pull_request.yml +++ b/.github/workflows/phpcs_on_pull_request.yml @@ -11,6 +11,7 @@ jobs: - name: Run PHPCS inspection uses: docker://rtcamp/action-phpcs-code-review:v2.0.0 env: + SKIP_FOLDERS: "tests,.github" VAULT_ADDR: ${{ secrets.VAULT_ADDR }} VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }} with: diff --git a/.github/workflows/phpunit_on_pull_request.yml b/.github/workflows/phpunit_on_pull_request.yml new file mode 100644 index 0000000..473d8f9 --- /dev/null +++ b/.github/workflows/phpunit_on_pull_request.yml @@ -0,0 +1,12 @@ +on: pull_request +name: PHPUnit +jobs: + runPHPCSInspection: + name: Run PHPUnit test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Run PHPUnit test + uses: docker://rtcamp/action-run-phpunit:v1.0.0 diff --git a/README.md b/README.md index b327cfd..b4e6083 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,42 @@ npm run precommit ``` +## Contributing + +### Reporting a bug 🐞 + +Before creating a new issue, do browse through the [existing issues](https://github.com/rtCamp/blank-theme/issues) for resolution or upcoming fixes. + +If you still need to [log an issue](https://github.com/rtCamp/blank-theme/issues/new), making sure to include as much detail as you can, including clear steps to reproduce your issue if possible. + +### Create a pull request + +Want to contribute a new feature? Start a conversation by logging an [issue](https://github.com/rtCamp/blank-theme/issues). + +Once you're ready to send a pull request, please run through the following checklist: + +1. Browse through the existing issues for anything related to what you want to work on. If you don't find any related issues, open a new one. + +1. Fork the repository. + +1. Create a branch from `develop` for each issue you'd like to address and commit your changes. + +1. Push the code changes from your local clone to your fork. + +1. Open a pull request and that's it! We'll with feedback as soon as possible (Isn't collaboration a great thing? 😌) + +1. Once your pull request has passed final code review and tests, it will be merged into `develop` and be in the pipeline for the next release. Props to you! 🎉 + +### Unit testing + +- Setup local unit test environment by running script from terminal + +```./bin/install-wp-tests.sh [db-host] [wp-version] [skip-database-creation]``` +- Execute `phpunit` in terminal from repository to run all test cases. +- Execute `phpunit ./tests/inc/test-class.php` in terminal with file path to run specific tests. + + +Good luck! Does this interest you? --------------- diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100755 index 0000000..5ceac4b --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") +WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then + WP_BRANCH=${WP_VERSION%\-*} + WP_TESTS_TAG="branches/$WP_BRANCH" + +elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" +elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p $TMPDIR/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip + unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ + mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then + # https serves multiple offers, whereas http serves single. + download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + LATEST_VERSION=${WP_VERSION%??} + else + # otherwise, scan the releases and get the most up to date minor version of the major release + local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` + LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) + fi + if [[ -z "$LATEST_VERSION" ]]; then + local ARCHIVE_NAME="wordpress-$WP_VERSION" + else + local ARCHIVE_NAME="wordpress-$LATEST_VERSION" + fi + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz + tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i.bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/inc/classes/class-assets.php b/inc/classes/class-assets.php index aae64cd..546dd52 100644 --- a/inc/classes/class-assets.php +++ b/inc/classes/class-assets.php @@ -60,8 +60,9 @@ public function register_scripts() { wp_enqueue_script( 'blank-theme-single' ); } + // Ignoring this block becuase trying to mock comments_open() throws error. if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { - wp_enqueue_script( 'comment-reply' ); + wp_enqueue_script( 'comment-reply' ); // @codeCoverageIgnore } } diff --git a/inc/classes/class-blank-theme.php b/inc/classes/class-blank-theme.php index 6055367..3650768 100644 --- a/inc/classes/class-blank-theme.php +++ b/inc/classes/class-blank-theme.php @@ -48,6 +48,7 @@ protected function setup_hooks() { */ add_action( 'wp_head', [ $this, 'add_pingback_link' ] ); add_action( 'after_setup_theme', [ $this, 'setup_theme' ] ); + add_action( 'init', [ $this, 'add_title_tag_support' ] ); } @@ -61,7 +62,6 @@ public function setup_theme() { load_theme_textdomain( 'blank-theme', BLANK_THEME_TEMP_DIR . '/languages' ); add_theme_support( 'automatic-feed-links' ); - add_theme_support( 'title-tag' ); add_theme_support( 'post-thumbnails' ); add_theme_support( 'customize-selective-refresh-widgets' ); add_theme_support( 'jetpack-responsive-videos' ); @@ -122,9 +122,18 @@ public function setup_theme() { ] ); - if ( ! isset( $content_width ) ) { - $content_width = 900; - } + } + + /** + * Function to add Title theme support. + * + * @action init. + * + * @codeCoverageIgnore Not able to test this as it throws unexpected incorrect usgae error + * when called in unit test case. + */ + public function add_title_tag_support() { + add_theme_support( 'title-tag' ); } /** diff --git a/inc/classes/class-customizer.php b/inc/classes/class-customizer.php index d0ae4f3..ee24095 100644 --- a/inc/classes/class-customizer.php +++ b/inc/classes/class-customizer.php @@ -47,9 +47,17 @@ protected function setup_hooks() { */ public function customize_register( \WP_Customize_Manager $wp_customize ) { - $wp_customize->get_setting( 'blogname' )->transport = 'postMessage'; - $wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage'; - $wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage'; + if ( ! empty( $wp_customize->get_setting( 'blogname' ) ) ) { + $wp_customize->get_setting( 'blogname' )->transport = 'postMessage'; + } + + if ( ! empty( $wp_customize->get_setting( 'blogdescription' ) ) ) { + $wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage'; + } + + if ( ! empty( $wp_customize->get_setting( 'header_textcolor' ) ) ) { + $wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage'; + } if ( isset( $wp_customize->selective_refresh ) ) { @@ -94,6 +102,9 @@ public function customize_partial_blog_description() { * Enqueue customizer scripts. * * @action customize_preview_init + * + * Ignoring this because the asset file names are generated dynamically and fetched from manifest so getting issues with mocking. + * @codeCoverageIgnore */ public function enqueue_customizer_scripts() { diff --git a/inc/helpers/custom-functions.php b/inc/helpers/custom-functions.php index 4f0a4ee..351bc2d 100644 --- a/inc/helpers/custom-functions.php +++ b/inc/helpers/custom-functions.php @@ -11,6 +11,8 @@ * @param string $slug file slug like you use in get_template_part without php extension. * @param array $variables pass an array of variables you want to use in array keys. * + * @codeCoverageIgnore Ignoring becuase not able to mock output for locate_template + * * @return void */ function blank_theme_get_template_part( $slug, $variables = [] ) { diff --git a/phpcs.xml b/phpcs.xml index 5c42785..372ce1c 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -45,6 +45,7 @@ + @@ -53,6 +54,10 @@ + + + + + tests/* diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..8406a73 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + ./tests/ + + + + + ./inc/ + + ./inc/helpers/template-tags.php + ./inc/helpers/autoloader.php + ./inc/traits + + + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..0e6819d --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,51 @@ +getMethod( $method_name ); + $method->setAccessible( true ); + + return $method->invokeArgs( $object, $parameters ); + + } + + /** + * Utility method to get private/protected property of a class/object. + * This is a generic wrapper function to align with relfection class and not to be use directly. + * + * @param mixed $object_or_class_name The object/class whose property is to be accessed. + * @param string $property_name The name of the property to access. + * + * @return mixed Value of the hidden property being accessed. + */ + public static function get_property( $object_or_class_name, $property_name ) { + + $object = null; + + if ( is_object( $object_or_class_name ) ) { + $object = $object_or_class_name; + $class_name = get_class( $object ); + } else { + $class_name = $object_or_class_name; + } + + $o_reflection = new \ReflectionClass( $class_name ); + $property = $o_reflection->getProperty( $property_name ); + $property->setAccessible( true ); + + return $property->getValue( $object ); + + } + + /** + * Utility method to set private/protected property of an object/class. + * This is a generic wrapper function to align with relfection class and not to be use directly. + * + * @param mixed $object_or_class_name The object/class whose property is to be accessed. + * @param string $property_name The name of the property to access. + * @param mixed $property_value The value to be set for the hidden property. + * + * @return mixed Value of the hidden property being accessed. + */ + public static function set_and_get_property( $object_or_class_name, string $property_name, $property_value ) { + + $object = null; + + if ( is_object( $object_or_class_name ) ) { + $object = $object_or_class_name; + $class_name = get_class( $object ); + } else { + $class_name = $object_or_class_name; + } + + $o_reflection = new \ReflectionClass( $class_name ); + $property = $o_reflection->getProperty( $property_name ); + $property->setAccessible( true ); + $property->setValue( $object, $property_value ); + + return $property->getValue( $object ); + + } + + /** + * Utility method to capture output from a function. + * + * @param Callable $callback The callback from which output is to be captured + * @param array $parameters Parameters to be passed to the $callback. + * @return mixed Output from callback + * + * @throws \ErrorException Error exception. + */ + public static function buffer_and_return( $callback, array $parameters = array() ) { + + if ( ! is_callable( $callback ) ) { + throw new \ErrorException( sprintf( '%s::%s() expects first parameter to be a valid callback', __CLASS__, __FUNCTION__ ) ); + } + + ob_start(); + + call_user_func_array( $callback, $parameters ); + + return trim( ob_get_clean() ); + + } + + /** + * Utility method to mock global wp query. + * + * @param array $args WP query arguments. + * @param array $conditions wp query conditions. + */ + public static function mock_wp_query( $args, $conditions ) { + + $wp_query = new \WP_Query( $args ); + + foreach ( $conditions as $key => $value ) { + $wp_query->{$key} = $value; + } + + // phpcs:disable + $GLOBALS['wp_query'] = $wp_query; + $GLOBALS['wp_the_query'] = $GLOBALS['wp_query']; + do_action_ref_array( 'pre_get_posts', [ &$GLOBALS['wp_query'] ] ); + // phpcs:enable + } + +} diff --git a/tests/inc/classes/test-class-assets.php b/tests/inc/classes/test-class-assets.php new file mode 100644 index 0000000..e5ab26d --- /dev/null +++ b/tests/inc/classes/test-class-assets.php @@ -0,0 +1,145 @@ + + * + * @package Blank_Theme + */ + +namespace BLANK_THEME\Tests; + +use BLANK_THEME\Inc\Assets; +use WP_Error; + +/** + * Class Test_Assets + * + * @coversDefaultClass \BLANK_THEME\Inc\Assets + */ +class Test_Assets extends \WP_UnitTestCase { + /** + * This Assets data member will contain Assets class object. + * + * @var BLANK_THEME\Inc\Assets + */ + protected $instance = false; + + /** + * This function activate the theme. + * + * @return void + */ + public function setUp() : void { + + parent::setUp(); + $this->instance = Assets::get_instance(); + switch_theme( 'blank-theme' ); + } + + /** + * Function to test hooks setup. + * + * @covers ::setup_hooks + * @covers ::__construct + * + * @return void + */ + public function test_setup_hooks() { + + Utility::invoke_method( $this->instance, '__construct' ); + + $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $this->instance, 'register_scripts' ) ) ); + $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $this->instance, 'register_styles' ) ) ); + + } + + /** + * Tests script registration. + * + * @covers ::register_scripts + * @covers ::register_script + */ + public function test_register_scripts() { + + $this->instance->register_scripts(); + + $this->assertTrue( wp_script_is( 'blank-theme-main' ) ); + + Utility::mock_wp_query( + array(), + array( + 'is_front_page' => true, + 'is_home' => true, + ) + ); + + $this->instance->register_scripts(); + + $this->assertTrue( wp_script_is( 'blank-theme-home' ) ); + + Utility::mock_wp_query( + array(), + array( + 'is_single' => true, + ) + ); + + $this->instance->register_scripts(); + + $this->assertTrue( wp_script_is( 'blank-theme-single' ) ); + } + + /** + * Tests styles registration. + * + * @covers ::register_styles + * @covers ::register_style + */ + public function test_register_styles() { + + $this->instance->register_styles(); + + $this->assertTrue( wp_style_is( 'blank-theme-main' ) ); + + Utility::mock_wp_query( + array(), + array( + 'is_front_page' => true, + 'is_home' => true, + ) + ); + + $this->instance->register_styles(); + + $this->assertTrue( wp_style_is( 'blank-theme-home' ) ); + + Utility::mock_wp_query( + array(), + array( + 'is_single' => true, + ) + ); + + $this->instance->register_styles(); + + $this->assertTrue( wp_style_is( 'blank-theme-single' ) ); + } + + /** + * Tests get_file_version function. + * + * @covers ::get_file_version + */ + public function test_get_file_version() { + + $this->assertEquals( '1.0' , $this->instance->get_file_version( 'non-existent.css', '1.0' ) ); + + $this->assertFalse( $this->instance->get_file_version( 'non-existent.css', false ) ); + + $file_path = sprintf( '%s/%s', BLANK_THEME_BUILD_DIR, 'css/main.css' ); + $expected = filemtime( $file_path ); + $this->assertEquals( $expected, $this->instance->get_file_version( 'css/main.css', false ) ); + } + +} diff --git a/tests/inc/classes/test-class-blank-theme.php b/tests/inc/classes/test-class-blank-theme.php new file mode 100644 index 0000000..048ef8d --- /dev/null +++ b/tests/inc/classes/test-class-blank-theme.php @@ -0,0 +1,220 @@ + + * + * @package Blank_Theme + */ + +namespace BLANK_THEME\Tests; + +use Exception; +use BLANK_THEME\Inc\BLANK_THEME; + +/** + * Class Test_Blank_Theme + * + * @coversDefaultClass \BLANK_THEME\Inc\BLANK_THEME + */ +class Test_Blank_Theme extends \WP_UnitTestCase { + /** + * This Assets data member will contain Assets class object. + * + * @var \BLANK_THEME\Inc\BLANK_THEME + */ + protected $instance = false; + + /** + * This function activate the theme. + * + * @return void + */ + public function setUp() : void { + + parent::setUp(); + switch_theme( 'blank-theme' ); + $this->instance = BLANK_THEME::get_instance(); + $this->mock_post = $this->factory()->post->create_and_get(); + $GLOBALS['post'] = $this->mock_post; + add_filter( 'is_active_sidebar', array( $this, 'deactivate_sidebar' ), 10, 2 ); + } + + /** + * Reset global post after every test case. + * + * @return void + */ + public function tearDown() { + unset( $GLOBALS['post'] ); + } + + /** + * Test constructor function. + * + * @covers ::__construct + * @covers ::setup_hooks + * + * @return void + */ + public function test_construct() { + + Utility::invoke_method( $this->instance, '__construct' ); + $this->assertInstanceOf( 'BLANK_THEME\Inc\BLANK_THEME', $this->instance ); + $hooks = array( + array( + 'type' => 'filter', + 'name' => 'excerpt_more', + 'priority' => 10, + 'function' => 'add_read_more_link', + ), + array( + 'type' => 'filter', + 'name' => 'body_class', + 'priority' => 10, + 'function' => 'filter_body_classes', + ), + array( + 'type' => 'action', + 'name' => 'wp_head', + 'priority' => 10, + 'function' => 'add_pingback_link', + ), + array( + 'type' => 'action', + 'name' => 'after_setup_theme', + 'priority' => 10, + 'function' => 'setup_theme', + ), + array( + 'type' => 'action', + 'name' => 'init', + 'priority' => 10, + 'function' => 'add_title_tag_support', + ), + ); + + // Check if hooks loaded. + foreach ( $hooks as $hook ) { + + $this->assertEquals( + $hook['priority'], + call_user_func( + sprintf( 'has_%s', $hook['type'] ), + $hook['name'], + array( + $this->instance, + $hook['function'], + ) + ), + sprintf( 'BLANK_THEME::__construct() failed to register %1$s "%2$s" to %3$s()', $hook['type'], $hook['name'], $hook['function'] ) + ); + } + } + + /** + * Test function setup theme + * + * @covers ::setup_theme + * + * @return void + */ + public function test_setup_theme() { + + $this->instance->setup_theme(); + + $this->assertTrue( get_theme_support( 'automatic-feed-links' ) ); + $this->assertTrue( get_theme_support( 'title-tag' ) ); + $this->assertTrue( get_theme_support( 'post-thumbnails' ) ); + $this->assertTrue( get_theme_support( 'customize-selective-refresh-widgets' ) ); + $this->assertTrue( get_theme_support( 'jetpack-responsive-videos' ) ); + $this->assertTrue( get_theme_support( 'wp-block-styles' ) ); + $this->assertTrue( get_theme_support( 'align-wide' ) ); + + $this->assertTrue( is_array( get_theme_support( 'html5' ) ) ); + $this->assertTrue( is_array( get_theme_support( 'post-formats' ) ) ); + $this->assertTrue( is_array( get_theme_support( 'custom-background' ) ) ); + $this->assertTrue( is_array( get_theme_support( 'custom-logo' ) ) ); + + $this->assertArrayHasKey( 'primary', get_registered_nav_menus(), 'Primary menu registered' ); + + } + + /** + * Test add read more link. + * + * @covers ::add_read_more_link + * + * @return void + */ + public function test_add_read_more_link() { + global $post; + + $post_id = $this->factory()->post->create( array( 'post_title' => 'Test Post' ) ); + $post = get_post( $post_id ); + + $read_more_link = sprintf( '%s', get_permalink( $post->ID ), esc_html__( 'Read More', 'blank-theme' ) ); + + $this->assertEquals( $read_more_link, $this->instance->add_read_more_link() ); + } + + /** + * Test function to add custom body classes. + * + * @covers ::filter_body_classes + * + * @return void + */ + public function test_filter_body_classes() { + // error_log( var_export( is_active_sidebar( 'sidebar-1' ), true ) ); + $this->assertContains( 'test-class', $this->instance->filter_body_classes( array( 'test-class' ) ) ); + $this->assertContains( 'hfeed', $this->instance->filter_body_classes( array( 'test-class' ) ) ); + $this->assertContains( 'no-sidebar', $this->instance->filter_body_classes( array( 'test-class' ) ) ); + Utility::mock_wp_query( + array(), + array( + 'is_singular' => true, + ) + ); + + $this->assertNotContains( 'hfeed', $this->instance->filter_body_classes( array( 'test-class' ) ) ); + } + + /** + * Test function to add pingback link. + * + * @covers ::add_pingback_link + * + * @return void + */ + public function test_add_pingback_link() { + + add_filter( 'pings_open', '__return_true' ); + $expected = ''; + + Utility::mock_wp_query( + array(), + array( + 'is_singular' => true, + ) + ); + $actual = Utility::buffer_and_return( array( $this->instance, 'add_pingback_link' ) ); + $this->assertEquals( $expected, $actual ); + } + + /** + * Helper function to disabled sidebar. + * + * @param boolean $is_active Whether the sidebar is active. + * @param int|string $index Index, name, or ID of the dynamic sidebar. + * + * @return boolean + */ + public function deactivate_sidebar( $is_active, $index ) { + + if ( 'sidebar-1' === $index ) { + return false; + } + return $is_active; + } +} diff --git a/tests/inc/classes/test-class-customizer.php b/tests/inc/classes/test-class-customizer.php new file mode 100644 index 0000000..ea454fd --- /dev/null +++ b/tests/inc/classes/test-class-customizer.php @@ -0,0 +1,162 @@ + + * + * @package Blank_Theme + */ + +namespace BLANK_THEME\Tests; + +use BLANK_THEME\Inc\Customizer; + +/** + * Class Test_Customizer + * + * @coversDefaultClass \Blank_Theme\Inc\Customizer + */ +class Test_Customizer extends \WP_UnitTestCase { + /** + * This Assets data member will contain Assets class object. + * + * @var BLANK_THEME\Inc\Customizer + */ + protected $instance = false; + + /** + * This function activate the theme. + * + * @return void + */ + public function setUp(): void { + + parent::setUp(); + switch_theme( 'blank-theme' ); + $this->instance = Customizer::get_instance(); + } + + /** + * Tests class construct. + * + * @covers ::__construct + * @covers ::setup_hooks + * + * @return void + */ + public function test_construct() { + Utility::invoke_method( $this->instance, '__construct' ); + + $this->assertInstanceOf( 'BLANK_THEME\Inc\Customizer', $this->instance ); + + $hooks = array( + array( + 'type' => 'action', + 'name' => 'customize_register', + 'priority' => 10, + 'function' => 'customize_register', + ), + array( + 'type' => 'action', + 'name' => 'customize_preview_init', + 'priority' => 10, + 'function' => 'enqueue_customizer_scripts', + ), + ); + + // Check if hooks loaded. + foreach ( $hooks as $hook ) { + + $this->assertEquals( + $hook['priority'], + call_user_func( + sprintf( 'has_%s', $hook['type'] ), + $hook['name'], + array( + $this->instance, + $hook['function'], + ) + ), + sprintf( 'Customizer::__construct() failed to register %1$s "%2$s" to %3$s()', $hook['type'], $hook['name'], $hook['function'] ) + ); + } + } + + /** + * Tests customize partial blog name. + * + * @covers ::customize_partial_blog_name + * + * @return void + */ + public function test_customize_partial_blog_name() { + $expected = get_bloginfo( 'name' ); + + $actual = Utility::buffer_and_return( array( $this->instance, 'customize_partial_blog_name' ) ); + $this->assertEquals( $expected, $actual ); + + } + + /** + * Tests customize partial blog description. + * + * @covers ::customize_partial_blog_description + * + * @return void + */ + public function test_partial_blog_description() { + $expected = get_bloginfo( 'description' ); + + $actual = Utility::buffer_and_return( array( $this->instance, 'customize_partial_blog_description' ) ); + $this->assertEquals( $expected, $actual ); + } + + /** + * Tests `customize_register` function. + * + * @covers ::customize_register + * + * @return void + */ + public function test_customize_register() { + + wp_set_current_user( + $this->factory->user->create( array( 'role' => 'administrator' ) ) + ); + + $wp_customize = new \WP_Customize_Manager(); + + $wp_customize->add_setting( + 'blogname', + array( + 'default' => 'Test', + 'transport' => 'postName', + ) + ); + + $wp_customize->add_setting( + 'blogdescription', + array( + 'default' => 'Test', + 'transport' => 'postName', + ) + ); + + $wp_customize->add_setting( + 'header_textcolor', + array( + 'default' => 'Test', + 'transport' => 'postName', + ) + ); + + $this->instance->customize_register( $wp_customize ); + + $this->assertEquals( $wp_customize->get_setting( 'blogname' )->transport, 'postMessage' ); + + $this->assertEquals( $wp_customize->get_setting( 'blogdescription' )->transport, 'postMessage' ); + + $this->assertEquals( $wp_customize->get_setting( 'header_textcolor' )->transport, 'postMessage' ); + + } +} diff --git a/tests/inc/classes/test-class-widgets.php b/tests/inc/classes/test-class-widgets.php new file mode 100644 index 0000000..b3295c6 --- /dev/null +++ b/tests/inc/classes/test-class-widgets.php @@ -0,0 +1,75 @@ + + * + * @package Blank_Theme + */ + +namespace BLANK_THEME\Tests; + +use BLANK_THEME\Inc\Widgets; + +/** + * Class Test_Customizer + * + * @coversDefaultClass \BLANK_THEME\Inc\Widgets + */ +class Test_Widgets extends \WP_UnitTestCase { + /** + * This Assets data member will contain Assets class object. + * + * @var BLANK_THEME\Inc\Widgets + */ + protected $instance = false; + + /** + * This function activate the theme. + * + * @return void + */ + public function setUp(): void { + + parent::setUp(); + switch_theme( 'blank-theme' ); + $this->instance = Widgets::get_instance(); + } + + /** + * Tests class construct. + * + * @covers ::__construct + * @covers ::setup_hooks + * + * @return void + */ + public function test_construct() { + + Utility::invoke_method( $this->instance, '__construct' ); + + $this->assertInstanceOf( 'BLANK_THEME\Inc\Widgets', $this->instance ); + + $this->assertEquals( 10, has_action( 'widgets_init', array( $this->instance, 'register_widgets' ) ) ); + + } + + /** + * Tests `register_widgets` function. + * + * @covers ::register_widgets + * + * @return void + */ + public function test_register_widgets() { + + $this->instance->register_widgets(); + + $sidebars = wp_get_sidebars_widgets(); + + $this->assertArrayHasKey( 'sidebar-1', $sidebars ); + $this->assertArrayHasKey( 'sidebar-2', $sidebars ); + + } + +}