diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0af4f7b89..fd285ba5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,10 @@ +<<<<<<< HEAD # How to contribute CakePHP loves to welcome your contributions. There are several ways to help out: * Create an [issue](https://github.com/cakephp/cakephp/issues) on GitHub, if you have found a bug -* Write testcases for open bug issues -* Write patches for open bug/feature issues, preferably with testcases included +* Write test cases for open bug issues +* Write patches for open bug/feature issues, preferably with test cases included * Contribute to the [documentation](https://github.com/cakephp/docs) There are a few guidelines that we need contributors to follow so that we have a @@ -30,7 +31,7 @@ chance of keeping on top of things. * Make commits of logical units. * Check for unnecessary whitespace with `git diff --check` before committing. * Use descriptive commit messages and reference the #issue number. -* Core testcases should continue to pass. You can run tests locally or enable +* Core test cases should continue to pass. You can run tests locally or enable [travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs will be executed. * Your work should apply the CakePHP coding standards. @@ -48,10 +49,10 @@ chance of keeping on top of things. * Submit a pull request to the repository in the cakephp organization, with the correct target branch. -## Testcases and codesniffer +## Test cases and codesniffer CakePHP tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html) -3.5 or higher. To run the testcases locally use the following command: +3.5 or higher. To run the test cases locally use the following command: ./lib/Cake/Console/cake test core AllTests --stderr diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index a7ce76d35..3c4153b02 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -1704,8 +1704,7 @@ class DboSource extends DataSource { if (!empty($assocData['order'])) { $queryData['order'][] = $assocData['order']; } - - if (!in_array($join, $queryData['joins'])) { + if (!in_array($join, $queryData['joins'], true)) { $queryData['joins'][] = $join; } diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 15735df17..8e854e7b2 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2705,8 +2705,9 @@ class Model extends Object implements CakeEventListener { } $ids = $this->find('all', array_merge(array( - 'fields' => "DISTINCT {$this->alias}.{$this->primaryKey}", + 'fields' => "{$this->alias}.{$this->primaryKey}", 'order' => false, + 'group' => "{$this->alias}.{$this->primaryKey}", 'recursive' => 0), compact('conditions')) ); diff --git a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php index 33acd0efe..2d884b246 100644 --- a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php +++ b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php @@ -62,24 +62,31 @@ class CakeTestCaseTest extends CakeTestCase { } /** - * testAssertGoodTags + * testAssertTags * * @return void */ - public function testAssertTagsQuotes() { + public function testAssertTagsBasic() { $test = new AssertTagsTestCase('testAssertTagsQuotes'); $result = $test->run(); $this->assertEquals(0, $result->errorCount()); $this->assertTrue($result->wasSuccessful()); $this->assertEquals(0, $result->failureCount()); + } +/** + * test assertTags works with single and double quotes + * + * @return void + */ + public function testAssertTagsQuoting() { $input = 'My link'; $pattern = array( 'a' => array('href' => '/test.html', 'class' => 'active'), 'My link', '/a' ); - $this->assertTrue($test->assertTags($input, $pattern), 'Double quoted attributes %s'); + $this->assertTags($input, $pattern); $input = "My link"; $pattern = array( @@ -87,7 +94,7 @@ class CakeTestCaseTest extends CakeTestCase { 'My link', '/a' ); - $this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); + $this->assertTags($input, $pattern); $input = "My link"; $pattern = array( @@ -95,7 +102,7 @@ class CakeTestCaseTest extends CakeTestCase { 'My link', '/a' ); - $this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); + $this->assertTags($input, $pattern); $input = "Text"; $pattern = array( @@ -105,7 +112,7 @@ class CakeTestCaseTest extends CakeTestCase { '/strong', '/span' ); - $this->assertTrue($test->assertTags($input, $pattern), 'Tags with no attributes'); + $this->assertTags($input, $pattern); $input = "Text"; $pattern = array( @@ -115,7 +122,34 @@ class CakeTestCaseTest extends CakeTestCase { '/strong', '/span' ); - $this->assertTrue($test->assertTags($input, $pattern), 'Test attribute presence'); + $this->assertTags($input, $pattern); + } + +/** + * Test that assertTags runs quickly. + * + * @return void + */ + public function testAssertTagsRuntimeComplexity() { + $pattern = array( + 'div' => array( + 'attr1' => 'val1', + 'attr2' => 'val2', + 'attr3' => 'val3', + 'attr4' => 'val4', + 'attr5' => 'val5', + 'attr6' => 'val6', + 'attr7' => 'val7', + 'attr8' => 'val8', + ), + 'My div', + '/div' + ); + $input = '
' . + 'My div' . + ''; + $this->assertTags($input, $pattern); } /** diff --git a/lib/Cake/Test/Case/Utility/InflectorTest.php b/lib/Cake/Test/Case/Utility/InflectorTest.php index 366a62635..1d269348b 100644 --- a/lib/Cake/Test/Case/Utility/InflectorTest.php +++ b/lib/Cake/Test/Case/Utility/InflectorTest.php @@ -92,6 +92,8 @@ class InflectorTest extends CakeTestCase { $this->assertEquals(Inflector::singularize('faxes'), 'fax'); $this->assertEquals(Inflector::singularize('waxes'), 'wax'); $this->assertEquals(Inflector::singularize('niches'), 'niche'); + $this->assertEquals(Inflector::singularize('caves'), 'cave'); + $this->assertEquals(Inflector::singularize('graves'), 'grave'); $this->assertEquals(Inflector::singularize('waves'), 'wave'); $this->assertEquals(Inflector::singularize('bureaus'), 'bureau'); $this->assertEquals(Inflector::singularize('genetic_analyses'), 'genetic_analysis'); diff --git a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php index 36fb95abd..35f7306f0 100755 --- a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php @@ -881,6 +881,7 @@ class FormHelperTest extends CakeTestCase { '/div', ); $this->assertTags($result, $expected); + $result = $this->Form->submit('Cancel', array('name' => 'cancel')); $expected = array( 'div' => array('class' => 'submit'), @@ -1863,8 +1864,8 @@ class FormHelperTest extends CakeTestCase { 'preg:/[^<]+/', '/label', 'input' => array( - 'type' => 'text', 'name' => 'preg:/[^<]+/', - 'id' => 'preg:/[^<]+/', 'class' => 'form-error' + 'type' => 'text', 'name', 'id', + 'class' => 'form-error' ), array('div' => array('class' => 'error-message')), 'You must have a last name', @@ -1888,7 +1889,7 @@ class FormHelperTest extends CakeTestCase { 'label' => array('for'), 'Balance', '/label', - 'input' => array('name', 'type' => 'number', 'id'), + 'input' => array('name', 'type' => 'number', 'id', 'step'), '/div', ); $this->assertTags($result, $expected); @@ -5750,14 +5751,14 @@ class FormHelperTest extends CakeTestCase { $result = $this->Form->checkbox('Contact.field', array('id' => 'theID', 'value' => 'myvalue')); $expected = array( 'input' => array('type' => 'hidden', 'class' => 'form-error', 'name' => 'data[Contact][field]', 'value' => '0', 'id' => 'theID_'), - array('input' => array('preg:/[^<]+/', 'value' => 'myvalue', 'id' => 'theID', 'checked' => 'checked', 'class' => 'form-error')) + array('input' => array('type', 'name', 'value' => 'myvalue', 'id' => 'theID', 'checked' => 'checked', 'class' => 'form-error')) ); $this->assertTags($result, $expected); $result = $this->Form->checkbox('Contact.field', array('value' => 'myvalue')); $expected = array( 'input' => array('type' => 'hidden', 'class' => 'form-error', 'name' => 'data[Contact][field]', 'value' => '0', 'id' => 'ContactField_'), - array('input' => array('preg:/[^<]+/', 'value' => 'myvalue', 'id' => 'ContactField', 'checked' => 'checked', 'class' => 'form-error')) + array('input' => array('type', 'name', 'value' => 'myvalue', 'id' => 'ContactField', 'checked' => 'checked', 'class' => 'form-error')) ); $this->assertTags($result, $expected); diff --git a/lib/Cake/Test/Fixture/AssertTagsTestCase.php b/lib/Cake/Test/Fixture/AssertTagsTestCase.php index 9391dd426..f0baeab3c 100644 --- a/lib/Cake/Test/Fixture/AssertTagsTestCase.php +++ b/lib/Cake/Test/Fixture/AssertTagsTestCase.php @@ -1,6 +1,6 @@ assertTags($input, $pattern); } + } diff --git a/lib/Cake/TestSuite/CakeTestCase.php b/lib/Cake/TestSuite/CakeTestCase.php index 16b4ee57b..4d8fd4050 100644 --- a/lib/Cake/TestSuite/CakeTestCase.php +++ b/lib/Cake/TestSuite/CakeTestCase.php @@ -340,24 +340,33 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { * * Checks for an input tag with a name attribute (contains any non-empty value) and an id * attribute that contains 'my-input': - * array('input' => array('name', 'id' => 'my-input')) + * + * {{{ + * array('input' => array('name', 'id' => 'my-input')) + * }}} * * Checks for two p elements with some text in them: - * array( - * array('p' => true), - * 'textA', - * '/p', - * array('p' => true), - * 'textB', - * '/p' - * ) + * + * {{{ + * array( + * array('p' => true), + * 'textA', + * '/p', + * array('p' => true), + * 'textB', + * '/p' + * ) + * }}} * * You can also specify a pattern expression as part of the attribute values, or the tag * being defined, if you prepend the value with preg: and enclose it with slashes, like so: - * array( - * array('input' => array('name', 'id' => 'preg:/FieldName\d+/')), - * 'preg:/My\s+field/' - * ) + * + * {{{ + * array( + * array('input' => array('name', 'id' => 'preg:/FieldName\d+/')), + * 'preg:/My\s+field/' + * ) + * }}} * * Important: This function is very forgiving about whitespace and also accepts any * permutation of attribute order. It will also allow whitespace between specified tags. @@ -439,8 +448,13 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { $val = '.+?'; $explanations[] = sprintf('Attribute "%s" present', $attr); } elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) { - $quotes = '["\']?'; - $val = $matches[1]; + $val = str_replace( + array('.*', '.+'), + array('.*?', '.+?'), + $matches[1] + ); + $quotes = $val !== $matches[1] ? '["\']' : '["\']?'; + $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val); } else { $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val); @@ -451,16 +465,9 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { $i++; } if ($attrs) { - $permutations = $this->_arrayPermute($attrs); - - $permutationTokens = array(); - foreach ($permutations as $permutation) { - $permutationTokens[] = implode('', $permutation); - } $regex[] = array( - sprintf('%s', implode(', ', $explanations)), - $permutationTokens, - $i, + 'explains' => $explanations, + 'attrs' => $attrs, ); } $regex[] = array( @@ -470,9 +477,14 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { ); } } - foreach ($regex as $i => $assertation) { - list($description, $expressions, $itemNum) = $assertation; + foreach ($regex as $i => $assertion) { $matches = false; + if (isset($assertion['attrs'])) { + $string = $this->_assertAttributes($assertion, $string); + continue; + } + + list($description, $expressions, $itemNum) = $assertion; foreach ((array)$expressions as $expression) { if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) { $matches = true; @@ -495,31 +507,31 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { } /** - * Generates all permutation of an array $items and returns them in a new array. + * Check the attributes as part of an assertTags() check. * - * @param array $items An array of items - * @param array $perms - * @return array + * @param array $assertions Assertions to run. + * @param string $string The HTML string to check. + * @return void */ - protected function _arrayPermute($items, $perms = array()) { - static $permuted; - if (empty($perms)) { - $permuted = array(); - } - - if (empty($items)) { - $permuted[] = $perms; - } else { - $numItems = count($items) - 1; - for ($i = $numItems; $i >= 0; --$i) { - $newItems = $items; - $newPerms = $perms; - list($tmp) = array_splice($newItems, $i, 1); - array_unshift($newPerms, $tmp); - $this->_arrayPermute($newItems, $newPerms); + protected function _assertAttributes($assertions, $string) { + $asserts = $assertions['attrs']; + $explains = $assertions['explains']; + while (count($asserts) > 0) { + $matches = false; + foreach ($asserts as $j => $assert) { + if (preg_match(sprintf('/^%s/s', $assert), $string, $match)) { + $matches = true; + $string = substr($string, strlen($match[0])); + array_splice($asserts, $j, 1); + array_splice($explains, $j, 1); + break; + } + } + if ($matches === false) { + $this->assertTrue(false, 'Attribute did not match. Was expecting ' . $explains[$j]); } - return $permuted; } + return $string; } // @codingStandardsIgnoreStart diff --git a/lib/Cake/TestSuite/ControllerTestCase.php b/lib/Cake/TestSuite/ControllerTestCase.php index 12701914d..23f03ce79 100644 --- a/lib/Cake/TestSuite/ControllerTestCase.php +++ b/lib/Cake/TestSuite/ControllerTestCase.php @@ -292,7 +292,7 @@ abstract class ControllerTestCase extends CakeTestCase { * ### Mocks: * * - `methods` Methods to mock on the controller. `_stop()` is mocked by default - * - `models` Models to mock. Models are added to the ClassRegistry so they any + * - `models` Models to mock. Models are added to the ClassRegistry so any * time they are instantiated the mock will be created. Pass as key value pairs * with the value being specific methods on the model to mock. If `true` or * no value is passed, the entire model will be mocked. diff --git a/lib/Cake/Utility/Inflector.php b/lib/Cake/Utility/Inflector.php index fb57413af..ef4685edf 100644 --- a/lib/Cake/Utility/Inflector.php +++ b/lib/Cake/Utility/Inflector.php @@ -117,7 +117,7 @@ class Inflector { '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', '/([ftw]ax)es/i' => '\1', '/(cris|ax|test)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', + '/(shoe)s$/i' => '\1', '/(o)es$/i' => '\1', '/ouses$/' => 'ouse', '/([^a])uses$/' => '\1us', @@ -130,7 +130,7 @@ class Inflector { '/(hive)s$/i' => '\1', '/(drive)s$/i' => '\1', '/([le])ves$/i' => '\1f', - '/([^rfo])ves$/i' => '\1fe', + '/([^rfoa])ves$/i' => '\1fe', '/(^analy)ses$/i' => '\1sis', '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', '/([ti])a$/i' => '\1um', @@ -147,7 +147,6 @@ class Inflector { ), 'irregular' => array( 'foes' => 'foe', - 'waves' => 'wave', ) );