3 use Behat\Behat\Context\Context;
4 use Behat\Gherkin\Node\PyStringNode;
5 use Behat\Gherkin\Node\TableNode;
6 use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
7 use Drupal\DrupalExtension\Hook\Scope\EntityScope;
8 use Symfony\Component\Process\PhpExecutableFinder;
9 use Symfony\Component\Process\Process;
12 * Features context for testing the Drupal Extension.
14 * @todo we are duplicating code from Behat's FeatureContext here for the
15 * purposes of testing since we can't easily run that as a context due to naming
18 class FeatureContext implements Context {
20 * Hook into node creation to test `@beforeNodeCreate`
24 public static function alterNodeParameters(BeforeNodeCreateScope $scope) {
25 call_user_func('\Drupal\DrupalExtension\Context\RawDrupalContext::alterNodeParameters', $scope);
26 // @see `features/api.feature`
27 // Change 'published on' to the expected 'created'.
28 $node = $scope->getEntity();
29 if (isset($node->{"published on"})) {
30 $node->created = $node->{"published on"};
31 unset($node->{"published on"});
36 * Hook into term creation to test `@beforeTermCreate`
40 public static function alterTermParameters(EntityScope $scope) {
41 // @see `features/api.feature`
42 // Change 'Label' to expected 'name'.
43 $term = $scope->getEntity();
44 if (isset($term->{'Label'})) {
45 $term->name = $term->{'Label'};
46 unset($term->{'Label'});
51 * Hook into user creation to test `@beforeUserCreate`
55 public static function alterUserParameters(EntityScope $scope) {
56 // @see `features/api.feature`
57 // Concatenate 'First name' and 'Last name' to form user name.
58 $user = $scope->getEntity();
59 if (isset($user->{"First name"}) && isset($user->{"Last name"})) {
60 $user->name = $user->{"First name"} . ' ' . $user->{"Last name"};
61 unset($user->{"First name"}, $user->{"Last name"});
63 // Transform custom 'E-mail' to 'mail'.
64 if (isset($user->{"E-mail"})) {
65 $user->mail = $user->{"E-mail"};
66 unset($user->{"E-mail"});
71 * Test that a node is returned after node create.
75 public static function afterNodeCreate(EntityScope $scope) {
76 if (!$node = $scope->getEntity()) {
77 throw new \Exception('Failed to find a node in @afterNodeCreate hook.');
82 * Test that a term is returned after term create.
86 public static function afterTermCreate(EntityScope $scope) {
87 if (!$term = $scope->getEntity()) {
88 throw new \Exception('Failed to find a term in @afterTermCreate hook.');
93 * Test that a user is returned after user create.
97 public static function afterUserCreate(EntityScope $scope) {
98 if (!$user = $scope->getEntity()) {
99 throw new \Exception('Failed to find a user in @afterUserCreate hook.');
104 * Transforms long address field columns into shorter aliases.
106 * This is used in field_handlers.feature for testing if lengthy field:column
107 * combinations can be shortened to more human friendly aliases.
109 * @Transform table:name,mail,street,city,postcode,country
111 public function castUsersTable(TableNode $user_table) {
113 'country' => 'field_post_address:country',
114 'city' => 'field_post_address:locality',
115 'street' => 'field_post_address:thoroughfare',
116 'postcode' => 'field_post_address:postal_code',
119 // The first row of the table contains the field names.
120 $table = $user_table->getTable();
122 $first_row = key($table);
124 // Replace the aliased field names with the actual ones.
125 foreach ($table[$first_row] as $key => $alias) {
126 if (array_key_exists($alias, $aliases)) {
127 $table[$first_row][$key] = $aliases[$alias];
131 return new TableNode($table);
135 * Transforms human readable field names into machine names.
137 * This is used in field_handlers.feature for testing if human readable names
138 * can be used instead of machine names in tests.
140 * @param TableNode $post_table
141 * The original table.
144 * The transformed table.
146 * @Transform rowtable:title,body,reference,date,links,select,address
148 public function transformPostContentTable(TableNode $post_table) {
150 'reference' => 'field_post_reference',
151 'date' => 'field_post_date',
152 'links' => 'field_post_links',
153 'select' => 'field_post_select',
154 'address' => 'field_post_address',
157 $table = $post_table->getTable();
158 array_walk($table, function (&$row) use ($aliases) {
159 // The first column of the row contains the field names. Replace the
160 // human readable field name with the machine name if it exists.
161 if (array_key_exists($row[0], $aliases)) {
162 $row[0] = $aliases[$row[0]];
166 return new TableNode($table);
170 * From here down is the Behat FeatureContext.
172 * @defgroup Behat FeatureContext
190 * Cleans test folders in the temporary directory.
195 public static function cleanTestFolders()
197 if (is_dir($dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'behat')) {
198 self::clearDirectory($dir);
203 * Prepares test folders in the temporary directory.
207 public function prepareTestFolders()
210 $random_name = md5((int) microtime(true) * rand(0, 100000));
211 $dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . $random_name;
212 } while (is_dir($dir));
214 mkdir($dir . '/features/bootstrap/i18n', 0777, true);
216 $phpFinder = new PhpExecutableFinder();
217 if (false === $php = $phpFinder->find()) {
218 throw new \RuntimeException('Unable to find the PHP executable.');
220 $this->workingDir = $dir;
221 $this->phpBin = $php;
222 $this->process = new Process(null);
226 * Creates a file with specified name and context in current workdir.
228 * @Given /^(?:there is )?a file named "([^"]*)" with:$/
230 * @param string $filename name of the file (relative path)
231 * @param PyStringNode $content PyString string instance
233 public function aFileNamedWith($filename, PyStringNode $content)
235 $content = strtr((string) $content, array("'''" => '"""'));
236 $this->createFile($this->workingDir . '/' . $filename, $content);
240 * Moves user to the specified path.
242 * @Given /^I am in the "([^"]*)" path$/
244 * @param string $path
246 public function iAmInThePath($path)
248 $this->moveToNewPath($path);
252 * Checks whether a file at provided path exists.
254 * @Given /^file "([^"]*)" should exist$/
256 * @param string $path
258 public function fileShouldExist($path)
260 PHPUnit_Framework_Assert::assertFileExists($this->workingDir . DIRECTORY_SEPARATOR . $path);
264 * Sets specified ENV variable
266 * @When /^"BEHAT_PARAMS" environment variable is set to:$/
268 * @param PyStringNode $value
270 public function iSetEnvironmentVariable(PyStringNode $value)
272 $this->process->setEnv(array('BEHAT_PARAMS' => (string) $value));
276 * Runs behat command with provided parameters
278 * @When /^I run "behat(?: ((?:\"|[^"])*))?"$/
280 * @param string $argumentsString
282 public function iRunBehat($argumentsString = '')
284 $argumentsString = strtr($argumentsString, array('\'' => '"'));
286 $this->process->setWorkingDirectory($this->workingDir);
287 $this->process->setCommandLine(
291 escapeshellarg(BEHAT_BIN_PATH),
293 strtr('--format-settings=\'{"timer": false}\'', array('\'' => '"', '"' => '\"'))
296 $this->process->start();
297 $this->process->wait();
301 * Checks whether previously ran command passes|fails with provided output.
303 * @Then /^it should (fail|pass) with:$/
305 * @param string $success "fail" or "pass"
306 * @param PyStringNode $text PyString text instance
308 public function itShouldPassWith($success, PyStringNode $text)
310 $this->itShouldFail($success);
311 $this->theOutputShouldContain($text);
315 * Checks whether specified file exists and contains specified string.
317 * @Then /^"([^"]*)" file should contain:$/
319 * @param string $path file path
320 * @param PyStringNode $text file content
322 public function fileShouldContain($path, PyStringNode $text)
324 $path = $this->workingDir . '/' . $path;
325 PHPUnit_Framework_Assert::assertFileExists($path);
327 $fileContent = trim(file_get_contents($path));
328 // Normalize the line endings in the output
329 if ("\n" !== PHP_EOL) {
330 $fileContent = str_replace(PHP_EOL, "\n", $fileContent);
333 PHPUnit_Framework_Assert::assertEquals($this->getExpectedOutput($text), $fileContent);
337 * Checks whether last command output contains provided string.
339 * @Then the output should contain:
341 * @param PyStringNode $text PyString text instance
343 public function theOutputShouldContain(PyStringNode $text)
345 PHPUnit_Framework_Assert::assertContains($this->getExpectedOutput($text), $this->getOutput());
348 private function getExpectedOutput(PyStringNode $expectedText)
350 $text = strtr($expectedText, array('\'\'\'' => '"""', '%%TMP_DIR%%' => sys_get_temp_dir() . DIRECTORY_SEPARATOR));
353 if ('/' !== DIRECTORY_SEPARATOR) {
354 $text = preg_replace_callback(
355 '/ features\/[^\n ]+/', function ($matches) {
356 return str_replace('/', DIRECTORY_SEPARATOR, $matches[0]);
359 $text = preg_replace_callback(
360 '/\<span class\="path"\>features\/[^\<]+/', function ($matches) {
361 return str_replace('/', DIRECTORY_SEPARATOR, $matches[0]);
364 $text = preg_replace_callback(
365 '/\+[fd] [^ ]+/', function ($matches) {
366 return str_replace('/', DIRECTORY_SEPARATOR, $matches[0]);
375 * Checks whether previously ran command failed|passed.
377 * @Then /^it should (fail|pass)$/
379 * @param string $success "fail" or "pass"
381 public function itShouldFail($success)
383 if ('fail' === $success) {
384 if (0 === $this->getExitCode()) {
385 echo 'Actual output:' . PHP_EOL . PHP_EOL . $this->getOutput();
388 PHPUnit_Framework_Assert::assertNotEquals(0, $this->getExitCode());
390 if (0 !== $this->getExitCode()) {
391 echo 'Actual output:' . PHP_EOL . PHP_EOL . $this->getOutput();
394 PHPUnit_Framework_Assert::assertEquals(0, $this->getExitCode());
398 private function getExitCode()
400 return $this->process->getExitCode();
403 private function getOutput()
405 $output = $this->process->getErrorOutput() . $this->process->getOutput();
407 // Normalize the line endings in the output
408 if ("\n" !== PHP_EOL) {
409 $output = str_replace(PHP_EOL, "\n", $output);
412 // Replace wrong warning message of HHVM
413 $output = str_replace('Notice: Undefined index: ', 'Notice: Undefined offset: ', $output);
415 return trim(preg_replace("/ +$/m", '', $output));
418 private function createFile($filename, $content)
420 $path = dirname($filename);
421 if (!is_dir($path)) {
422 mkdir($path, 0777, true);
425 file_put_contents($filename, $content);
428 private function moveToNewPath($path)
430 $newWorkingDir = $this->workingDir .'/' . $path;
431 if (!file_exists($newWorkingDir)) {
432 mkdir($newWorkingDir, 0777, true);
435 $this->workingDir = $newWorkingDir;
438 private static function clearDirectory($path)
440 $files = scandir($path);
444 foreach ($files as $file) {
445 $file = $path . DIRECTORY_SEPARATOR . $file;
447 self::clearDirectory($file);
457 * @} End of defgroup Behat FeatureContext.