Merge branch 'master' into 2.5

Conflicts:
	CONTRIBUTING.md
	lib/Cake/Model/Datasource/DboSource.php
This commit is contained in:
mark_story 2014-03-21 22:55:28 -04:00
commit 8acb75425d
10 changed files with 122 additions and 72 deletions

View file

@ -1,9 +1,10 @@
<<<<<<< HEAD
# How to contribute # How to contribute
CakePHP loves to welcome your contributions. There are several ways to help out: 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 * Create an [issue](https://github.com/cakephp/cakephp/issues) on GitHub, if you have found a bug
* Write testcases for open bug issues * Write test cases for open bug issues
* Write patches for open bug/feature issues, preferably with testcases included * Write patches for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://github.com/cakephp/docs) * 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 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. * Make commits of logical units.
* Check for unnecessary whitespace with `git diff --check` before committing. * Check for unnecessary whitespace with `git diff --check` before committing.
* Use descriptive commit messages and reference the #issue number. * 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 [travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs
will be executed. will be executed.
* Your work should apply the CakePHP coding standards. * 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 * Submit a pull request to the repository in the cakephp organization, with the
correct target branch. correct target branch.
## Testcases and codesniffer ## Test cases and codesniffer
CakePHP tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html) 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 ./lib/Cake/Console/cake test core AllTests --stderr

View file

@ -1704,8 +1704,7 @@ class DboSource extends DataSource {
if (!empty($assocData['order'])) { if (!empty($assocData['order'])) {
$queryData['order'][] = $assocData['order']; $queryData['order'][] = $assocData['order'];
} }
if (!in_array($join, $queryData['joins'], true)) {
if (!in_array($join, $queryData['joins'])) {
$queryData['joins'][] = $join; $queryData['joins'][] = $join;
} }

View file

@ -2705,8 +2705,9 @@ class Model extends Object implements CakeEventListener {
} }
$ids = $this->find('all', array_merge(array( $ids = $this->find('all', array_merge(array(
'fields' => "DISTINCT {$this->alias}.{$this->primaryKey}", 'fields' => "{$this->alias}.{$this->primaryKey}",
'order' => false, 'order' => false,
'group' => "{$this->alias}.{$this->primaryKey}",
'recursive' => 0), compact('conditions')) 'recursive' => 0), compact('conditions'))
); );

View file

@ -62,24 +62,31 @@ class CakeTestCaseTest extends CakeTestCase {
} }
/** /**
* testAssertGoodTags * testAssertTags
* *
* @return void * @return void
*/ */
public function testAssertTagsQuotes() { public function testAssertTagsBasic() {
$test = new AssertTagsTestCase('testAssertTagsQuotes'); $test = new AssertTagsTestCase('testAssertTagsQuotes');
$result = $test->run(); $result = $test->run();
$this->assertEquals(0, $result->errorCount()); $this->assertEquals(0, $result->errorCount());
$this->assertTrue($result->wasSuccessful()); $this->assertTrue($result->wasSuccessful());
$this->assertEquals(0, $result->failureCount()); $this->assertEquals(0, $result->failureCount());
}
/**
* test assertTags works with single and double quotes
*
* @return void
*/
public function testAssertTagsQuoting() {
$input = '<a href="/test.html" class="active">My link</a>'; $input = '<a href="/test.html" class="active">My link</a>';
$pattern = array( $pattern = array(
'a' => array('href' => '/test.html', 'class' => 'active'), 'a' => array('href' => '/test.html', 'class' => 'active'),
'My link', 'My link',
'/a' '/a'
); );
$this->assertTrue($test->assertTags($input, $pattern), 'Double quoted attributes %s'); $this->assertTags($input, $pattern);
$input = "<a href='/test.html' class='active'>My link</a>"; $input = "<a href='/test.html' class='active'>My link</a>";
$pattern = array( $pattern = array(
@ -87,7 +94,7 @@ class CakeTestCaseTest extends CakeTestCase {
'My link', 'My link',
'/a' '/a'
); );
$this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); $this->assertTags($input, $pattern);
$input = "<a href='/test.html' class='active'>My link</a>"; $input = "<a href='/test.html' class='active'>My link</a>";
$pattern = array( $pattern = array(
@ -95,7 +102,7 @@ class CakeTestCaseTest extends CakeTestCase {
'My link', 'My link',
'/a' '/a'
); );
$this->assertTrue($test->assertTags($input, $pattern), 'Single quoted attributes %s'); $this->assertTags($input, $pattern);
$input = "<span><strong>Text</strong></span>"; $input = "<span><strong>Text</strong></span>";
$pattern = array( $pattern = array(
@ -105,7 +112,7 @@ class CakeTestCaseTest extends CakeTestCase {
'/strong', '/strong',
'/span' '/span'
); );
$this->assertTrue($test->assertTags($input, $pattern), 'Tags with no attributes'); $this->assertTags($input, $pattern);
$input = "<span class='active'><strong>Text</strong></span>"; $input = "<span class='active'><strong>Text</strong></span>";
$pattern = array( $pattern = array(
@ -115,7 +122,34 @@ class CakeTestCaseTest extends CakeTestCase {
'/strong', '/strong',
'/span' '/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 = '<div attr8="val8" attr6="val6" attr4="val4" attr2="val2"' .
' attr1="val1" attr3="val3" attr5="val5" attr7="val7" />' .
'My div' .
'</div>';
$this->assertTags($input, $pattern);
} }
/** /**

View file

@ -92,6 +92,8 @@ class InflectorTest extends CakeTestCase {
$this->assertEquals(Inflector::singularize('faxes'), 'fax'); $this->assertEquals(Inflector::singularize('faxes'), 'fax');
$this->assertEquals(Inflector::singularize('waxes'), 'wax'); $this->assertEquals(Inflector::singularize('waxes'), 'wax');
$this->assertEquals(Inflector::singularize('niches'), 'niche'); $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('waves'), 'wave');
$this->assertEquals(Inflector::singularize('bureaus'), 'bureau'); $this->assertEquals(Inflector::singularize('bureaus'), 'bureau');
$this->assertEquals(Inflector::singularize('genetic_analyses'), 'genetic_analysis'); $this->assertEquals(Inflector::singularize('genetic_analyses'), 'genetic_analysis');

View file

@ -881,6 +881,7 @@ class FormHelperTest extends CakeTestCase {
'/div', '/div',
); );
$this->assertTags($result, $expected); $this->assertTags($result, $expected);
$result = $this->Form->submit('Cancel', array('name' => 'cancel')); $result = $this->Form->submit('Cancel', array('name' => 'cancel'));
$expected = array( $expected = array(
'div' => array('class' => 'submit'), 'div' => array('class' => 'submit'),
@ -1863,8 +1864,8 @@ class FormHelperTest extends CakeTestCase {
'preg:/[^<]+/', 'preg:/[^<]+/',
'/label', '/label',
'input' => array( 'input' => array(
'type' => 'text', 'name' => 'preg:/[^<]+/', 'type' => 'text', 'name', 'id',
'id' => 'preg:/[^<]+/', 'class' => 'form-error' 'class' => 'form-error'
), ),
array('div' => array('class' => 'error-message')), array('div' => array('class' => 'error-message')),
'You must have a last name', 'You must have a last name',
@ -1888,7 +1889,7 @@ class FormHelperTest extends CakeTestCase {
'label' => array('for'), 'label' => array('for'),
'Balance', 'Balance',
'/label', '/label',
'input' => array('name', 'type' => 'number', 'id'), 'input' => array('name', 'type' => 'number', 'id', 'step'),
'/div', '/div',
); );
$this->assertTags($result, $expected); $this->assertTags($result, $expected);
@ -5750,14 +5751,14 @@ class FormHelperTest extends CakeTestCase {
$result = $this->Form->checkbox('Contact.field', array('id' => 'theID', 'value' => 'myvalue')); $result = $this->Form->checkbox('Contact.field', array('id' => 'theID', 'value' => 'myvalue'));
$expected = array( $expected = array(
'input' => array('type' => 'hidden', 'class' => 'form-error', 'name' => 'data[Contact][field]', 'value' => '0', 'id' => 'theID_'), '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); $this->assertTags($result, $expected);
$result = $this->Form->checkbox('Contact.field', array('value' => 'myvalue')); $result = $this->Form->checkbox('Contact.field', array('value' => 'myvalue'));
$expected = array( $expected = array(
'input' => array('type' => 'hidden', 'class' => 'form-error', 'name' => 'data[Contact][field]', 'value' => '0', 'id' => 'ContactField_'), '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); $this->assertTags($result, $expected);

View file

@ -1,6 +1,6 @@
<?php <?php
/** /**
* This class helpes in indirectly testing the functionalities of CakeTestCase::assertTags * This class helps in indirectly testing the functionalities of CakeTestCase::assertTags
* *
* @package Cake.Test.Fixture * @package Cake.Test.Fixture
*/ */
@ -115,4 +115,5 @@ class AssertTagsTestCase extends CakeTestCase {
); );
$this->assertTags($input, $pattern); $this->assertTags($input, $pattern);
} }
} }

View file

@ -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 * Checks for an input tag with a name attribute (contains any non-empty value) and an id
* attribute that contains 'my-input': * 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: * Checks for two p elements with some text in them:
* array( *
* array('p' => true), * {{{
* 'textA', * array(
* '/p', * array('p' => true),
* array('p' => true), * 'textA',
* 'textB', * '/p',
* '/p' * array('p' => true),
* ) * 'textB',
* '/p'
* )
* }}}
* *
* You can also specify a pattern expression as part of the attribute values, or the tag * 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: * 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 * Important: This function is very forgiving about whitespace and also accepts any
* permutation of attribute order. It will also allow whitespace between specified tags. * permutation of attribute order. It will also allow whitespace between specified tags.
@ -439,8 +448,13 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase {
$val = '.+?'; $val = '.+?';
$explanations[] = sprintf('Attribute "%s" present', $attr); $explanations[] = sprintf('Attribute "%s" present', $attr);
} elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) { } elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
$quotes = '["\']?'; $val = str_replace(
$val = $matches[1]; array('.*', '.+'),
array('.*?', '.+?'),
$matches[1]
);
$quotes = $val !== $matches[1] ? '["\']' : '["\']?';
$explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val); $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
} else { } else {
$explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val); $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
@ -451,16 +465,9 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase {
$i++; $i++;
} }
if ($attrs) { if ($attrs) {
$permutations = $this->_arrayPermute($attrs);
$permutationTokens = array();
foreach ($permutations as $permutation) {
$permutationTokens[] = implode('', $permutation);
}
$regex[] = array( $regex[] = array(
sprintf('%s', implode(', ', $explanations)), 'explains' => $explanations,
$permutationTokens, 'attrs' => $attrs,
$i,
); );
} }
$regex[] = array( $regex[] = array(
@ -470,9 +477,14 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase {
); );
} }
} }
foreach ($regex as $i => $assertation) { foreach ($regex as $i => $assertion) {
list($description, $expressions, $itemNum) = $assertation;
$matches = false; $matches = false;
if (isset($assertion['attrs'])) {
$string = $this->_assertAttributes($assertion, $string);
continue;
}
list($description, $expressions, $itemNum) = $assertion;
foreach ((array)$expressions as $expression) { foreach ((array)$expressions as $expression) {
if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) { if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
$matches = true; $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 $assertions Assertions to run.
* @param array $perms * @param string $string The HTML string to check.
* @return array * @return void
*/ */
protected function _arrayPermute($items, $perms = array()) { protected function _assertAttributes($assertions, $string) {
static $permuted; $asserts = $assertions['attrs'];
if (empty($perms)) { $explains = $assertions['explains'];
$permuted = array(); while (count($asserts) > 0) {
} $matches = false;
foreach ($asserts as $j => $assert) {
if (empty($items)) { if (preg_match(sprintf('/^%s/s', $assert), $string, $match)) {
$permuted[] = $perms; $matches = true;
} else { $string = substr($string, strlen($match[0]));
$numItems = count($items) - 1; array_splice($asserts, $j, 1);
for ($i = $numItems; $i >= 0; --$i) { array_splice($explains, $j, 1);
$newItems = $items; break;
$newPerms = $perms; }
list($tmp) = array_splice($newItems, $i, 1); }
array_unshift($newPerms, $tmp); if ($matches === false) {
$this->_arrayPermute($newItems, $newPerms); $this->assertTrue(false, 'Attribute did not match. Was expecting ' . $explains[$j]);
} }
return $permuted;
} }
return $string;
} }
// @codingStandardsIgnoreStart // @codingStandardsIgnoreStart

View file

@ -292,7 +292,7 @@ abstract class ControllerTestCase extends CakeTestCase {
* ### Mocks: * ### Mocks:
* *
* - `methods` Methods to mock on the controller. `_stop()` is mocked by default * - `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 * 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 * with the value being specific methods on the model to mock. If `true` or
* no value is passed, the entire model will be mocked. * no value is passed, the entire model will be mocked.

View file

@ -117,7 +117,7 @@ class Inflector {
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1', '/([ftw]ax)es/i' => '\1',
'/(cris|ax|test)es$/i' => '\1is', '/(cris|ax|test)es$/i' => '\1is',
'/(shoe|slave)s$/i' => '\1', '/(shoe)s$/i' => '\1',
'/(o)es$/i' => '\1', '/(o)es$/i' => '\1',
'/ouses$/' => 'ouse', '/ouses$/' => 'ouse',
'/([^a])uses$/' => '\1us', '/([^a])uses$/' => '\1us',
@ -130,7 +130,7 @@ class Inflector {
'/(hive)s$/i' => '\1', '/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1', '/(drive)s$/i' => '\1',
'/([le])ves$/i' => '\1f', '/([le])ves$/i' => '\1f',
'/([^rfo])ves$/i' => '\1fe', '/([^rfoa])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis', '/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um', '/([ti])a$/i' => '\1um',
@ -147,7 +147,6 @@ class Inflector {
), ),
'irregular' => array( 'irregular' => array(
'foes' => 'foe', 'foes' => 'foe',
'waves' => 'wave',
) )
); );