diff --git a/.distignore b/.distignore
new file mode 100755
index 0000000000000000000000000000000000000000..de45f50cb1a7a08e1da5aa43d801213aa7e23503
--- /dev/null
+++ b/.distignore
@@ -0,0 +1,35 @@
+# A set of files you probably don't want in your WordPress.org distribution
+.distignore
+.editorconfig
+.git
+.gitignore
+.gitlab-ci.yml
+.travis.yml
+.DS_Store
+Thumbs.db
+behat.yml
+bitbucket-pipelines.yml
+bin
+.circleci/config.yml
+composer.json
+composer.lock
+Gruntfile.js
+package.json
+package-lock.json
+phpunit.xml
+phpunit.xml.dist
+multisite.xml
+multisite.xml.dist
+.phpcs.xml
+phpcs.xml
+.phpcs.xml.dist
+phpcs.xml.dist
+README.md
+wp-cli.local.yml
+yarn.lock
+tests
+vendor
+node_modules
+*.sql
+*.tar.gz
+*.zip
diff --git a/.editorconfig b/.editorconfig
new file mode 100755
index 0000000000000000000000000000000000000000..79207a40cb9326b8c6b8c958fa864b5345f94e68
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,22 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+# WordPress Coding Standards
+# https://make.wordpress.org/core/handbook/coding-standards/
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = tab
+indent_size = 4
+
+[{.jshintrc,*.json,*.yml}]
+indent_style = space
+indent_size = 2
+
+[{*.txt,wp-config-sample.php}]
+end_of_line = crlf
diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..6f68bc7f65c25ceae8e856eb9ab25fa815acf0cd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+phpcs.xml
+phpunit.xml
+Thumbs.db
+wp-cli.local.yml
+node_modules/
+*.sql
+*.tar.gz
+*.zip
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100755
index 0000000000000000000000000000000000000000..9b4ac3d82afb3c36b59848557a88a69859588aee
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,46 @@
+variables:
+  # Configure mysql service (https://hub.docker.com/_/mysql/)
+  MYSQL_DATABASE: wordpress_tests
+  MYSQL_ROOT_PASSWORD: mysql
+
+cache:
+  paths:
+    - $HOME/.composer
+    - /root/.composer
+
+before_script:
+  # Set up WordPress tests
+  - bash bin/install-wp-tests.sh $MYSQL_DATABASE root $MYSQL_ROOT_PASSWORD mysql latest true
+
+  # PHPUnit
+  - |
+    if [[ $(php -v) =~ "PHP 7." ]]; then
+      composer global require "phpunit/phpunit=6.1.*"
+    else
+      composer global require "phpunit/phpunit=4.8.*"
+    fi
+
+  # Install PHPCS and WPCS
+  - composer global require automattic/vipwpcs
+  - composer global require phpcompatibility/phpcompatibility-wp
+  - phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/automattic/vipwpcs,$HOME/.composer/vendor/phpcompatibility/php-compatibility,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-paragonie,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-wp
+
+PHPunit:PHP7.2:MySQL:
+  image: containers.ethitter.com:443/docker/images/php:7.2
+  services:
+    - mysql:5.6
+  script:
+    - find . -type "f" -iname "*.php" | xargs -L "1" php -l
+    - phpcs -n
+    - phpunit
+  allow_failure: true
+
+PHPunit:PHP7.3:MySQL:
+  image: containers.ethitter.com:443/docker/images/php:7.3
+  services:
+    - mysql:5.6
+  script:
+    - find . -type "f" -iname "*.php" | xargs -L "1" php -l
+    - phpcs -n
+    - phpunit
+  allow_failure: true
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..3f154fca742b01ea0248772ebd888de38484f40c
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<ruleset name="Taxonomy Dropdown Widget">
+	<description>Generally-applicable sniffs for WordPress plugins.</description>
+
+	<!-- What to scan -->
+	<file>.</file>
+	<exclude-pattern>/vendor/</exclude-pattern>
+	<exclude-pattern>/node_modules/</exclude-pattern>
+	<exclude-pattern>/tests/*</exclude-pattern>
+
+	<!-- How to scan -->
+	<!-- Usage instructions: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage -->
+	<!-- Annotated ruleset: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml -->
+	<arg value="sp"/> <!-- Show sniff and progress -->
+	<arg name="basepath" value="./"/><!-- Strip the file paths down to the relevant bit -->
+	<arg name="colors"/>
+	<arg name="extensions" value="php"/>
+	<arg name="parallel" value="8"/><!-- Enables parallel processing when available for faster results. -->
+
+	<!-- Rules: Check PHP version compatibility -->
+	<!-- https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions -->
+	<config name="testVersion" value="7.2-"/>
+	<!-- https://github.com/PHPCompatibility/PHPCompatibilityWP -->
+	<rule ref="PHPCompatibilityWP"/>
+
+	<!-- Rules: WordPress Coding Standards -->
+	<!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards -->
+	<!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties -->
+	<config name="minimum_supported_wp_version" value="2.8"/>
+	<rule ref="WordPress" />
+	<rule ref="WordPressVIPMinimum" />
+	<rule ref="WordPress-VIP-Go" />
+	<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
+		<properties>
+			<!-- Value: replace the function, class, and variable prefixes used. Separate multiple prefixes with a comma. -->
+			<property name="prefixes" type="array" value="taxonomy_dropdown_"/>
+		</properties>
+	</rule>
+	<rule ref="WordPress.WP.I18n">
+		<properties>
+			<!-- Value: replace the text domain used. -->
+			<property name="text_domain" type="array" value="taxonomy_dropdown_widget"/>
+		</properties>
+	</rule>
+	<rule ref="WordPress.WhiteSpace.ControlStructureSpacing">
+		<properties>
+			<property name="blank_line_check" value="true"/>
+		</properties>
+	</rule>
+</ruleset>
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100755
index 0000000000000000000000000000000000000000..1b5b61d5f96677ff5868840eed4b763ce1b58856
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,56 @@
+module.exports = function( grunt ) {
+
+	'use strict';
+
+	// Project configuration
+	grunt.initConfig( {
+
+		pkg: grunt.file.readJSON( 'package.json' ),
+
+		addtextdomain: {
+			options: {
+				textdomain: 'taxonomy_dropdown_widget',
+			},
+			update_all_domains: {
+				options: {
+					updateDomains: true
+				},
+				src: [ '*.php', '**/*.php', '!\.git/**/*', '!bin/**/*', '!node_modules/**/*', '!tests/**/*' ]
+			}
+		},
+
+		wp_readme_to_markdown: {
+			your_target: {
+				files: {
+					'README.md': 'readme.txt'
+				}
+			},
+		},
+
+		makepot: {
+			target: {
+				options: {
+					domainPath: '/languages',
+					exclude: [ '\.git/*', 'bin/*', 'node_modules/*', 'tests/*' ],
+					mainFile: 'tag-dropdown-widget.php',
+					potFilename: 'tag-dropdown-widget.pot',
+					potHeaders: {
+						poedit: true,
+						'x-poedit-keywordslist': true
+					},
+					type: 'wp-plugin',
+					updateTimestamp: true
+				}
+			}
+		},
+	} );
+
+	grunt.loadNpmTasks( 'grunt-wp-i18n' );
+	grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' );
+	grunt.registerTask( 'default', [ 'i18n','readme' ] );
+	grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] );
+	grunt.registerTask( 'readme', ['wp_readme_to_markdown'] );
+
+	grunt.util.linefeed = '\n';
+
+};
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f3530c8abd9105c43368c4198adc65312743c43
--- /dev/null
+++ b/README.md
@@ -0,0 +1,195 @@
+# Taxonomy Dropdown Widget #
+**Contributors:** ethitter  
+**Donate link:** https://ethitter.com/donate/  
+**Tags:** tag, tags, taxonomy, sidebar, widget, widgets, dropdown, drop down  
+**Requires at least:** 2.8  
+**Tested up to:** 5.2  
+**Stable tag:** 2.3  
+**License:** GPLv2 or later  
+**License URI:** http://www.gnu.org/licenses/gpl-2.0.html  
+
+Creates a dropdown list of non-hierarchical taxonomies as an alternative to the term (tag) cloud. Formerly known as Tag Dropdown Widget.
+
+## Description ##
+
+Creates dropdown lists of non-hierarchical taxonomies (such as `post tags`) as an alternative to term (tag) clouds. Multiple widgets can be used, each with its own set of options.
+
+Numerous formatting options are provided, including maximum numbers of terms, term order, truncating of term names, and more.
+
+Using the `taxonomy_dropdown_widget()` function, users can generate dropdowns for use outside of the included widget.
+
+**Only use version 2.2 or higher with WordPress 4.2 and later releases.** WordPress 4.2 changed how taxonomy information is stored in the database, which directly impacts this plugin's include/exclude term functionality.
+
+This plugin was formerly known as the `Tag Dropdown Widget`. It was completely rewritten for version 2.0.
+
+**Follow and contribute to development on GitHub at https://github.com/ethitter/Taxonomy-Dropdown-Widget.**
+
+## Installation ##
+
+1. Upload taxonomy-dropdown-widget.php to /wp-content/plugins/.
+2. Activate plugin through the WordPress Plugins menu.
+3. Activate widget from the Appearance > Widgets menu in WordPress.
+4. Set display options from the widget's administration panel.
+
+## Frequently Asked Questions ##
+
+### What happened to the Tag Dropdown Widget plugin? ###
+
+Since I first released this plugin in November 2009, WordPress introduced custom taxonomies and, as more-fully discussed below, saw a new widgets API overtake its predecessor. As part of the widgets-API-related rewrite, I expanded the plugin to support non-hierarchical custom taxonomies, which necessitated a new name for the plugin.
+
+### Why did you rewrite the plugin? ###
+
+When I first wrote the Tag Dropdown Widget plugin (and it's sister Tag List Widget), WordPress was amidst a change in how widgets were managed. I decided to utilize the old widget methods to ensure the greatest compatibility at the time. In the nearly two years since I released version 1.0, the new widget system has been widely adopted, putting this plugin at a disadvantage. So, I rewrote the plugin to use the new widget API and added support for non-hierarchical taxonomies other than just post tags.
+
+### I upgraded to version 2.0 and all of my widgets disappeared. What happened? ###
+
+As discussed above, WordPress' widget system has changed drastically since I first released this plugin. To facilitate multiple uses of the same widget while allowing each to maintain its own set of options, the manner for storing widget options changed. As a result, there is no practical way to transition a widget's options from version 1.7 to 2.0.
+
+### If my theme does not support widgets, or I would like to include the dropdown outside of the sidebar, can I still use the plugin? ###
+
+Insert the function `<?php if( function_exists( 'taxonomy_dropdown_widget' ) ) echo taxonomy_dropdown_widget( $args, $id ); ?>` where the dropdown should appear, specifying `$args` as an array of arguments and, optionally, `$id` as a string uniquely identifying this dropdown.
+
+* taxonomy - slug of taxonomy for dropdown. Defaults to `post_tag`.
+* select_name - name of first (default) option in the dropdown. Defaults to `Select Tag`.
+* max_name_length - integer representing maximum length of term name to display. Set to `0` to show full names. Defaults to `0`.
+* cutoff - string indicating that a term name has been cutoff based on the `max_name_length` setting. Defaults to an ellipsis (`&hellip;`).
+* limit - integer specifying maximum number of terms to retrieve. Set to `0` for no limit. Defaults to `0`.
+* orderby - either `name` to order by term name or `count` to order by the number of posts associated with the given term. Defaults to `name`.
+* order - either `ASC` for ascending order or `DESC` for descending order. Defaults to `ASC`.
+* threshold - integer specifying the minimum number of posts to which a term must be assigned to be included in the dropdown. Set to `0` for now threshold. Defaults to `0`.
+* incexc - `include` or `exclude` to either include or exclude the terms whose IDs are included in `incexc_ids`. By default, this restriction is not enabled.
+* incexc_ids - comma-separated list of term IDs to either include or exclude based on the `incexc` setting.
+* hide_empty - set to `false` to include in the dropdown any terms that haven't been assigned to any objects (i.e. unused tags). Defaults to `true`.
+* post_counts - set to `true` to include post counts after term names. Defaults to `false`.
+
+### Why are the makeTagDropdown(), TDW_direct(), and generateTagDropdown() functions deprecated? ###
+
+Version 2.0 represents a complete rewrite of the original Tag Dropdown Widget plugin. As part of the rewrite, all prior functions for generating tag dropdowns were deprecated, or marked as obsolete, because they are unable to access the full complement of features introduced in version 2.0. While the functions still exist, their capabilities are extremely limited and they should now be replaced with `taxonomy_dropdown_widget()`.
+
+### Where do I obtain a term's ID for use with the inclusion or exclusion options? ###
+
+Term IDs can be obtained in a variety of ways. The easiest is to visit the taxonomy term editor (Post Tags, found under Posts, for example) and, while hovering over the term's name, looking at your browser's status bar. At the very end of the address shown in the status bar, the term ID will follow the text "tag_ID."
+
+You can also obtain the term ID by clicking the edit link below any term's name in the Post Tags page. Next, look at your browser's address bar. At the very end of the address, the term ID will follow the text "tag_ID."
+
+### I'd like more control over the tags shown in the dropdown. Is this possible? ###
+
+This plugin relies on WordPress' `get_terms()` function (http://codex.wordpress.org/Function_Reference/get_terms). To modify the arguments passed to this function, use the `taxonomy_dropdown_widget_options` filter to specify any of the arguments discussed in the Codex page for `get_terms()`.
+
+To make targeting a specific filter reference possible should you use multiple instances of the dropdown (multiple widgets, use of the `taxonomy_dropdown_widget()` function, or some combination thereof), the filter provides a second argument, `$id`, that is either the numeric ID of the widget's instance or the string provided as the second argument to `taxonomy_dropdown_widget()`.
+
+## Changelog ##
+
+### 2.3 ###
+* Update for WordPress 4.3 by removing PHP4-style widget constructor usage (https://make.wordpress.org/core/2015/07/02/deprecating-php4-style-constructors-in-wordpress-4-3/).
+
+### 2.2 ###
+* Update for WordPress 4.2 to handle term splitting in the plugin's include/exclude functionality. Details at https://make.wordpress.org/core/2015/02/16/taxonomy-term-splitting-in-4-2-a-developer-guide/.
+
+### 2.1 ###
+* Introduce filters on dropdown and its components for greater customizability.
+* Implement plugin as a singleton for proper reusability.
+* Improve adherence to WordPress coding standards.
+* Improve translatability of plugin.
+* Generally clean up code for better readability and clarity.
+* Eliminates all uses of `extract()` for clarity's sake.
+
+### 2.0.3 ###
+* Correct problem in WordPress 3.3 and higher that resulted in an empty taxonomy dropdown.
+* Remove all uses of PHP short tags.
+
+### 2.0.2 ###
+* Allow empty title in widget options. If empty, the `taxonomy_dropdown_widget_title` filter isn't run.
+
+### 2.0.1 ###
+* Fix fatal error in older WordPress versions resulting from PHP4 and PHP5 constructors existing in widget class.
+
+### 2.0.0.2 ###
+* Fix bug in post count threshold that resulted in no terms being listed.
+
+
+### 2.0.0.1 ###
+* Fix bug that appended cutoff indicators when unnecessary.
+
+### 2.0 ###
+* Completely rewritten plugin to use WordPress' newer Widgets API.
+* Drop support for WordPress 2.7 and earlier.
+* Add support for all public, non-hierarchical custom taxonomies, in addition to Post Tags.
+* Introduce new, more flexible function for manually generating dropdown menus.
+* Introduce options requested by the community, such as control over the default dropdown item.
+* Fixed persistent bugs in the include/exclude functionality.
+* Widget admin is translation-ready.
+
+### 1.7 ###
+* Replaced `TDW_direct()` and `makeTagDropdown()` with `generateTagDropdown()`.
+* Recoded entire plugin to simplify and clean up overall functionality.
+* Switched exclude functionality to use tag ids rather than tag slugs.
+* Added numerous additional options to the widget panel based on user response, as detailed below.
+* Added the ability to specify the indicator shown when a tag name is trimmed.
+* Added the ability to limit the number of tags shown.
+* Added the ability to specify the minimum number of posts a given tag must be associated with before it will show in the dropdown.
+* Added options for specifying the order tags are displayed in.
+* Added the ability to specify a list of tags to include in the dropdown, expanding on the existing ability to exclude certain tags.
+* Added the option to display tags which aren't associated with any posts.
+* Added the `TagDropdown_get_tags` filter to provide advanced users the ability to modify the arguments passed to WordPress' `get_tags` function. Using this filter, the trimming, trimming indicator, and count display settings are still obeyed.
+
+### 1.6 ###
+* Add `TDW_direct()` function.
+* Add count and exclusion options to new direct-implementation function (`TDW_direct()`).
+* Corrects two XHTML validation errors.
+
+### 1.5.2 ###
+* Unloads tag exclusion list upon deactivation.
+
+### 1.5.1 ###
+* Moved plugin pages to ethitter.com.
+
+### 1.5 ###
+* Added option to display number of posts within each tag.
+
+### 1.4 ###
+* Added option to exclude tags based on comma-separated list of slugs.
+
+### 1.3 ###
+* Rewrote certain widget elements for compatibility back to version 2.3.
+
+### 1.2 ###
+* Added function to remove plugin settings when deactivated.
+
+### 1.1 ###
+* Added the ability to trim tag names when calling the function directly.
+
+## Upgrade Notice ##
+
+### 2.3 ###
+Updated for WordPress 4.3. Removed PHP4-style widget constructor usage (https://make.wordpress.org/core/2015/07/02/deprecating-php4-style-constructors-in-wordpress-4-3/).
+
+### 2.2 ###
+Updated for WordPress 4.2. Only version 2.2 or higher should be used with WordPress 4.2 or higher, otherwise included/excluded terms may reappear in dropdowns. This is due to WordPress splitting shared terms, as detailed at https://make.wordpress.org/core/2015/02/16/taxonomy-term-splitting-in-4-2-a-developer-guide/.
+
+### 2.1 ###
+While no major functional changes are included in this release, the plugin itself is better-written and users are encouraged to upgrade. A set of filters are now applied to the dropdown and its components, for greater customizability.
+
+### 2.0.3 ###
+Corrects a problem in WordPress 3.3 and higher that resulted in an empty taxonomy dropdown. Also removes all uses of PHP short tags.
+
+### 2.0.2 ###
+Allows empty title in widget options. If empty, the `taxonomy_dropdown_widget_title` filter isn't run.
+
+### 2.0.1 ###
+Fixes a backwards-compatibility problem in the widget class that generated fatal errors in WordPress 3.0 and earlier.
+
+### 2.0.0.2 ###
+Fixes a minor bug in the post count threshold setting.
+
+### 2.0.0.1 ###
+Fixes minor bug that appended cutoff indicators when unnecessary.
+
+### 2.0 ###
+The plugin was renamed, completely rewritten, and drops support for WordPress 2.7 and earlier. Upgrading will delete all of your existing widgets; see the FAQ for an explanation. Review the changelog and FAQ for more information.
+
+### 1.7 ###
+This is a major revision to the Tag Dropdown Widget. Before upgrading, please be aware that both `TDW_direct()` and `makeTagDropdown()` are now deprecated functions. Additionally, tags can no longer be excluded based on slug. See changelog for full details.
+
+### 1.6 ###
+Replaces `makeTagDropdown()` with `TDW_direct()` function, adds post count and exclusion options to direct-implementation function. `makeTagDropdown()` function retained for backwards compatibility. Corrects two XHTML validation errors.
diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5ceac4b84b4d05ad6b3525cc9d8b2fd055c32554
--- /dev/null
+++ b/bin/install-wp-tests.sh
@@ -0,0 +1,155 @@
+#!/usr/bin/env bash
+
+if [ $# -lt 3 ]; then
+	echo "usage: $0 <db-name> <db-user> <db-pass> [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/languages/tag-dropdown-widget.pot b/languages/tag-dropdown-widget.pot
new file mode 100644
index 0000000000000000000000000000000000000000..8c4677eda98421fb81c5f1e7b7316d6b1d11ac7d
--- /dev/null
+++ b/languages/tag-dropdown-widget.pot
@@ -0,0 +1,167 @@
+# Copyright (C) 2019 Erick Hitter
+# This file is distributed under the same license as the Taxonomy Dropdown Widget package.
+msgid ""
+msgstr ""
+"Project-Id-Version: Taxonomy Dropdown Widget 2.3\n"
+"Report-Msgid-Bugs-To: "
+"https://wordpress.org/support/plugin/tag-dropdown-widget\n"
+"POT-Creation-Date: 2019-04-13 21:49:06+00:00\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2019-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"X-Generator: grunt-wp-i18n 0.5.4\n"
+"X-Poedit-KeywordsList: "
+"__;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;_nx_noop:1,2,3c;esc_"
+"attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;esc_html_x:1,2c;\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Country: United States\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-Basepath: ../\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-Bookmarks: \n"
+"X-Textdomain-Support: yes\n"
+
+#: tag-dropdown-widget.php:549
+msgid "Basic Settings"
+msgstr ""
+
+#: tag-dropdown-widget.php:552
+msgid "Taxonomy"
+msgstr ""
+
+#: tag-dropdown-widget.php:561
+msgid "Title:"
+msgstr ""
+
+#: tag-dropdown-widget.php:566
+msgid "Default dropdown item:"
+msgstr ""
+
+#: tag-dropdown-widget.php:570
+msgid "Order"
+msgstr ""
+
+#: tag-dropdown-widget.php:573
+msgid "Order terms by:"
+msgstr ""
+
+#: tag-dropdown-widget.php:576
+msgid "Name"
+msgstr ""
+
+#: tag-dropdown-widget.php:579
+msgid "Post count"
+msgstr ""
+
+#: tag-dropdown-widget.php:583
+msgid "Order terms:"
+msgstr ""
+
+#: tag-dropdown-widget.php:586
+msgid "Ascending"
+msgstr ""
+
+#: tag-dropdown-widget.php:589
+msgid "Descending"
+msgstr ""
+
+#: tag-dropdown-widget.php:592
+msgid "Term Display"
+msgstr ""
+
+#: tag-dropdown-widget.php:595
+msgid "Limit number of terms shown to:"
+msgstr ""
+
+#: tag-dropdown-widget.php:597
+msgid "Enter <strong>0</strong> for no limit."
+msgstr ""
+
+#: tag-dropdown-widget.php:601
+msgid "Trim long term names to <em>x</em> characters:</label>"
+msgstr ""
+
+#: tag-dropdown-widget.php:603
+msgid "Enter <strong>0</strong> to show full tag names."
+msgstr ""
+
+#: tag-dropdown-widget.php:607
+msgid "Indicator that term names are trimmed:"
+msgstr ""
+
+#: tag-dropdown-widget.php:609
+msgid "Leave blank to use an elipsis (&hellip;)."
+msgstr ""
+
+#: tag-dropdown-widget.php:614
+msgid "Include terms that aren't assigned to any objects (empty terms)."
+msgstr ""
+
+#: tag-dropdown-widget.php:619
+msgid "Display object (post) counts after term names."
+msgstr ""
+
+#: tag-dropdown-widget.php:622
+msgid "Include/Exclude Terms"
+msgstr ""
+
+#: tag-dropdown-widget.php:625
+msgid "Include/exclude terms:"
+msgstr ""
+
+#: tag-dropdown-widget.php:628
+msgid "Include only the term IDs listed below"
+msgstr ""
+
+#: tag-dropdown-widget.php:631
+msgid "Exclude the term IDs listed below"
+msgstr ""
+
+#: tag-dropdown-widget.php:635
+msgid "Term IDs to include/exclude based on above setting:"
+msgstr ""
+
+#: tag-dropdown-widget.php:637
+msgid "Enter comma-separated list of term IDs."
+msgstr ""
+
+#: tag-dropdown-widget.php:640
+msgid "Advanced"
+msgstr ""
+
+#: tag-dropdown-widget.php:643
+msgid "Show terms assigned to at least this many posts:"
+msgstr ""
+
+#: tag-dropdown-widget.php:645
+msgid "Set to <strong>0</strong> to display all terms matching the above criteria."
+msgstr ""
+
+#. Plugin Name of the plugin/theme
+msgid "Taxonomy Dropdown Widget"
+msgstr ""
+
+#. Plugin URI of the plugin/theme
+msgid "https://ethitter.com/plugins/taxonomy-dropdown-widget/"
+msgstr ""
+
+#. Description of the plugin/theme
+msgid ""
+"Creates a dropdown list of non-hierarchical taxonomies as an alternative to "
+"the term (tag) cloud. Widget provides numerous options to tailor the output "
+"to fit your site. Dropdown function can also be called directly for use "
+"outside of the widget. Formerly known as <strong><em>Tag Dropdown "
+"Widget</em></strong>."
+msgstr ""
+
+#. Author of the plugin/theme
+msgid "Erick Hitter"
+msgstr ""
+
+#. Author URI of the plugin/theme
+msgid "https://ethitter.com/"
+msgstr ""
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100755
index 0000000000000000000000000000000000000000..bcf0bc140ea09c3d9b6f483be8de117d583b55b9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "taxonomy-dropdown-widget",
+  "version": "0.1.0",
+  "main": "Gruntfile.js",
+  "author": "Erick Hitter",
+  "devDependencies": {
+    "grunt": "~0.4.5",
+    "grunt-wp-i18n": "~0.5.0",
+    "grunt-wp-readme-to-markdown": "~1.0.0"
+  }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..16a39027e72be2cf0a2656056074b6e6ed818be1
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<phpunit
+	bootstrap="tests/bootstrap.php"
+	backupGlobals="false"
+	colors="true"
+	convertErrorsToExceptions="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	>
+	<testsuites>
+		<testsuite>
+			<directory prefix="test-" suffix=".php">./tests/</directory>
+			<exclude>./tests/test-sample.php</exclude>
+		</testsuite>
+	</testsuites>
+</phpunit>
diff --git a/readme.txt b/readme.txt
index 283073e224a7491621ac6902508a7027538321b6..a00eba8c05b183bba57795415611bc0bbd5986bd 100644
--- a/readme.txt
+++ b/readme.txt
@@ -3,7 +3,7 @@ Contributors: ethitter
 Donate link: https://ethitter.com/donate/
 Tags: tag, tags, taxonomy, sidebar, widget, widgets, dropdown, drop down
 Requires at least: 2.8
-Tested up to: 4.6
+Tested up to: 5.2
 Stable tag: 2.3
 License: GPLv2 or later
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
diff --git a/tag-dropdown-widget.php b/tag-dropdown-widget.php
index 3d609bbd5013e93b7a72b78534e631cee260ac07..1139e730cd8544476b2fb8c596f432e57e51fc0d 100644
--- a/tag-dropdown-widget.php
+++ b/tag-dropdown-widget.php
@@ -546,10 +546,10 @@ class taxonomy_dropdown_widget extends WP_Widget {
 		}
 
 	?>
-		<h3><?php _e( 'Basic Settings' ); ?></h3>
+		<h3><?php _e( 'Basic Settings', 'taxonomy_dropdown_widget' ); ?></h3>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'taxonomy' ); ?>"><?php _e( 'Taxonomy' ); ?>:</label><br />
+			<label for="<?php echo $this->get_field_id( 'taxonomy' ); ?>"><?php _e( 'Taxonomy', 'taxonomy_dropdown_widget' ); ?>:</label><br />
 			<select name="<?php echo $this->get_field_name( 'taxonomy' ); ?>" id="<?php echo $this->get_field_id( 'taxonomy' ); ?>">
 				<?php foreach ( $taxonomies as $tax ) : ?>
 					<option value="<?php echo esc_attr( $tax->name ); ?>"<?php selected( $tax->name, $options['taxonomy'], true ); ?>><?php echo $tax->labels->name; ?></option>
@@ -558,91 +558,91 @@ class taxonomy_dropdown_widget extends WP_Widget {
 		</p>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'title' ); ?>" class="widefat code" id="<?php echo $this->get_field_id( 'title' ); ?>" value="<?php echo esc_attr( $options['title'] ); ?>" />
 		</p>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'select_name' ); ?>"><?php _e( 'Default dropdown item:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'select_name' ); ?>"><?php _e( 'Default dropdown item:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'select_name' ); ?>" class="widefat code" id="<?php echo $this->get_field_id( 'select_name' ); ?>" value="<?php echo esc_attr( $options['select_name'] ); ?>" />
 		</p>
 
-		<h3><?php _e( 'Order' ); ?></h3>
+		<h3><?php _e( 'Order', 'taxonomy_dropdown_widget' ); ?></h3>
 
 		<p>
-			<label><?php _e( 'Order terms by:' ); ?></label><br />
+			<label><?php _e( 'Order terms by:', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'orderby' ); ?>" value="name" id="<?php echo $this->get_field_name( 'order_name' ); ?>"<?php checked( $options['orderby'], 'name', true ); ?> />
-			<label for="<?php echo $this->get_field_name( 'order_name' ); ?>"><?php _e( 'Name' ); ?></label><br />
+			<label for="<?php echo $this->get_field_name( 'order_name' ); ?>"><?php _e( 'Name', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'orderby' ); ?>" value="count" id="<?php echo $this->get_field_name( 'order_count' ); ?>"<?php checked( $options['orderby'], 'count', true ); ?> />
-			<label for="<?php echo $this->get_field_name( 'order_count' ); ?>"><?php _e( 'Post count' ); ?></label>
+			<label for="<?php echo $this->get_field_name( 'order_count' ); ?>"><?php _e( 'Post count', 'taxonomy_dropdown_widget' ); ?></label>
 		</p>
 
 		<p>
-			<label><?php _e( 'Order terms:' ); ?></label><br />
+			<label><?php _e( 'Order terms:', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'order' ); ?>" value="ASC" id="<?php echo $this->get_field_name( 'order_asc' ); ?>"<?php checked( $options['order'], 'ASC', true ); ?> />
-			<label for="<?php echo $this->get_field_name( 'order_asc' ); ?>"><?php _e( 'Ascending' ); ?></label><br />
+			<label for="<?php echo $this->get_field_name( 'order_asc' ); ?>"><?php _e( 'Ascending', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'order' ); ?>" value="DESC" id="<?php echo $this->get_field_name( 'order_desc' ); ?>"<?php checked( $options['order'], 'DESC', true ); ?> />
-			<label for="<?php echo $this->get_field_name( 'order_desc' ); ?>"><?php _e( 'Descending' ); ?></label>
+			<label for="<?php echo $this->get_field_name( 'order_desc' ); ?>"><?php _e( 'Descending', 'taxonomy_dropdown_widget' ); ?></label>
 		</p>
 
-		<h3><?php _e( 'Term Display' ); ?></h3>
+		<h3><?php _e( 'Term Display', 'taxonomy_dropdown_widget' ); ?></h3>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'limit' ); ?>"><?php _e( 'Limit number of terms shown to:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'limit' ); ?>"><?php _e( 'Limit number of terms shown to:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'limit' ); ?>" id="<?php echo $this->get_field_id( 'limit' ); ?>" value="<?php echo intval( $options['limit'] ); ?>" size="3" /><br />
-			<span class="description"><small><?php _e( 'Enter <strong>0</strong> for no limit.' ); ?></small></span>
+			<span class="description"><small><?php _e( 'Enter <strong>0</strong> for no limit.', 'taxonomy_dropdown_widget' ); ?></small></span>
 		</p>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'max_name_length' ); ?>"><?php _e( 'Trim long term names to <em>x</em> characters:</label>' ); ?><br />
+			<label for="<?php echo $this->get_field_id( 'max_name_length' ); ?>"><?php _e( 'Trim long term names to <em>x</em> characters:</label>', 'taxonomy_dropdown_widget' ); ?><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'max_name_length' ); ?>" id="<?php echo $this->get_field_id( 'max_name_length' ); ?>" value="<?php echo intval( $options['max_name_length'] ); ?>" size="3" /><br />
-			<span class="description"><small><?php _e( 'Enter <strong>0</strong> to show full tag names.' ); ?></small></span>
+			<span class="description"><small><?php _e( 'Enter <strong>0</strong> to show full tag names.', 'taxonomy_dropdown_widget' ); ?></small></span>
 		</p>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'cutoff' ); ?>"><?php _e( 'Indicator that term names are trimmed:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'cutoff' ); ?>"><?php _e( 'Indicator that term names are trimmed:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'cutoff' ); ?>" id="<?php echo $this->get_field_id( 'cutoff' ); ?>" value="<?php echo esc_attr( $options['cutoff'] ); ?>" size="3" /><br />
-			<span class="description"><small><?php _e( 'Leave blank to use an elipsis (&hellip;).' ); ?></small></span>
+			<span class="description"><small><?php _e( 'Leave blank to use an elipsis (&hellip;).', 'taxonomy_dropdown_widget' ); ?></small></span>
 		</p>
 
 		<p>
 			<input type="checkbox" name="<?php echo $this->get_field_name( 'hide_empty' ); ?>" id="<?php echo $this->get_field_id( 'hide_empty' ); ?>"  value="0"<?php checked( false, $options['hide_empty'], true ); ?> />
-			<label for="<?php echo $this->get_field_id( 'hide_empty' ); ?>"><?php _e( 'Include terms that aren\'t assigned to any objects (empty terms).' ); ?></label>
+			<label for="<?php echo $this->get_field_id( 'hide_empty' ); ?>"><?php _e( 'Include terms that aren\'t assigned to any objects (empty terms).', 'taxonomy_dropdown_widget' ); ?></label>
 		</p>
 
 		<p>
 			<input type="checkbox" name="<?php echo $this->get_field_name( 'post_counts' ); ?>" id="<?php echo $this->get_field_id( 'post_counts' ); ?>"  value="1"<?php checked( true, $options['post_counts'], true ); ?> />
-			<label for="<?php echo $this->get_field_id( 'post_counts' ); ?>"><?php _e( 'Display object (post) counts after term names.' ); ?></label>
+			<label for="<?php echo $this->get_field_id( 'post_counts' ); ?>"><?php _e( 'Display object (post) counts after term names.', 'taxonomy_dropdown_widget' ); ?></label>
 		</p>
 
-		<h3><?php _e( 'Include/Exclude Terms' ); ?></h3>
+		<h3><?php _e( 'Include/Exclude Terms', 'taxonomy_dropdown_widget' ); ?></h3>
 
 		<p>
-			<label><?php _e( 'Include/exclude terms:' ); ?></label><br />
+			<label><?php _e( 'Include/exclude terms:', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'incexc' ); ?>" value="include" id="<?php echo $this->get_field_id( 'include' ); ?>"<?php checked( $options['incexc'], 'include', true ); ?> />
-			<label for="<?php echo $this->get_field_id( 'include' ); ?>"><?php _e( 'Include only the term IDs listed below' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'include' ); ?>"><?php _e( 'Include only the term IDs listed below', 'taxonomy_dropdown_widget' ); ?></label><br />
 
 			<input type="radio" name="<?php echo $this->get_field_name( 'incexc' ); ?>" value="exclude" id="<?php echo $this->get_field_id( 'exclude' ); ?>"<?php checked( $options['incexc'], 'exclude', true ); ?> />
-			<label for="<?php echo $this->get_field_id( 'exclude' ); ?>"><?php _e( 'Exclude the term IDs listed below' ); ?></label>
+			<label for="<?php echo $this->get_field_id( 'exclude' ); ?>"><?php _e( 'Exclude the term IDs listed below', 'taxonomy_dropdown_widget' ); ?></label>
 		</p>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'incexc_ids' ); ?>"><?php _e( 'Term IDs to include/exclude based on above setting:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'incexc_ids' ); ?>"><?php _e( 'Term IDs to include/exclude based on above setting:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'incexc_ids' ); ?>" class="widefat code" id="<?php echo $this->get_field_id( 'incexc_ids' ); ?>" value="<?php echo esc_attr( implode( ', ', $options['incexc_ids'] ) ); ?>" /><br />
-			<span class="description"><small><?php _e( 'Enter comma-separated list of term IDs.' ); ?></small></span>
+			<span class="description"><small><?php _e( 'Enter comma-separated list of term IDs.', 'taxonomy_dropdown_widget' ); ?></small></span>
 		</p>
 
-		<h3><?php _e( 'Advanced' ); ?></h3>
+		<h3><?php _e( 'Advanced', 'taxonomy_dropdown_widget' ); ?></h3>
 
 		<p>
-			<label for="<?php echo $this->get_field_id( 'threshold' ); ?>"><?php _e( 'Show terms assigned to at least this many posts:' ); ?></label><br />
+			<label for="<?php echo $this->get_field_id( 'threshold' ); ?>"><?php _e( 'Show terms assigned to at least this many posts:', 'taxonomy_dropdown_widget' ); ?></label><br />
 			<input type="text" name="<?php echo $this->get_field_name( 'threshold' ); ?>" id="<?php echo $this->get_field_id( 'threshold' ); ?>" value="<?php echo intval( $options['threshold'] ); ?>" size="3" /><br />
-			<span class="description"><small><?php _e( 'Set to <strong>0</strong> to display all terms matching the above criteria.' ); ?></small></span>
+			<span class="description"><small><?php _e( 'Set to <strong>0</strong> to display all terms matching the above criteria.', 'taxonomy_dropdown_widget' ); ?></small></span>
 		</p>
 
 	<?php
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100755
index 0000000000000000000000000000000000000000..3c9b1b00ed0f9c3a14707045d58fb8c5f73541ce
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * PHPUnit bootstrap file
+ *
+ * @package Taxonomy_Dropdown_Widget
+ */
+
+$_tests_dir = getenv( 'WP_TESTS_DIR' );
+
+if ( ! $_tests_dir ) {
+	$_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
+}
+
+if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) {
+	echo "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh ?" . PHP_EOL; // WPCS: XSS ok.
+	exit( 1 );
+}
+
+// Give access to tests_add_filter() function.
+require_once $_tests_dir . '/includes/functions.php';
+
+/**
+ * Manually load the plugin being tested.
+ */
+function _manually_load_plugin() {
+	require dirname( dirname( __FILE__ ) ) . '/tag-dropdown-widget.php';
+}
+tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
+
+// Start up the WP testing environment.
+require $_tests_dir . '/includes/bootstrap.php';
diff --git a/tests/test-sample.php b/tests/test-sample.php
new file mode 100755
index 0000000000000000000000000000000000000000..c60aed6d5f3bfd5e0debb913bd3c1418dcc6df60
--- /dev/null
+++ b/tests/test-sample.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Class SampleTest
+ *
+ * @package Taxonomy_Dropdown_Widget
+ */
+
+/**
+ * Sample test case.
+ */
+class SampleTest extends WP_UnitTestCase {
+
+	/**
+	 * A single example test.
+	 */
+	public function test_sample() {
+		// Replace this with some actual testing code.
+		$this->assertTrue( true );
+	}
+}