Index: src/wp-includes/formatting.php
===================================================================
--- src/wp-includes/formatting.php	(revision 27515)
+++ src/wp-includes/formatting.php	(working copy)
@@ -73,27 +73,44 @@
 		$static_characters = array_merge( array( '---', ' -- ', '--', ' - ', 'xn&#8211;', '...', '``', '\'\'', ' (tm)' ), $cockney );
 		$static_replacements = array_merge( array( $em_dash, ' ' . $em_dash . ' ', $en_dash, ' ' . $en_dash . ' ', 'xn--', '&#8230;', $opening_quote, $closing_quote, ' &#8482;' ), $cockneyreplace );
 
+		// Pattern-based replacements of characters.
 		$dynamic = array();
-		if ( "'" != $apos ) {
-			$dynamic[ '/\'(\d\d(?:&#8217;|\')?s)/' ] = $apos . '$1'; // '99's
-			$dynamic[ '/\'(\d)/'                   ] = $apos . '$1'; // '99
-		}
+		$spaces = wp_spaces_regexp();
+
+		// '99s (apostrophe)
+		if ( "'" != $apos )
+			$dynamic[ '/\'(?=\d)/' ] = $apos;
+
+		// Single quote at start, or preceded by (, {, <, [, ", or spaces.
 		if ( "'" != $opening_single_quote )
-			$dynamic[ '/(\s|\A|[([{<]|")\'/'       ] = '$1' . $opening_single_quote; // opening single quote, even after (, {, <, [
+			$dynamic[ '/(?<=\A|[([{<"]|' . $spaces . ')\'/' ] = $opening_single_quote;
+
+		// 9" (double prime)
 		if ( '"' != $double_prime )
-			$dynamic[ '/(\d)"/'                    ] = '$1' . $double_prime; // 9" (double prime)
+			$dynamic[ '/(?<=\d)"/' ] = $double_prime;
+
+		// 9' (prime)
 		if ( "'" != $prime )
-			$dynamic[ '/(\d)\'/'                   ] = '$1' . $prime; // 9' (prime)
+			$dynamic[ '/(?<=\d)\'/' ] = $prime;
+
+		// Apostrophe in a word.  No spaces or double primes.
 		if ( "'" != $apos )
-			$dynamic[ '/(\S)\'([^\'\s])/'          ] = '$1' . $apos . '$2'; // apostrophe in a word
+			$dynamic[ '/(?<!' . $spaces . ')\'(?!\'|' . $spaces . ')/' ] = $apos;
+
+		// Double quote at start, or preceded by (, {, <, [, or spaces, and not followed by spaces.
 		if ( '"' != $opening_quote )
-			$dynamic[ '/(\s|\A|[([{<])"(?!\s)/'    ] = '$1' . $opening_quote . '$2'; // opening double quote, even after (, {, <, [
+			$dynamic[ '/(?<=\A|[([{<]|' . $spaces . ')"(?!' . $spaces . ')/' ] = $opening_quote;
+
+		// Any remaining double quotes.
 		if ( '"' != $closing_quote )
-			$dynamic[ '/"(\s|\S|\Z)/'              ] = $closing_quote . '$1'; // closing double quote
+			$dynamic[ '/"/' ] = $closing_quote;
+
+		// Single quotes followed by spaces or a period.
 		if ( "'" != $closing_single_quote )
-			$dynamic[ '/\'([\s.]|\Z)/'             ] = $closing_single_quote . '$1'; // closing single quote
+			$dynamic[ '/\'(?=\Z|\.|' . $spaces . ')/' ] = $closing_single_quote; 
 
-		$dynamic[ '/\b(\d+)x(\d+)\b/'              ] = '$1&#215;$2'; // 9x9 (times)
+		// 9x9 (times)
+		$dynamic[ '/\b(\d+)x(\d+)\b/' ] = '$1&#215;$2';
 
 		$dynamic_characters = array_keys( $dynamic );
 		$dynamic_replacements = array_values( $dynamic );
@@ -318,11 +335,12 @@
 	}
 
 	$tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
+	$spaces = wp_spaces_regexp();
 
 	$pattern =
 		  '/'
 		. '<p>'                              // Opening paragraph
-		. '\\s*+'                            // Optional leading whitespace
+		. '(?:' . $spaces . ')*+'            // Optional leading whitespace
 		. '('                                // 1: The shortcode
 		.     '\\['                          // Opening bracket
 		.     "($tagregexp)"                 // 2: Shortcode name
@@ -347,7 +365,7 @@
 		.         ')?'
 		.     ')'
 		. ')'
-		. '\\s*+'                            // optional trailing whitespace
+		. '(?:' . $spaces . ')*+'            // optional trailing whitespace
 		. '<\\/p>'                           // closing paragraph
 		. '/s';
 
@@ -1865,7 +1883,7 @@
 	 */
 	$src_url = apply_filters( 'smilies_src', includes_url( "images/smilies/$img" ), $img, site_url() );
 
-	return sprintf( ' <img src="%s" alt="%s" class="wp-smiley" /> ', esc_url( $src_url ), esc_attr( $smiley ) );
+	return sprintf( '<img src="%s" alt="%s" class="wp-smiley" />', esc_url( $src_url ), esc_attr( $smiley ) );
 }
 
 /**
@@ -3781,3 +3799,24 @@
 
 	return false;
 }
+
+/**
+ * Returns the regexp for common whitespace characters.
+ *
+ * By default, spaces include new lines, tabs, nbsp entities, and the UTF-8 nbsp.
+ * This is designed to replace the PCRE \s sequence.  In ticket #22692, that
+ * sequence was found to be unreliable due to random inclusion of the A0 byte.
+ *
+ * @since 3.9.0
+ *
+ * @return string The spaces regexp.
+ */
+function wp_spaces_regexp() {
+	static $spaces;
+
+	if (empty($spaces)) {
+		$spaces = apply_filters( 'wp_spaces_regexp', '[\r\n\t ]|\xC2\xA0|&nbsp;' );
+	}
+
+	return $spaces;
+}
Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 27515)
+++ src/wp-includes/functions.php	(working copy)
@@ -2560,8 +2560,11 @@
 	 */
 	krsort($wpsmiliestrans);
 
-	$wp_smiliessearch = '/((?:\s|^)';
+	$spaces = wp_spaces_regexp();
 
+	// Begin first "subpattern"
+	$wp_smiliessearch = '/(?<=' . $spaces . '|^)';
+
 	$subchar = '';
 	foreach ( (array) $wpsmiliestrans as $smiley => $img ) {
 		$firstchar = substr($smiley, 0, 1);
@@ -2570,7 +2573,8 @@
 		// new subpattern?
 		if ($firstchar != $subchar) {
 			if ($subchar != '') {
-				$wp_smiliessearch .= ')(?=\s|$))|((?:\s|^)'; ;
+				$wp_smiliessearch .= ')(?=' . $spaces . '|$)';  // End previous "subpattern"
+				$wp_smiliessearch .= '|(?<=' . $spaces . '|^)'; // Begin another "subpattern"
 			}
 			$subchar = $firstchar;
 			$wp_smiliessearch .= preg_quote($firstchar, '/') . '(?:';
@@ -2580,7 +2584,7 @@
 		$wp_smiliessearch .= preg_quote($rest, '/');
 	}
 
-	$wp_smiliessearch .= ')(?=\s|$))/m';
+	$wp_smiliessearch .= ')(?=' . $spaces . '|$)/m';
 
 }
 
Index: tests/phpunit/tests/formatting/Smilies.php
===================================================================
--- tests/phpunit/tests/formatting/Smilies.php	(revision 27515)
+++ tests/phpunit/tests/formatting/Smilies.php	(working copy)
@@ -275,4 +275,48 @@
 
 		$wpsmiliestrans = $orig_trans; // reset original translations array
 	}
-}
\ No newline at end of file
+
+	/**
+	 * Check that $wp_smiliessearch pattern will match smilies
+	 * between spaces, but never capture those spaces.
+	 *
+	 * Further check that spaces aren't randomly deleted
+	 * or added when replacing the text with an image.
+	 *
+	 * @ticket 22692
+	 */
+	function test_spaces_around_smilies() {
+		$nbsp = "\xC2\xA0";
+
+		$input  = array();
+		$output = array();
+
+		$input[]  = 'My test :) smile';
+		$output[] = array('test <img ', 'alt=":)"', ' /> smile');
+
+		$input[]  = 'My test ;) smile';
+		$output[] = array('test <img ', 'alt=";)"', ' /> smile');
+
+		$input[]  = 'My test &nbsp;:)&nbsp;smile';
+		$output[] = array('test &nbsp;<img ', 'alt=":)"', ' />&nbsp;smile');
+
+		$input[]  = 'My test &nbsp;;)&nbsp;smile';
+		$output[] = array('test &nbsp;<img ', 'alt=";)"', ' />&nbsp;smile');
+
+		$input[]  = "My test {$nbsp}:){$nbsp}smile";
+		$output[] = array("test {$nbsp}<img ", 'alt=":)"', " />{$nbsp}smile");
+
+		$input[]  = "My test {$nbsp};){$nbsp}smile";
+		$output[] = array("test {$nbsp}<img ", 'alt=";)"', " />{$nbsp}smile");
+
+		foreach($input as $key => $in) {
+			$result = convert_smilies( $in );
+			foreach($output[$key] as $k => $out) {
+
+				// Each output element must appear in the results.
+				$this->assertContains( $out[$k], $result );
+
+			}
+		}
+	}
+}
Index: tests/phpunit/tests/formatting/WPTexturize.php
===================================================================
--- tests/phpunit/tests/formatting/WPTexturize.php	(revision 27515)
+++ tests/phpunit/tests/formatting/WPTexturize.php	(working copy)
@@ -1,197 +1,248 @@
-<?php
-
-/**
- * @group formatting
- */
-class Tests_Formatting_WPTexturize extends WP_UnitTestCase {
-	function test_dashes() {
-		$this->assertEquals('Hey &#8212; boo?', wptexturize('Hey -- boo?'));
-		$this->assertEquals('<a href="http://xx--xx">Hey &#8212; boo?</a>', wptexturize('<a href="http://xx--xx">Hey -- boo?</a>'));
-	}
-
-	function test_disable() {
-		$this->assertEquals('<pre>---</pre>', wptexturize('<pre>---</pre>'));
-		$this->assertEquals('[a]a&#8211;b[code]---[/code]a&#8211;b[/a]', wptexturize('[a]a--b[code]---[/code]a--b[/a]'));
-		$this->assertEquals('<pre><code></code>--</pre>', wptexturize('<pre><code></code>--</pre>'));
-
-		$this->assertEquals('<code>---</code>', wptexturize('<code>---</code>'));
-
-		$this->assertEquals('<code>href="baba"</code> &#8220;baba&#8221;', wptexturize('<code>href="baba"</code> "baba"'));
-
-		$enabled_tags_inside_code = '<code>curl -s <a href="http://x/">baba</a> | grep sfive | cut -d "\"" -f 10 &gt; topmp3.txt</code>';
-		$this->assertEquals($enabled_tags_inside_code, wptexturize($enabled_tags_inside_code));
-
-		$double_nest = '<pre>"baba"<code>"baba"<pre></pre></code>"baba"</pre>';
-		$this->assertEquals($double_nest, wptexturize($double_nest));
-
-		$invalid_nest = '<pre></code>"baba"</pre>';
-		$this->assertEquals($invalid_nest, wptexturize($invalid_nest));
-
-	}
-
-	//WP Ticket #1418
-	function test_bracketed_quotes_1418() {
-		$this->assertEquals('(&#8220;test&#8221;)', wptexturize('("test")'));
-		$this->assertEquals('(&#8216;test&#8217;)', wptexturize("('test')"));
-		$this->assertEquals('(&#8217;twas)', wptexturize("('twas)"));
-	}
-
-	//WP Ticket #3810
-	function test_bracketed_quotes_3810() {
-		$this->assertEquals('A dog (&#8220;Hubertus&#8221;) was sent out.', wptexturize('A dog ("Hubertus") was sent out.'));
-	}
-
-	//WP Ticket #4539
-	function test_basic_quotes() {
-		$this->assertEquals('test&#8217;s', wptexturize('test\'s'));
-		$this->assertEquals('test&#8217;s', wptexturize('test\'s'));
-
-		$this->assertEquals('&#8216;quoted&#8217;', wptexturize('\'quoted\''));
-		$this->assertEquals('&#8220;quoted&#8221;', wptexturize('"quoted"'));
-
-		$this->assertEquals('space before &#8216;quoted&#8217; space after', wptexturize('space before \'quoted\' space after'));
-		$this->assertEquals('space before &#8220;quoted&#8221; space after', wptexturize('space before "quoted" space after'));
-
-		$this->assertEquals('(&#8216;quoted&#8217;)', wptexturize('(\'quoted\')'));
-		$this->assertEquals('{&#8220;quoted&#8221;}', wptexturize('{"quoted"}'));
-
-		$this->assertEquals('&#8216;qu(ot)ed&#8217;', wptexturize('\'qu(ot)ed\''));
-		$this->assertEquals('&#8220;qu{ot}ed&#8221;', wptexturize('"qu{ot}ed"'));
-
-		$this->assertEquals(' &#8216;test&#8217;s quoted&#8217; ', wptexturize(' \'test\'s quoted\' '));
-		$this->assertEquals(' &#8220;test&#8217;s quoted&#8221; ', wptexturize(' "test\'s quoted" '));
-	}
-
-	/**
-	 * @ticket 4539
-	 * @ticket 15241
-	 */
-	function test_full_sentences_with_unmatched_single_quotes() {
-		$this->assertEquals(
-			'That means every moment you&#8217;re working on something without it being in the public it&#8217;s actually dying.',
-			wptexturize("That means every moment you're working on something without it being in the public it's actually dying.")
-		);
-	}
-
-	/**
-	 * @ticket 4539
-	 */
-	function test_quotes() {
-		$this->assertEquals('&#8220;Quoted String&#8221;', wptexturize('"Quoted String"'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link and a period</a>&#8221;.', wptexturize('Here is "<a href="http://example.com">a test with a link and a period</a>".'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221; and a space.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>" and a space.'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a> and some text quoted&#8221;', wptexturize('Here is "<a href="http://example.com">a test with a link</a> and some text quoted"'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;, and a comma.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>", and a comma.'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;; and a semi-colon.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"; and a semi-colon.'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;- and a dash.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"- and a dash.'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;&#8230; and ellipses.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"... and ellipses.'));
-		$this->assertEquals('Here is &#8220;a test <a href="http://example.com">with a link</a>&#8221;.', wptexturize('Here is "a test <a href="http://example.com">with a link</a>".'));
-		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;and a work stuck to the end.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"and a work stuck to the end.'));
-		$this->assertEquals('A test with a finishing number, &#8220;like 23&#8221;.', wptexturize('A test with a finishing number, "like 23".'));
-		$this->assertEquals('A test with a number, &#8220;like 62&#8221;, is nice to have.', wptexturize('A test with a number, "like 62", is nice to have.'));
-	}
-
-	/**
-	 * @ticket 4539
-	 */
-	function test_quotes_before_s() {
-		$this->assertEquals('test&#8217;s', wptexturize("test's"));
-		$this->assertEquals('&#8216;test&#8217;s', wptexturize("'test's"));
-		$this->assertEquals('&#8216;test&#8217;s&#8217;', wptexturize("'test's'"));
-		$this->assertEquals('&#8216;string&#8217;', wptexturize("'string'"));
-		$this->assertEquals('&#8216;string&#8217;s&#8217;', wptexturize("'string's'"));
-	}
-
-	/**
-	 * @ticket 4539
-	 */
-	function test_quotes_before_numbers() {
-		$this->assertEquals('Class of &#8217;99', wptexturize("Class of '99"));
-		$this->assertEquals('Class of &#8217;99&#8217;s', wptexturize("Class of '99's"));
-		$this->assertEquals('&#8216;Class of &#8217;99&#8217;', wptexturize("'Class of '99'"));
-		$this->assertEquals('&#8216;Class of &#8217;99&#8217;s&#8217;', wptexturize("'Class of '99's'"));
-		$this->assertEquals('&#8216;Class of &#8217;99&#8217;s&#8217;', wptexturize("'Class of '99&#8217;s'"));
-		$this->assertEquals('&#8220;Class of 99&#8221;', wptexturize("\"Class of 99\""));
-		$this->assertEquals('&#8220;Class of &#8217;99&#8221;', wptexturize("\"Class of '99\""));
-	}
-
-	function test_quotes_after_numbers() {
-		$this->assertEquals('Class of &#8217;99', wptexturize("Class of '99"));
-	}
-
-	/**
-	 * @ticket 4539
-	 * @ticket 15241
-	 */
-	function test_other_html() {
-		$this->assertEquals('&#8216;<strong>', wptexturize("'<strong>"));
-		$this->assertEquals('&#8216;<strong>Quoted Text</strong>&#8217;,', wptexturize("'<strong>Quoted Text</strong>',"));
-		$this->assertEquals('&#8220;<strong>Quoted Text</strong>&#8221;,', wptexturize('"<strong>Quoted Text</strong>",'));
-	}
-
-	function test_x() {
-		$this->assertEquals('14&#215;24', wptexturize("14x24"));
-	}
-
-	function test_minutes_seconds() {
-		$this->assertEquals('9&#8242;', wptexturize('9\''));
-		$this->assertEquals('9&#8243;', wptexturize("9\""));
-
-		$this->assertEquals('a 9&#8242; b', wptexturize('a 9\' b'));
-		$this->assertEquals('a 9&#8243; b', wptexturize("a 9\" b"));
-
-		$this->assertEquals('&#8220;a 9&#8242; b&#8221;', wptexturize('"a 9\' b"'));
-		$this->assertEquals('&#8216;a 9&#8243; b&#8217;', wptexturize("'a 9\" b'"));
-	}
-
-	/**
-	 * @ticket 8775
-	 */
-	function test_wptexturize_quotes_around_numbers() {
-		$this->assertEquals('&#8220;12345&#8221;', wptexturize('"12345"'));
-		$this->assertEquals('&#8216;12345&#8217;', wptexturize('\'12345\''));
-		$this->assertEquals('&#8220;a 9&#8242; plus a &#8216;9&#8217;, maybe a 9&#8242; &#8216;9&#8217; &#8221;', wptexturize('"a 9\' plus a \'9\', maybe a 9\' \'9\' "'));
-		$this->assertEquals('<p>&#8216;99<br />&#8216;123&#8217;<br />&#8217;tis<br />&#8216;s&#8217;</p>', wptexturize('<p>\'99<br />\'123\'<br />\'tis<br />\'s\'</p>'));
-	}
-
-	/**
-	 * @ticket 8912
-	 */
-	function test_wptexturize_html_comments() {
-		$this->assertEquals('<!--[if !IE]>--><!--<![endif]-->', wptexturize('<!--[if !IE]>--><!--<![endif]-->'));
-		$this->assertEquals('<!--[if !IE]>"a 9\' plus a \'9\', maybe a 9\' \'9\' "<![endif]-->', wptexturize('<!--[if !IE]>"a 9\' plus a \'9\', maybe a 9\' \'9\' "<![endif]-->'));
-		$this->assertEquals('<ul><li>Hello.</li><!--<li>Goodbye.</li>--></ul>', wptexturize('<ul><li>Hello.</li><!--<li>Goodbye.</li>--></ul>'));
-	}
-
-	/**
-	 * @ticket 4539
-	 * @ticket 15241
-	 */
-	function test_entity_quote_cuddling() {
-		$this->assertEquals('&nbsp;&#8220;Testing&#8221;', wptexturize('&nbsp;"Testing"'));
-		$this->assertEquals('&#38;&#8220;Testing&#8221;', wptexturize('&#38;"Testing"'));
-	}
-
-	/**
-	 * @ticket 22823
-	 */
-	function test_apostrophes_before_primes() {
-		$this->assertEquals( 'WordPress 3.5&#8217;s release date', wptexturize( "WordPress 3.5's release date" ) );
-	}
-
-	/**
-	 * @ticket 23185
-	 */
-	function test_spaces_around_hyphens() {
-		$this->assertEquals( ' &#8211; ', wptexturize( ' - ' ) ); 
-		$this->assertEquals( '&nbsp;&#8211;&nbsp;', wptexturize( '&nbsp;-&nbsp;' ) );
-		$this->assertEquals( ' &#8211;&nbsp;', wptexturize( ' -&nbsp;' ) );
-		$this->assertEquals( '&nbsp;&#8211; ', wptexturize( '&nbsp;- ') );
-
-		$this->assertEquals( ' &#8212; ', wptexturize( ' -- ' ) ); 
-		$this->assertEquals( '&nbsp;&#8212;&nbsp;', wptexturize( '&nbsp;--&nbsp;' ) );
-		$this->assertEquals( ' &#8212;&nbsp;', wptexturize( ' --&nbsp;' ) );
-		$this->assertEquals( '&nbsp;&#8212; ', wptexturize( '&nbsp;-- ') );
-	}
-}
+<?php
+
+/**
+ * @group formatting
+ */
+class Tests_Formatting_WPTexturize extends WP_UnitTestCase {
+	function test_dashes() {
+		$this->assertEquals('Hey &#8212; boo?', wptexturize('Hey -- boo?'));
+		$this->assertEquals('<a href="http://xx--xx">Hey &#8212; boo?</a>', wptexturize('<a href="http://xx--xx">Hey -- boo?</a>'));
+	}
+
+	function test_disable() {
+		$this->assertEquals('<pre>---</pre>', wptexturize('<pre>---</pre>'));
+		$this->assertEquals('[a]a&#8211;b[code]---[/code]a&#8211;b[/a]', wptexturize('[a]a--b[code]---[/code]a--b[/a]'));
+		$this->assertEquals('<pre><code></code>--</pre>', wptexturize('<pre><code></code>--</pre>'));
+
+		$this->assertEquals('<code>---</code>', wptexturize('<code>---</code>'));
+
+		$this->assertEquals('<code>href="baba"</code> &#8220;baba&#8221;', wptexturize('<code>href="baba"</code> "baba"'));
+
+		$enabled_tags_inside_code = '<code>curl -s <a href="http://x/">baba</a> | grep sfive | cut -d "\"" -f 10 &gt; topmp3.txt</code>';
+		$this->assertEquals($enabled_tags_inside_code, wptexturize($enabled_tags_inside_code));
+
+		$double_nest = '<pre>"baba"<code>"baba"<pre></pre></code>"baba"</pre>';
+		$this->assertEquals($double_nest, wptexturize($double_nest));
+
+		$invalid_nest = '<pre></code>"baba"</pre>';
+		$this->assertEquals($invalid_nest, wptexturize($invalid_nest));
+
+	}
+
+	//WP Ticket #1418
+	function test_bracketed_quotes_1418() {
+		$this->assertEquals('(&#8220;test&#8221;)', wptexturize('("test")'));
+		$this->assertEquals('(&#8216;test&#8217;)', wptexturize("('test')"));
+		$this->assertEquals('(&#8217;twas)', wptexturize("('twas)"));
+	}
+
+	//WP Ticket #3810
+	function test_bracketed_quotes_3810() {
+		$this->assertEquals('A dog (&#8220;Hubertus&#8221;) was sent out.', wptexturize('A dog ("Hubertus") was sent out.'));
+	}
+
+	//WP Ticket #4539
+	function test_basic_quotes() {
+		$this->assertEquals('test&#8217;s', wptexturize('test\'s'));
+		$this->assertEquals('test&#8217;s', wptexturize('test\'s'));
+
+		$this->assertEquals('&#8216;quoted&#8217;', wptexturize('\'quoted\''));
+		$this->assertEquals('&#8220;quoted&#8221;', wptexturize('"quoted"'));
+
+		$this->assertEquals('space before &#8216;quoted&#8217; space after', wptexturize('space before \'quoted\' space after'));
+		$this->assertEquals('space before &#8220;quoted&#8221; space after', wptexturize('space before "quoted" space after'));
+
+		$this->assertEquals('(&#8216;quoted&#8217;)', wptexturize('(\'quoted\')'));
+		$this->assertEquals('{&#8220;quoted&#8221;}', wptexturize('{"quoted"}'));
+
+		$this->assertEquals('&#8216;qu(ot)ed&#8217;', wptexturize('\'qu(ot)ed\''));
+		$this->assertEquals('&#8220;qu{ot}ed&#8221;', wptexturize('"qu{ot}ed"'));
+
+		$this->assertEquals(' &#8216;test&#8217;s quoted&#8217; ', wptexturize(' \'test\'s quoted\' '));
+		$this->assertEquals(' &#8220;test&#8217;s quoted&#8221; ', wptexturize(' "test\'s quoted" '));
+	}
+
+	/**
+	 * @ticket 4539
+	 * @ticket 15241
+	 */
+	function test_full_sentences_with_unmatched_single_quotes() {
+		$this->assertEquals(
+			'That means every moment you&#8217;re working on something without it being in the public it&#8217;s actually dying.',
+			wptexturize("That means every moment you're working on something without it being in the public it's actually dying.")
+		);
+	}
+
+	/**
+	 * @ticket 4539
+	 */
+	function test_quotes() {
+		$this->assertEquals('&#8220;Quoted String&#8221;', wptexturize('"Quoted String"'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link and a period</a>&#8221;.', wptexturize('Here is "<a href="http://example.com">a test with a link and a period</a>".'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221; and a space.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>" and a space.'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a> and some text quoted&#8221;', wptexturize('Here is "<a href="http://example.com">a test with a link</a> and some text quoted"'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;, and a comma.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>", and a comma.'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;; and a semi-colon.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"; and a semi-colon.'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;- and a dash.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"- and a dash.'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;&#8230; and ellipses.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"... and ellipses.'));
+		$this->assertEquals('Here is &#8220;a test <a href="http://example.com">with a link</a>&#8221;.', wptexturize('Here is "a test <a href="http://example.com">with a link</a>".'));
+		$this->assertEquals('Here is &#8220;<a href="http://example.com">a test with a link</a>&#8221;and a work stuck to the end.', wptexturize('Here is "<a href="http://example.com">a test with a link</a>"and a work stuck to the end.'));
+		$this->assertEquals('A test with a finishing number, &#8220;like 23&#8221;.', wptexturize('A test with a finishing number, "like 23".'));
+		$this->assertEquals('A test with a number, &#8220;like 62&#8221;, is nice to have.', wptexturize('A test with a number, "like 62", is nice to have.'));
+	}
+
+	/**
+	 * @ticket 4539
+	 */
+	function test_quotes_before_s() {
+		$this->assertEquals('test&#8217;s', wptexturize("test's"));
+		$this->assertEquals('&#8216;test&#8217;s', wptexturize("'test's"));
+		$this->assertEquals('&#8216;test&#8217;s&#8217;', wptexturize("'test's'"));
+		$this->assertEquals('&#8216;string&#8217;', wptexturize("'string'"));
+		$this->assertEquals('&#8216;string&#8217;s&#8217;', wptexturize("'string's'"));
+	}
+
+	/**
+	 * @ticket 4539
+	 */
+	function test_quotes_before_numbers() {
+		$this->assertEquals('Class of &#8217;99', wptexturize("Class of '99"));
+		$this->assertEquals('Class of &#8217;99&#8217;s', wptexturize("Class of '99's"));
+		$this->assertEquals('&#8216;Class of &#8217;99&#8217;', wptexturize("'Class of '99'"));
+		$this->assertEquals('&#8216;Class of &#8217;99&#8217;s&#8217;', wptexturize("'Class of '99's'"));
+		$this->assertEquals('&#8216;Class of &#8217;99&#8217;s&#8217;', wptexturize("'Class of '99&#8217;s'"));
+		$this->assertEquals('&#8220;Class of 99&#8221;', wptexturize("\"Class of 99\""));
+		$this->assertEquals('&#8220;Class of &#8217;99&#8221;', wptexturize("\"Class of '99\""));
+	}
+
+	function test_quotes_after_numbers() {
+		$this->assertEquals('Class of &#8217;99', wptexturize("Class of '99"));
+	}
+
+	/**
+	 * @ticket 4539
+	 * @ticket 15241
+	 */
+	function test_other_html() {
+		$this->assertEquals('&#8216;<strong>', wptexturize("'<strong>"));
+		$this->assertEquals('&#8216;<strong>Quoted Text</strong>&#8217;,', wptexturize("'<strong>Quoted Text</strong>',"));
+		$this->assertEquals('&#8220;<strong>Quoted Text</strong>&#8221;,', wptexturize('"<strong>Quoted Text</strong>",'));
+	}
+
+	function test_x() {
+		$this->assertEquals('14&#215;24', wptexturize("14x24"));
+	}
+
+	function test_minutes_seconds() {
+		$this->assertEquals('9&#8242;', wptexturize('9\''));
+		$this->assertEquals('9&#8243;', wptexturize("9\""));
+
+		$this->assertEquals('a 9&#8242; b', wptexturize('a 9\' b'));
+		$this->assertEquals('a 9&#8243; b', wptexturize("a 9\" b"));
+
+		$this->assertEquals('&#8220;a 9&#8242; b&#8221;', wptexturize('"a 9\' b"'));
+		$this->assertEquals('&#8216;a 9&#8243; b&#8217;', wptexturize("'a 9\" b'"));
+	}
+
+	/**
+	 * @ticket 8775
+	 */
+	function test_wptexturize_quotes_around_numbers() {
+		$this->assertEquals('&#8220;12345&#8221;', wptexturize('"12345"'));
+		$this->assertEquals('&#8216;12345&#8217;', wptexturize('\'12345\''));
+		$this->assertEquals('&#8220;a 9&#8242; plus a &#8216;9&#8217;, maybe a 9&#8242; &#8216;9&#8217; &#8221;', wptexturize('"a 9\' plus a \'9\', maybe a 9\' \'9\' "'));
+		$this->assertEquals('<p>&#8216;99<br />&#8216;123&#8217;<br />&#8217;tis<br />&#8216;s&#8217;</p>', wptexturize('<p>\'99<br />\'123\'<br />\'tis<br />\'s\'</p>'));
+	}
+
+	/**
+	 * @ticket 8912
+	 */
+	function test_wptexturize_html_comments() {
+		$this->assertEquals('<!--[if !IE]>--><!--<![endif]-->', wptexturize('<!--[if !IE]>--><!--<![endif]-->'));
+		$this->assertEquals('<!--[if !IE]>"a 9\' plus a \'9\', maybe a 9\' \'9\' "<![endif]-->', wptexturize('<!--[if !IE]>"a 9\' plus a \'9\', maybe a 9\' \'9\' "<![endif]-->'));
+		$this->assertEquals('<ul><li>Hello.</li><!--<li>Goodbye.</li>--></ul>', wptexturize('<ul><li>Hello.</li><!--<li>Goodbye.</li>--></ul>'));
+	}
+
+	/**
+	 * @ticket 4539
+	 * @ticket 15241
+	 */
+	function test_entity_quote_cuddling() {
+		$this->assertEquals('&nbsp;&#8220;Testing&#8221;', wptexturize('&nbsp;"Testing"'));
+		$this->assertEquals('&#38;&#8220;Testing&#8221;', wptexturize('&#38;"Testing"'));
+	}
+
+	/**
+	 * @ticket 22823
+	 */
+	function test_apostrophes_before_primes() {
+		$this->assertEquals( 'WordPress 3.5&#8217;s release date', wptexturize( "WordPress 3.5's release date" ) );
+	}
+
+	/**
+	 * @ticket 23185
+	 */
+	function test_spaces_around_hyphens() {
+		$this->assertEquals( ' &#8211; ', wptexturize( ' - ' ) ); 
+		$this->assertEquals( '&nbsp;&#8211;&nbsp;', wptexturize( '&nbsp;-&nbsp;' ) );
+		$this->assertEquals( ' &#8211;&nbsp;', wptexturize( ' -&nbsp;' ) );
+		$this->assertEquals( '&nbsp;&#8211; ', wptexturize( '&nbsp;- ') );
+
+		$this->assertEquals( ' &#8212; ', wptexturize( ' -- ' ) ); 
+		$this->assertEquals( '&nbsp;&#8212;&nbsp;', wptexturize( '&nbsp;--&nbsp;' ) );
+		$this->assertEquals( ' &#8212;&nbsp;', wptexturize( ' --&nbsp;' ) );
+		$this->assertEquals( '&nbsp;&#8212; ', wptexturize( '&nbsp;-- ') );
+	}
+
+	/**
+	 * @ticket 22692
+	 */
+	function test_spaces_around_quotes() {
+		$nbsp = "\xC2\xA0";
+		$pi   = "\xCE\xA0";
+
+		// This should never happen, even if the desired output changes some day.
+
+		$problem_input  = "$nbsp\"A";
+		$problem_output = "$nbsp&#8221;A";
+
+		$this->assertNotEquals( $problem_output, wptexturize( $problem_input ) );
+
+		// These are desirable outputs for the current design.
+
+		$input  = array();
+		$output = array();
+
+		$input[]  = "stop. $nbsp\"A quote after 2 spaces.\"";
+		$output[] = "stop. $nbsp&#8220;A quote after 2 spaces.&#8221;";
+
+		$input[]  = "stop.$nbsp$nbsp\"A quote after 2 spaces.\"";
+		$output[] = "stop.$nbsp$nbsp&#8220;A quote after 2 spaces.&#8221;";
+
+		$input[]  = "stop. $nbsp'A quote after 2 spaces.'";
+		$output[] = "stop. $nbsp&#8216;A quote after 2 spaces.&#8217;";
+
+		$input[]  = "stop.$nbsp$nbsp'A quote after 2 spaces.'";
+		$output[] = "stop.$nbsp$nbsp&#8216;A quote after 2 spaces.&#8217;";
+
+		$input[]  = "stop. &nbsp;\"A quote after 2 spaces.\"";
+		$output[] = "stop. &nbsp;&#8220;A quote after 2 spaces.&#8221;";
+
+		$input[]  = "stop.&nbsp;&nbsp;\"A quote after 2 spaces.\"";
+		$output[] = "stop.&nbsp;&nbsp;&#8220;A quote after 2 spaces.&#8221;";
+
+		$input[]  = "stop. &nbsp;'A quote after 2 spaces.'";
+		$output[] = "stop. &nbsp;&#8216;A quote after 2 spaces.&#8217;";
+
+		$input[]  = "stop.&nbsp;&nbsp;'A quote after 2 spaces.'";
+		$output[] = "stop.&nbsp;&nbsp;&#8216;A quote after 2 spaces.&#8217;";
+
+		$input[]  = "Contraction: $pi's";
+		$output[] = "Contraction: $pi&#8217;s";
+
+		foreach($input as $key => $in) {
+			$this->assertEquals( $output[$key], wptexturize( $in ) );
+		}
+	}
+}
Index: tests/phpunit/tests/shortcode.php
===================================================================
--- tests/phpunit/tests/shortcode.php	(revision 27515)
+++ tests/phpunit/tests/shortcode.php	(working copy)
@@ -1,376 +1,397 @@
-<?php
-/**
- * @group shortcode
- */
-class Tests_Shortcode extends WP_UnitTestCase {
-
-	protected $shortcodes = array( 'test-shortcode-tag', 'footag', 'bartag', 'baztag', 'dumptag', 'hyphen', 'hyphen-foo', 'hyphen-foo-bar' );
-
-	function setUp() {
-		parent::setUp();
-
-		foreach ( $this->shortcodes as $shortcode )
-			add_shortcode( $shortcode, array( $this, '_shortcode_' . str_replace( '-', '_', $shortcode ) ) );
-
-		$this->atts = null;
-		$this->content = null;
-		$this->tagname = null;
-
-	}
-
-	function tearDown() {
-		global $shortcode_tags;
-		parent::tearDown();
-		foreach ( $this->shortcodes as $shortcode )
-			unset( $shortcode_tags[ $shortcode ] );
-	}
-
-	function _shortcode_test_shortcode_tag( $atts, $content = null, $tagname = null ) {
-		$this->atts = $atts;
-		$this->content = $content;
-		$this->tagname = $tagname;
-		$this->filter_atts_out = null;
-		$this->filter_atts_pairs = null;
-		$this->filter_atts_atts = null;
-	}
-
-	// [footag foo="bar"]
-	function _shortcode_footag( $atts ) {
-		return @"foo = {$atts['foo']}";
-	}
-
-	// [bartag foo="bar"]
-	function _shortcode_bartag( $atts ) {
-		extract(shortcode_atts(array(
-			'foo' => 'no foo',
-			'baz' => 'default baz',
-		), $atts, 'bartag'));
-
-		return "foo = {$foo}";
-	}
-
-	// [baztag]content[/baztag]
-	function _shortcode_baztag( $atts, $content = '' ) {
-		return 'content = '.do_shortcode($content);
-	}
-
-	function _shortcode_dumptag( $atts ) {
-		$out = '';
-		foreach ($atts as $k=>$v)
-			$out .= "$k = $v\n";
-		return $out;
-	}
-
-	function _shortcode_hyphen() {
-		return __FUNCTION__;
-	}
-
-	function _shortcode_hyphen_foo() {
-		return __FUNCTION__;
-	}
-
-	function _shortcode_hyphen_foo_bar() {
-		return __FUNCTION__;
-	}
-
-	function test_noatts() {
-		do_shortcode('[test-shortcode-tag /]');
-		$this->assertEquals( '', $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_one_att() {
-		do_shortcode('[test-shortcode-tag foo="asdf" /]');
-		$this->assertEquals( array('foo' => 'asdf'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_not_a_tag() {
-		$out = do_shortcode('[not-a-shortcode-tag]');
-		$this->assertEquals( '[not-a-shortcode-tag]', $out );
-	}
-
-	/**
-	 * @ticket 17657
-	 */
-	function test_tag_hyphen_not_tag() {
-		$out = do_shortcode( '[dumptag-notreal]' );
-		$this->assertEquals( '[dumptag-notreal]', $out );
-	}
-
-	function test_tag_underscore_not_tag() {
-		$out = do_shortcode( '[dumptag_notreal]' );
-		$this->assertEquals( '[dumptag_notreal]', $out );
-	}
-
-	function test_tag_not_tag() {
-		$out = do_shortcode( '[dumptagnotreal]' );
-		$this->assertEquals( '[dumptagnotreal]', $out );
-	}
-
-	/**
-	 * @ticket 17657
-	 */
-	function test_tag_hyphen() {
- 		$this->assertEquals( '_shortcode_hyphen', do_shortcode( '[hyphen]' ) );
- 		$this->assertEquals( '_shortcode_hyphen_foo', do_shortcode( '[hyphen-foo]' ) );
- 		$this->assertEquals( '_shortcode_hyphen_foo_bar', do_shortcode( '[hyphen-foo-bar]' ) );
-		$this->assertEquals( '[hyphen-baz]', do_shortcode( '[hyphen-baz]' ) );
-		$this->assertEquals( '[hyphen-foo-bar-baz]', do_shortcode( '[hyphen-foo-bar-baz]' ) );
-	}
-
-	function test_two_atts() {
-		do_shortcode('[test-shortcode-tag foo="asdf" bar="bing" /]');
-		$this->assertEquals( array('foo' => 'asdf', 'bar' => 'bing'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_noatts_enclosing() {
-		do_shortcode('[test-shortcode-tag]content[/test-shortcode-tag]');
-		$this->assertEquals( '', $this->atts );
-		$this->assertEquals( 'content', $this->content );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_one_att_enclosing() {
-		do_shortcode('[test-shortcode-tag foo="bar"]content[/test-shortcode-tag]');
-		$this->assertEquals( array('foo' => 'bar'), $this->atts );
-		$this->assertEquals( 'content', $this->content );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_two_atts_enclosing() {
-		do_shortcode('[test-shortcode-tag foo="bar" baz="bing"]content[/test-shortcode-tag]');
-		$this->assertEquals( array('foo' => 'bar', 'baz' => 'bing'), $this->atts );
-		$this->assertEquals( 'content', $this->content );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_unclosed() {
-		$out = do_shortcode('[test-shortcode-tag]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( '', $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_positional_atts_num() {
-		$out = do_shortcode('[test-shortcode-tag 123]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( array(0=>'123'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_positional_atts_url() {
-		$out = do_shortcode('[test-shortcode-tag http://www.youtube.com/watch?v=eBGIQ7ZuuiU]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( array(0=>'http://www.youtube.com/watch?v=eBGIQ7ZuuiU'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_positional_atts_quotes() {
-		$out = do_shortcode('[test-shortcode-tag "something in quotes" "something else"]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( array(0=>'something in quotes', 1=>'something else'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_positional_atts_mixed() {
-		$out = do_shortcode('[test-shortcode-tag 123 http://wordpress.com/ 0 "foo" bar]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( array(0=>'123', 1=>'http://wordpress.com/', 2=>'0', 3=>'foo', 4=>'bar'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_positional_and_named_atts() {
-		$out = do_shortcode('[test-shortcode-tag 123 url=http://wordpress.com/ foo bar="baz"]');
-		$this->assertEquals( '', $out );
-		$this->assertEquals( array(0=>'123', 'url' => 'http://wordpress.com/', 1=>'foo', 'bar' => 'baz'), $this->atts );
-		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
-	}
-
-	function test_footag_default() {
-		$out = do_shortcode('[footag]');
-		$this->assertEquals('foo = ', $out);
-	}
-
-	function test_footag_val() {
-		$val = rand_str();
-		$out = do_shortcode('[footag foo="'.$val.'"]');
-		$this->assertEquals('foo = '.$val, $out);
-	}
-
-	function test_nested_tags() {
-		$out = do_shortcode('[baztag][dumptag abc="foo" def=123 http://wordpress.com/][/baztag]');
-		$expected = "content = abc = foo\ndef = 123\n0 = http://wordpress.com\n";
-		$this->assertEquals($expected, $out);
-	}
-
-	/**
-	 * @ticket 6518
-	 */
-	function test_tag_escaped() {
-		$out = do_shortcode('[[footag]] [[bartag foo="bar"]]');
-		$this->assertEquals('[footag] [bartag foo="bar"]', $out);
-
-		$out = do_shortcode('[[footag /]] [[bartag foo="bar" /]]');
-		$this->assertEquals('[footag /] [bartag foo="bar" /]', $out);
-
-		$out = do_shortcode('[[baztag foo="bar"]the content[/baztag]]');
-		$this->assertEquals('[baztag foo="bar"]the content[/baztag]', $out);
-
-		// double escaped
-		$out = do_shortcode('[[[footag]]] [[[bartag foo="bar"]]]');
-		$this->assertEquals('[[footag]] [[bartag foo="bar"]]', $out);
-	}
-
-	function test_tag_not_escaped() {
-		// these have square brackets on either end but aren't actually escaped
-		$out = do_shortcode('[[footag] [bartag foo="bar"]]');
-		$this->assertEquals('[foo =  foo = bar]', $out);
-
-		$out = do_shortcode('[[footag /] [bartag foo="bar" /]]');
-		$this->assertEquals('[foo =  foo = bar]', $out);
-
-		$out = do_shortcode('[[baztag foo="bar"]the content[/baztag]');
-		$this->assertEquals('[content = the content', $out);
-
-		$out = do_shortcode('[[not-a-tag]]');
-		$this->assertEquals('[[not-a-tag]]', $out);
-
-		$out = do_shortcode('[[[footag] [bartag foo="bar"]]]');
-		$this->assertEquals('[[foo =  foo = bar]]', $out);
-	}
-
-	function test_mixed_tags() {
-		$in = <<<EOF
-So this is a post with [footag foo="some stuff"] and a bunch of tags.
-
-[bartag]
-
-[baztag]
-Here's some content
-on more than one line
-[/baztag]
-
-[bartag foo=1] [baztag] [footag foo="2"] [baztag]
-
-[baztag]
-more content
-[/baztag]
-
-EOF;
-		$expected = <<<EOF
-So this is a post with foo = some stuff and a bunch of tags.
-
-foo = no foo
-
-content =
-Here's some content
-on more than one line
-
-
-foo = 1 content =  foo = 2 content =
-content =
-more content
-
-EOF;
-		$out = do_shortcode($in);
-		$this->assertEquals(strip_ws($expected), strip_ws($out));
-	}
-
-	/**
-	 * @ticket 6562
-	 */
-	function test_utf8_whitespace_1() {
-		// NO-BREAK SPACE: U+00A0
-		do_shortcode("[test-shortcode-tag foo=\"bar\" \xC2\xA0baz=\"123\"]");
-		$this->assertEquals( array('foo' => 'bar', 'baz' => '123'), $this->atts );
-		$this->assertEquals( '', $this->content );
-	}
-
-	/**
-	 * @ticket 6562
-	 */
-	function test_utf8_whitespace_2() {
-		// ZERO WIDTH SPACE: U+200B
-		do_shortcode("[test-shortcode-tag foo=\"bar\" \xE2\x80\x8Babc=\"def\"]");
-		$this->assertEquals( array('foo' => 'bar', 'abc' => 'def'), $this->atts );
-		$this->assertEquals( '', $this->content );
-	}
-
-	/**
-	 * @ticket 14050
-	 */
-	function test_shortcode_unautop() {
-		// a blank line is added at the end, so test with it already there
-		$test_string = "[footag]\n";
-		$this->assertEquals( $test_string, shortcode_unautop( wpautop( $test_string ) ) );
-	}
-
-	/**
-	 * @ticket 14050
-	 */
-	function test_multiple_shortcode_unautop() {
-		// a blank line is added at the end, so test with it already there
-		$test_string = "[footag]\n[footag]\n";
-		$actual = shortcode_unautop( wpautop( $test_string ) );
-		$this->assertEquals( $test_string, $actual );
-	}
-
-	/**
-	 * @ticket 10326
-	 */
-	function test_strip_shortcodes() {
-		$this->assertEquals('before', strip_shortcodes('before[gallery]'));
-		$this->assertEquals('after', strip_shortcodes('[gallery]after'));
-		$this->assertEquals('beforeafter', strip_shortcodes('before[gallery]after'));
-	}
-
-
-	// Store passed in shortcode_atts_{$shortcode} args
-	function _filter_atts( $out, $pairs, $atts ) {
-		$this->filter_atts_out = $out;
-		$this->filter_atts_pairs = $pairs;
-		$this->filter_atts_atts = $atts;
-		return $out;
-	}
-
-	// Filter shortcode atts in various ways
-	function _filter_atts2( $out, $pairs, $atts ) {
-		// If foo attribute equals "foo1", change it to be default value
-		if ( isset( $out['foo'] ) && 'foo1' == $out['foo'] )
-			$out['foo'] = $pairs['foo'];
-
-		// If baz attribute is set, remove it
-		if ( isset( $out['baz'] ) )
-			unset( $out['baz'] );
-
-		$this->filter_atts_out = $out;
-		return $out;
-	}
-
-	function test_shortcode_atts_filter_passes_original_arguments() {
-		add_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts' ), 10, 3 );
-
-		do_shortcode('[bartag foo="foo1" /]');
-		$this->assertEquals( array( 'foo' => 'foo1', 'baz' => 'default baz' ), $this->filter_atts_out );
-		$this->assertEquals( array( 'foo' => 'no foo', 'baz' => 'default baz' ), $this->filter_atts_pairs );
-		$this->assertEquals( array( 'foo' => 'foo1' ), $this->filter_atts_atts );
-
-		remove_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts' ), 10, 3 );
-	}
-
-	function test_shortcode_atts_filtering() {
-		add_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts2' ), 10, 3 );
-
-		$out = do_shortcode('[bartag foo="foo1" baz="baz1" /]');
-		$this->assertEquals( array( 'foo' => 'no foo' ), $this->filter_atts_out );
-		$this->assertEquals( 'foo = no foo', $out );
-
-		$out = do_shortcode('[bartag foo="foo2" /]');
-		$this->assertEquals( 'foo = foo2', $out );
-
-		remove_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts2' ), 10, 3 );
-	}
-
-}
+<?php
+/**
+ * @group shortcode
+ */
+class Tests_Shortcode extends WP_UnitTestCase {
+
+	protected $shortcodes = array( 'test-shortcode-tag', 'footag', 'bartag', 'baztag', 'dumptag', 'hyphen', 'hyphen-foo', 'hyphen-foo-bar' );
+
+	function setUp() {
+		parent::setUp();
+
+		foreach ( $this->shortcodes as $shortcode )
+			add_shortcode( $shortcode, array( $this, '_shortcode_' . str_replace( '-', '_', $shortcode ) ) );
+
+		$this->atts = null;
+		$this->content = null;
+		$this->tagname = null;
+
+	}
+
+	function tearDown() {
+		global $shortcode_tags;
+		parent::tearDown();
+		foreach ( $this->shortcodes as $shortcode )
+			unset( $shortcode_tags[ $shortcode ] );
+	}
+
+	function _shortcode_test_shortcode_tag( $atts, $content = null, $tagname = null ) {
+		$this->atts = $atts;
+		$this->content = $content;
+		$this->tagname = $tagname;
+		$this->filter_atts_out = null;
+		$this->filter_atts_pairs = null;
+		$this->filter_atts_atts = null;
+	}
+
+	// [footag foo="bar"]
+	function _shortcode_footag( $atts ) {
+		return @"foo = {$atts['foo']}";
+	}
+
+	// [bartag foo="bar"]
+	function _shortcode_bartag( $atts ) {
+		extract(shortcode_atts(array(
+			'foo' => 'no foo',
+			'baz' => 'default baz',
+		), $atts, 'bartag'));
+
+		return "foo = {$foo}";
+	}
+
+	// [baztag]content[/baztag]
+	function _shortcode_baztag( $atts, $content = '' ) {
+		return 'content = '.do_shortcode($content);
+	}
+
+	function _shortcode_dumptag( $atts ) {
+		$out = '';
+		foreach ($atts as $k=>$v)
+			$out .= "$k = $v\n";
+		return $out;
+	}
+
+	function _shortcode_hyphen() {
+		return __FUNCTION__;
+	}
+
+	function _shortcode_hyphen_foo() {
+		return __FUNCTION__;
+	}
+
+	function _shortcode_hyphen_foo_bar() {
+		return __FUNCTION__;
+	}
+
+	function test_noatts() {
+		do_shortcode('[test-shortcode-tag /]');
+		$this->assertEquals( '', $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_one_att() {
+		do_shortcode('[test-shortcode-tag foo="asdf" /]');
+		$this->assertEquals( array('foo' => 'asdf'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_not_a_tag() {
+		$out = do_shortcode('[not-a-shortcode-tag]');
+		$this->assertEquals( '[not-a-shortcode-tag]', $out );
+	}
+
+	/**
+	 * @ticket 17657
+	 */
+	function test_tag_hyphen_not_tag() {
+		$out = do_shortcode( '[dumptag-notreal]' );
+		$this->assertEquals( '[dumptag-notreal]', $out );
+	}
+
+	function test_tag_underscore_not_tag() {
+		$out = do_shortcode( '[dumptag_notreal]' );
+		$this->assertEquals( '[dumptag_notreal]', $out );
+	}
+
+	function test_tag_not_tag() {
+		$out = do_shortcode( '[dumptagnotreal]' );
+		$this->assertEquals( '[dumptagnotreal]', $out );
+	}
+
+	/**
+	 * @ticket 17657
+	 */
+	function test_tag_hyphen() {
+ 		$this->assertEquals( '_shortcode_hyphen', do_shortcode( '[hyphen]' ) );
+ 		$this->assertEquals( '_shortcode_hyphen_foo', do_shortcode( '[hyphen-foo]' ) );
+ 		$this->assertEquals( '_shortcode_hyphen_foo_bar', do_shortcode( '[hyphen-foo-bar]' ) );
+		$this->assertEquals( '[hyphen-baz]', do_shortcode( '[hyphen-baz]' ) );
+		$this->assertEquals( '[hyphen-foo-bar-baz]', do_shortcode( '[hyphen-foo-bar-baz]' ) );
+	}
+
+	function test_two_atts() {
+		do_shortcode('[test-shortcode-tag foo="asdf" bar="bing" /]');
+		$this->assertEquals( array('foo' => 'asdf', 'bar' => 'bing'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_noatts_enclosing() {
+		do_shortcode('[test-shortcode-tag]content[/test-shortcode-tag]');
+		$this->assertEquals( '', $this->atts );
+		$this->assertEquals( 'content', $this->content );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_one_att_enclosing() {
+		do_shortcode('[test-shortcode-tag foo="bar"]content[/test-shortcode-tag]');
+		$this->assertEquals( array('foo' => 'bar'), $this->atts );
+		$this->assertEquals( 'content', $this->content );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_two_atts_enclosing() {
+		do_shortcode('[test-shortcode-tag foo="bar" baz="bing"]content[/test-shortcode-tag]');
+		$this->assertEquals( array('foo' => 'bar', 'baz' => 'bing'), $this->atts );
+		$this->assertEquals( 'content', $this->content );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_unclosed() {
+		$out = do_shortcode('[test-shortcode-tag]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( '', $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_positional_atts_num() {
+		$out = do_shortcode('[test-shortcode-tag 123]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( array(0=>'123'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_positional_atts_url() {
+		$out = do_shortcode('[test-shortcode-tag http://www.youtube.com/watch?v=eBGIQ7ZuuiU]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( array(0=>'http://www.youtube.com/watch?v=eBGIQ7ZuuiU'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_positional_atts_quotes() {
+		$out = do_shortcode('[test-shortcode-tag "something in quotes" "something else"]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( array(0=>'something in quotes', 1=>'something else'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_positional_atts_mixed() {
+		$out = do_shortcode('[test-shortcode-tag 123 http://wordpress.com/ 0 "foo" bar]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( array(0=>'123', 1=>'http://wordpress.com/', 2=>'0', 3=>'foo', 4=>'bar'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_positional_and_named_atts() {
+		$out = do_shortcode('[test-shortcode-tag 123 url=http://wordpress.com/ foo bar="baz"]');
+		$this->assertEquals( '', $out );
+		$this->assertEquals( array(0=>'123', 'url' => 'http://wordpress.com/', 1=>'foo', 'bar' => 'baz'), $this->atts );
+		$this->assertEquals( 'test-shortcode-tag', $this->tagname );
+	}
+
+	function test_footag_default() {
+		$out = do_shortcode('[footag]');
+		$this->assertEquals('foo = ', $out);
+	}
+
+	function test_footag_val() {
+		$val = rand_str();
+		$out = do_shortcode('[footag foo="'.$val.'"]');
+		$this->assertEquals('foo = '.$val, $out);
+	}
+
+	function test_nested_tags() {
+		$out = do_shortcode('[baztag][dumptag abc="foo" def=123 http://wordpress.com/][/baztag]');
+		$expected = "content = abc = foo\ndef = 123\n0 = http://wordpress.com\n";
+		$this->assertEquals($expected, $out);
+	}
+
+	/**
+	 * @ticket 6518
+	 */
+	function test_tag_escaped() {
+		$out = do_shortcode('[[footag]] [[bartag foo="bar"]]');
+		$this->assertEquals('[footag] [bartag foo="bar"]', $out);
+
+		$out = do_shortcode('[[footag /]] [[bartag foo="bar" /]]');
+		$this->assertEquals('[footag /] [bartag foo="bar" /]', $out);
+
+		$out = do_shortcode('[[baztag foo="bar"]the content[/baztag]]');
+		$this->assertEquals('[baztag foo="bar"]the content[/baztag]', $out);
+
+		// double escaped
+		$out = do_shortcode('[[[footag]]] [[[bartag foo="bar"]]]');
+		$this->assertEquals('[[footag]] [[bartag foo="bar"]]', $out);
+	}
+
+	function test_tag_not_escaped() {
+		// these have square brackets on either end but aren't actually escaped
+		$out = do_shortcode('[[footag] [bartag foo="bar"]]');
+		$this->assertEquals('[foo =  foo = bar]', $out);
+
+		$out = do_shortcode('[[footag /] [bartag foo="bar" /]]');
+		$this->assertEquals('[foo =  foo = bar]', $out);
+
+		$out = do_shortcode('[[baztag foo="bar"]the content[/baztag]');
+		$this->assertEquals('[content = the content', $out);
+
+		$out = do_shortcode('[[not-a-tag]]');
+		$this->assertEquals('[[not-a-tag]]', $out);
+
+		$out = do_shortcode('[[[footag] [bartag foo="bar"]]]');
+		$this->assertEquals('[[foo =  foo = bar]]', $out);
+	}
+
+	function test_mixed_tags() {
+		$in = <<<EOF
+So this is a post with [footag foo="some stuff"] and a bunch of tags.
+
+[bartag]
+
+[baztag]
+Here's some content
+on more than one line
+[/baztag]
+
+[bartag foo=1] [baztag] [footag foo="2"] [baztag]
+
+[baztag]
+more content
+[/baztag]
+
+EOF;
+		$expected = <<<EOF
+So this is a post with foo = some stuff and a bunch of tags.
+
+foo = no foo
+
+content =
+Here's some content
+on more than one line
+
+
+foo = 1 content =  foo = 2 content =
+content =
+more content
+
+EOF;
+		$out = do_shortcode($in);
+		$this->assertEquals(strip_ws($expected), strip_ws($out));
+	}
+
+	/**
+	 * @ticket 6562
+	 */
+	function test_utf8_whitespace_1() {
+		// NO-BREAK SPACE: U+00A0
+		do_shortcode("[test-shortcode-tag foo=\"bar\" \xC2\xA0baz=\"123\"]");
+		$this->assertEquals( array('foo' => 'bar', 'baz' => '123'), $this->atts );
+		$this->assertEquals( '', $this->content );
+	}
+
+	/**
+	 * @ticket 6562
+	 */
+	function test_utf8_whitespace_2() {
+		// ZERO WIDTH SPACE: U+200B
+		do_shortcode("[test-shortcode-tag foo=\"bar\" \xE2\x80\x8Babc=\"def\"]");
+		$this->assertEquals( array('foo' => 'bar', 'abc' => 'def'), $this->atts );
+		$this->assertEquals( '', $this->content );
+	}
+
+	/**
+	 * @ticket 14050
+	 */
+	function test_shortcode_unautop() {
+		// a blank line is added at the end, so test with it already there
+		$test_string = "[footag]\n";
+		$this->assertEquals( $test_string, shortcode_unautop( wpautop( $test_string ) ) );
+	}
+
+	/**
+	 * @ticket 14050
+	 */
+	function test_multiple_shortcode_unautop() {
+		// a blank line is added at the end, so test with it already there
+		$test_string = "[footag]\n[footag]\n";
+		$actual = shortcode_unautop( wpautop( $test_string ) );
+		$this->assertEquals( $test_string, $actual );
+	}
+
+	/**
+	 * @ticket 10326
+	 */
+	function test_strip_shortcodes() {
+		$this->assertEquals('before', strip_shortcodes('before[gallery]'));
+		$this->assertEquals('after', strip_shortcodes('[gallery]after'));
+		$this->assertEquals('beforeafter', strip_shortcodes('before[gallery]after'));
+	}
+
+
+	// Store passed in shortcode_atts_{$shortcode} args
+	function _filter_atts( $out, $pairs, $atts ) {
+		$this->filter_atts_out = $out;
+		$this->filter_atts_pairs = $pairs;
+		$this->filter_atts_atts = $atts;
+		return $out;
+	}
+
+	// Filter shortcode atts in various ways
+	function _filter_atts2( $out, $pairs, $atts ) {
+		// If foo attribute equals "foo1", change it to be default value
+		if ( isset( $out['foo'] ) && 'foo1' == $out['foo'] )
+			$out['foo'] = $pairs['foo'];
+
+		// If baz attribute is set, remove it
+		if ( isset( $out['baz'] ) )
+			unset( $out['baz'] );
+
+		$this->filter_atts_out = $out;
+		return $out;
+	}
+
+	function test_shortcode_atts_filter_passes_original_arguments() {
+		add_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts' ), 10, 3 );
+
+		do_shortcode('[bartag foo="foo1" /]');
+		$this->assertEquals( array( 'foo' => 'foo1', 'baz' => 'default baz' ), $this->filter_atts_out );
+		$this->assertEquals( array( 'foo' => 'no foo', 'baz' => 'default baz' ), $this->filter_atts_pairs );
+		$this->assertEquals( array( 'foo' => 'foo1' ), $this->filter_atts_atts );
+
+		remove_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts' ), 10, 3 );
+	}
+
+	function test_shortcode_atts_filtering() {
+		add_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts2' ), 10, 3 );
+
+		$out = do_shortcode('[bartag foo="foo1" baz="baz1" /]');
+		$this->assertEquals( array( 'foo' => 'no foo' ), $this->filter_atts_out );
+		$this->assertEquals( 'foo = no foo', $out );
+
+		$out = do_shortcode('[bartag foo="foo2" /]');
+		$this->assertEquals( 'foo = foo2', $out );
+
+		remove_filter( 'shortcode_atts_bartag', array( $this, '_filter_atts2' ), 10, 3 );
+	}
+
+	/**
+	 * Check that shortcode_unautop() will always recognize spaces around shortcodes.
+	 *
+	 * @ticket 22692
+	 */
+	function test_spaces_around_shortcodes() {
+		$nbsp = "\xC2\xA0";
+
+		$input  = array();
+
+		$input[] = "<p>[gallery ids=\"37,15,11\"]</p>";
+		$input[] = "<p> [gallery ids=\"37,15,11\"] </p>";
+		$input[] = "<p> $nbsp[gallery ids=\"37,15,11\"] $nbsp</p>";
+		$input[] = "<p> &nbsp;[gallery ids=\"37,15,11\"] &nbsp;</p>";
+
+		$output = "[gallery ids=\"37,15,11\"]";
+
+		foreach($input as $in) {
+			$this->assertEquals( $output, shortcode_unautop( $in ) );
+		}
+	}
+}
