diff --git a/inc/class-wp-revisions-control.php b/inc/class-wp-revisions-control.php
index a78bb40dd59bcd1f8559732ca64d0f9f4ad48c95..ead01d01a1ce1882b836a94913399534351a6be3 100644
--- a/inc/class-wp-revisions-control.php
+++ b/inc/class-wp-revisions-control.php
@@ -505,10 +505,12 @@ class WP_Revisions_Control {
 	 * @return array
 	 */
 	private function get_settings() {
-		if ( empty( self::$settings ) ) {
-			$post_types = $this->get_post_types();
+		static $hash = null;
+
+		$settings = get_option( $this->settings_section, array() );
 
-			$settings = get_option( $this->settings_section, array() );
+		if ( empty( self::$settings ) || $hash !== $this->hash_settings( $settings ) ) {
+			$post_types = $this->get_post_types();
 
 			if ( ! is_array( $settings ) ) {
 				$settings = array();
@@ -525,11 +527,23 @@ class WP_Revisions_Control {
 			}
 
 			self::$settings = $merged_settings;
+			$hash           = $this->hash_settings( self::$settings );
 		}
 
 		return self::$settings;
 	}
 
+	/**
+	 * Hash settings to limit re-parsing.
+	 *
+	 * @param array $settings Settings array.
+	 * @return string
+	 */
+	private function hash_settings( $settings ) {
+		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
+		return md5( serialize( $settings ) );
+	}
+
 	/**
 	 * Retrieve array of supported post types and their labels.
 	 *
@@ -572,7 +586,7 @@ class WP_Revisions_Control {
 		$_post   = new WP_Post( (object) array( 'post_type' => $post_type ) );
 		$to_keep = wp_revisions_to_keep( $_post );
 
-		if ( $blank_for_all && -1 === $to_keep ) {
+		if ( $blank_for_all && ( -1 === $to_keep || '-1' === $to_keep ) ) {
 			return '';
 		} else {
 			return (int) $to_keep;
@@ -588,7 +602,7 @@ class WP_Revisions_Control {
 	private function get_post_revisions_to_keep( $post_id ) {
 		$to_keep = get_post_meta( $post_id, $this->meta_key_limit, true );
 
-		if ( -1 === $to_keep || empty( $to_keep ) ) {
+		if ( empty( $to_keep ) || -1 === $to_keep || '-1' === $to_keep ) {
 			$to_keep = '';
 		} else {
 			$to_keep = (int) $to_keep;
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index bf3d9cf10eeabc2e477142d0a6714834e3d51512..1ed0283d6fa461d12676a99c6309d8449dea5cbe 100755
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -19,6 +19,13 @@ if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) {
 // Give access to tests_add_filter() function.
 require_once $_tests_dir . '/includes/functions.php';
 
+/**
+ * Stub admin-only function not needed for testing.
+ */
+if ( ! function_exists( 'post_revisions_meta_box' ) ) {
+	function post_revisions_meta_box() {}
+}
+
 /**
  * Manually load the plugin being tested.
  */
diff --git a/tests/test-misc.php b/tests/test-misc.php
new file mode 100755
index 0000000000000000000000000000000000000000..ec01dbb7b3c39255f6d06b043616eea66432795d
--- /dev/null
+++ b/tests/test-misc.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Test miscellaneous methods.
+ *
+ * @package WP_Revisions_Control
+ */
+
+/**
+ * Class TestMisc.
+ */
+class TestMisc extends WP_UnitTestCase {
+	/**
+	 * Test settings sanitization.
+	 */
+	public function test_settings_sanitization() {
+		$input = array(
+			'minus_ten'    => -10,
+			'minus_one'    => -1,
+			'zero'         => 0,
+			'one'          => 1,
+			'thirty'       => 30,
+			'empty_string' => '',
+			'bool_false'   => false,
+			'bool_true'    => true,
+			'null'         => null,
+		);
+
+		$expected = array(
+			'minus_ten'    => -1,
+			'minus_one'    => -1,
+			'zero'         => 0,
+			'one'          => 1,
+			'thirty'       => 30,
+			'empty_string' => -1,
+			'bool_false'   => -1,
+			'bool_true'    => 1,
+			'null'         => -1,
+		);
+
+		$sanitized = WP_Revisions_Control::get_instance()->sanitize_options( $input );
+
+		$this->assertEquals(
+			$expected,
+			$sanitized,
+			'Failed to assert that options were sanitized correctly.'
+		);
+	}
+}
diff --git a/tests/test-purges.php b/tests/test-purges.php
index 77f9386f4dfd3c3f58c88ea3bf2f45c72afaabed..3bfd4e84f7882e16e46874e4741b330d9e95b191 100755
--- a/tests/test-purges.php
+++ b/tests/test-purges.php
@@ -9,13 +9,6 @@
  * Class TestPurges.
  */
 class TestPurges extends WP_UnitTestCase {
-	/**
-	 * Plugin slug used in many settings etc.
-	 *
-	 * @var string
-	 */
-	protected static $settings_section = 'wp_revisions_control';
-
 	/**
 	 * Plugin's limit meta key.
 	 *
diff --git a/tests/test-ui.php b/tests/test-ui.php
new file mode 100755
index 0000000000000000000000000000000000000000..23688c1287ba0c2ee59133d64cf9fc714b0b4d89
--- /dev/null
+++ b/tests/test-ui.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Test UI methods.
+ *
+ * @package WP_Revisions_Control
+ */
+
+/**
+ * Class TestUI.
+ */
+class TestUI extends WP_UnitTestCase {
+	/**
+	 * Plugin slug used in many settings etc.
+	 *
+	 * @var string
+	 */
+	protected static $settings_section = 'wp_revisions_control';
+
+	/**
+	 * Plugin's limit meta key.
+	 *
+	 * @var string
+	 */
+	protected static $meta_key = '_wp_rev_ctl_limit';
+
+	/**
+	 * Test meta box with no meta set.
+	 */
+	public function test_no_meta() {
+		$post_id    = $this->factory->post->create();
+
+		ob_start();
+		WP_Revisions_Control::get_instance()->revisions_meta_box( get_post( $post_id ) );
+		$meta_box = ob_get_clean();
+
+		$this->assertContains(
+			'value=""',
+			$meta_box,
+			'Failed to assert that meta box has no value when no setting exists.'
+		);
+	}
+
+	/**
+	 * Test meta box with no limit set.
+	 */
+	public function test_no_limit() {
+		$post_id    = $this->factory->post->create();
+		update_post_meta( $post_id, static::$meta_key, -1 );
+
+		ob_start();
+		WP_Revisions_Control::get_instance()->revisions_meta_box( get_post( $post_id ) );
+		$meta_box = ob_get_clean();
+
+		$this->assertContains(
+			'value=""',
+			$meta_box,
+			'Failed to assert that meta box has no value when no limit exists.'
+		);
+	}
+
+	/**
+	 * Test settings-field output when no options are set.
+	 */
+	public function test_settings_fields_no_options() {
+		ob_start();
+		WP_Revisions_Control::get_instance()->field_post_type( array( 'post_type' => 'post' ) );
+		$post_field = ob_get_clean();
+
+		ob_start();
+		WP_Revisions_Control::get_instance()->field_post_type( array( 'post_type' => 'page' ) );
+		$page_field = ob_get_clean();
+
+		$name_format  = 'name="%1$s[%2$s]"';
+		$value_format = 'value=""';
+
+		$this->assertContains(
+			sprintf(
+				$name_format,
+				static::$settings_section,
+				'post'
+			),
+			$post_field,
+			'Failed to assert that post field had correct name for post type.'
+		);
+
+		$this->assertContains(
+			sprintf(
+				$name_format,
+				static::$settings_section,
+				'page'
+			),
+			$page_field,
+			'Failed to assert that page field had correct name for post type.'
+		);
+
+		$this->assertContains(
+			$value_format,
+			$post_field,
+			'Failed to assert that post field had correct value.'
+		);
+
+		$this->assertContains(
+			$value_format,
+			$page_field,
+			'Failed to assert that page field had correct value.'
+		);
+	}
+
+	/**
+	 * Test settings-field output when options are set.
+	 */
+	public function test_settings_fields_with_options() {
+		$value = 12;
+
+		update_option(
+			static::$settings_section,
+			[
+				'post' => $value,
+			]
+		);
+
+		ob_start();
+		WP_Revisions_Control::get_instance()->field_post_type( array( 'post_type' => 'post' ) );
+		$post_field = ob_get_clean();
+
+		$name_format  = 'name="%1$s[%2$s]"';
+		$value_format = 'value="%1$s"';
+
+		$this->assertContains(
+			sprintf(
+				$name_format,
+				static::$settings_section,
+				'post'
+			),
+			$post_field,
+			'Failed to assert that post field had correct name for post type.'
+		);
+
+		$this->assertContains(
+			sprintf(
+				$value_format,
+				$value
+			),
+			$post_field,
+			'Failed to assert that post field had correct value.'
+		);
+	}
+
+	/**
+	 * Test settings-field output when options are set.
+	 */
+	public function test_settings_fields_with_options_keep_all() {
+		$value = -1;
+
+		update_option(
+			static::$settings_section,
+			[
+				'post' => $value,
+			]
+		);
+
+		ob_start();
+		WP_Revisions_Control::get_instance()->field_post_type( array( 'post_type' => 'post' ) );
+		$post_field = ob_get_clean();
+
+		$name_format  = 'name="%1$s[%2$s]"';
+		$value_format = 'value=""';
+
+		$this->assertContains(
+			sprintf(
+				$name_format,
+				static::$settings_section,
+				'post'
+			),
+			$post_field,
+			'Failed to assert that post field had correct name for post type.'
+		);
+
+		$this->assertContains(
+			sprintf(
+				$value_format,
+				$value
+			),
+			$post_field,
+			'Failed to assert that post field had correct value.'
+		);
+	}
+}