'Image styles path and URL functions', 'description' => 'Tests functions for generating paths and URLs to image styles.', 'group' => 'Image', ); } function setUp() { parent::setUp(); $this->style_name = 'style_foo'; image_style_save(array('name' => $this->style_name)); } /** * Test image_style_path(). */ function testImageStylePath() { $actual = image_style_path($this->style_name, 'public://foo/bar.gif'); $expected = 'public://styles/' . $this->style_name . '/foo/bar.gif'; $this->assertEqual($actual, $expected, t('Got the path for a file URI.')); $actual = image_style_path($this->style_name, 'foo/bar.gif'); $expected = 'public://styles/' . $this->style_name . '/foo/bar.gif'; $this->assertEqual($actual, $expected, t('Got the path for a relative file path.')); } /** * Test image_style_url() with a file using the "public://" scheme. */ function testImageStyleUrlAndPathPublic() { $this->_testImageStyleUrlAndPath('public'); } /** * Test image_style_url() with a file using the "private://" scheme. */ function testImageStyleUrlAndPathPrivate() { $this->_testImageStyleUrlAndPath('private'); } /** * Test image_style_url(). */ function _testImageStyleUrlAndPath($scheme) { // Make the default scheme neither "public" nor "private" to verify the // functions work for other than the default scheme. variable_set('file_default_scheme', 'temporary'); $d = 'temporary://'; file_prepare_directory($d, FILE_CREATE_DIRECTORY); // Create the directories for the styles. $d = $scheme . '://styles/' . $this->style_name; $status = file_prepare_directory($d, FILE_CREATE_DIRECTORY); $this->assertNotIdentical(FALSE, $status, t('Created the directory for the generated images for the test style.' )); // Create a working copy of the file. $files = $this->drupalGetTestFiles('image'); $file = reset($files); $image_info = image_get_info($file->uri); $original_uri = file_unmanaged_copy($file->uri, $scheme . '://', FILE_EXISTS_RENAME); $this->assertNotIdentical(FALSE, $original_uri, t('Created the generated image file.')); // Get the URL of a file that has not been generated yet and try to access // it before image_style_url has been called. $generated_uri = $scheme . '://styles/' . $this->style_name . '/' . basename($original_uri); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $expected_generate_url = url('image/generate/' . $this->style_name . '/' . $scheme . '/' . basename($original_uri), array('absolute' => TRUE)); $this->drupalGet($expected_generate_url); $this->assertResponse(403, t('Access to generate URL was denied.')); // Check that a generate URL is returned. $actual_generate_url = image_style_url($this->style_name, $original_uri); $this->assertEqual($actual_generate_url, $expected_generate_url, t('Got the generate URL for a non-existent file.')); // Fetch the URL that generates the file while another process appears to // be generating the same file (this is signaled using a lock). $lock_name = 'image_style_generate:' . $this->style_name . ':' . md5($original_uri); $this->assertTrue(lock_acquire($lock_name), t('Lock was acquired.')); $this->drupalGet($expected_generate_url); $this->assertResponse(503, t('Service Unavailable response received.')); $this->assertTrue($this->drupalGetHeader('Retry-After'), t('Retry-After header received.')); lock_release($lock_name); // Fetch the URL that generates the file. $this->drupalGet($expected_generate_url); $this->assertTrue(file_exists($generated_uri), t('Generated file was created.')); $this->assertRaw(file_get_contents($generated_uri), t('URL returns expected file.')); $generated_image_info = image_get_info($generated_uri); $this->assertEqual($this->drupalGetHeader('Content-Type'), $generated_image_info['mime_type'], t('Expected Content-Type was reported.')); $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], t('Expected Content-Length was reported.')); $this->assertTrue(lock_may_be_available($lock_name), t('Lock was released.')); // Check that the URL points directly to the generated file. $expected_generated_url = file_create_url($generated_uri); $actual_generated_url = image_style_url($this->style_name, $original_uri); $this->drupalGet($expected_generated_url); $this->assertEqual($actual_generated_url, $expected_generated_url, t('Got the download URL for an existing file.')); $this->assertRaw(file_get_contents($generated_uri), t('URL returns expected file.')); $this->assertEqual($this->drupalGetHeader('Content-Type'), $image_info['mime_type'], t('Expected Content-Type was reported.')); $this->assertEqual($this->drupalGetHeader('Content-Length'), $image_info['file_size'], t('Expected Content-Length was reported.')); } } /** * Use the image_test.module's mock toolkit to ensure that the effects are * properly passing parameters to the image toolkit. */ class ImageEffectsUnitTest extends ImageToolkitTestCase { public static function getInfo() { return array( 'name' => 'Image effects', 'description' => 'Test that the image effects pass parameters to the toolkit correctly.', 'group' => 'Image', ); } function setUp() { parent::setUp('image_test'); module_load_include('inc', 'image', 'image.effects'); } /** * Test the image_resize_effect() function. */ function testResizeEffect() { $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual($calls['resize'][0][1], 1, t('Width was passed correctly')); $this->assertEqual($calls['resize'][0][2], 2, t('Height was passed correctly')); } /** * Test the image_scale_effect() function. */ function testScaleEffect() { // @todo: need to test upscaling. $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual($calls['resize'][0][1], 10, t('Width was passed correctly')); $this->assertEqual($calls['resize'][0][2], 5, t('Height was based off aspect ratio and passed correctly')); } /** * Test the image_crop_effect() function. */ function testCropEffect() { // @todo should test the keyword offsets. $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('crop')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual($calls['crop'][0][1], 0, t('X was passed correctly')); $this->assertEqual($calls['crop'][0][2], 1, t('Y was passed correctly')); $this->assertEqual($calls['crop'][0][3], 3, t('Width was passed correctly')); $this->assertEqual($calls['crop'][0][4], 4, t('Height was passed correctly')); } /** * Test the image_scale_and_crop_effect() function. */ function testScaleAndCropEffect() { $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('resize', 'crop')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual($calls['crop'][0][1], 7.5, t('X was computed and passed correctly')); $this->assertEqual($calls['crop'][0][2], 0, t('Y was computed and passed correctly')); $this->assertEqual($calls['crop'][0][3], 5, t('Width was computed and passed correctly')); $this->assertEqual($calls['crop'][0][4], 10, t('Height was computed and passed correctly')); } /** * Test the image_desaturate_effect() function. */ function testDesaturateEffect() { $this->assertTrue(image_desaturate_effect($this->image, array()), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('desaturate')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual(count($calls['desaturate'][0]), 1, t('Only the image was passed.')); } /** * Test the image_rotate_effect() function. */ function testRotateEffect() { // @todo: need to test with 'random' => TRUE $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), t('Function returned the expected value.')); $this->assertToolkitOperationsCalled(array('rotate')); // Check the parameters. $calls = image_test_get_all_calls(); $this->assertEqual($calls['rotate'][0][1], 90, t('Degrees were passed correctly')); $this->assertEqual($calls['rotate'][0][2], 0xffffff, t('Background color was passed correctly')); } } /** * Tests creation, deletion, and editing of image styles and effects. */ class ImageAdminStylesUnitTest extends DrupalWebTestCase { function getInfo() { return array( 'name' => 'Image styles and effects UI configuration', 'description' => 'Tests creation, deletion, and editing of image styles and effects at the UI level.', 'group' => 'Image', ); } /** * Implementation of setUp(). */ function setUp() { parent::setUp(); // Create an administrative user. $this->admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer image styles')); $this->drupalLogin($this->admin_user); } /** * Given an image style, generate an image. */ function createSampleImage($style) { static $file_path; // First, we need to make sure we have an image in our testing // file directory. Copy over an image on the first run. if (!isset($file_path)) { $files = $this->drupalGetTestFiles('image'); $file = reset($files); $file_path = file_unmanaged_copy($file->uri); } return image_style_url($style['name'], $file_path) ? $file_path : FALSE; } /** * Count the number of images currently create for a style. */ function getImageCount($style) { $directory = file_directory_path() . '/styles/' . $style['name']; return count(file_scan_directory($directory, '/.*/')); } /** * General test to add a style, add/remove/edit effects to it, then delete it. */ function testStyle() { // Setup a style to be created and effects to add to it. $style_name = strtolower($this->randomName(10)); $style_path = 'admin/config/media/image-styles/edit/' . $style_name; $effect_edits = array( 'image_resize' => array( 'data[width]' => 100, 'data[height]' => 101, ), 'image_scale' => array( 'data[width]' => 110, 'data[height]' => 111, 'data[upscale]' => 1, ), 'image_scale_and_crop' => array( 'data[width]' => 120, 'data[height]' => 121, ), 'image_crop' => array( 'data[width]' => 130, 'data[height]' => 131, 'data[anchor]' => 'center-center', ), 'image_desaturate' => array( // No options for desaturate. ), 'image_rotate' => array( 'data[degrees]' => 5, 'data[random]' => 1, 'data[bgcolor]' => '#FFFF00', ), ); // Add style form. $edit = array( 'name' => $style_name, ); $this->drupalPost('admin/config/media/image-styles/add', $edit, t('Create new style')); $this->assertRaw(t('Style %name was created.', array('%name' => $style_name)), t('Image style successfully created.')); // Add effect form. // Add each sample effect to the style. foreach ($effect_edits as $effect => $edit) { // Add the effect. $this->drupalPost($style_path, array('new' => $effect), t('Add')); if (!empty($edit)) { $this->drupalPost(NULL, $edit, t('Add effect')); } } // Edit effect form. // Revisit each form to make sure the effect was saved. drupal_static_reset('image_styles'); $style = image_style_load($style_name); foreach ($style['effects'] as $ieid => $effect) { $this->drupalGet($style_path . '/effects/' . $ieid); foreach ($effect_edits[$effect['name']] as $field => $value) { $this->assertFieldByName($field, $value, t('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['name'], '%value' => $value))); } } // Image style overview form (ordering and renaming). // Confirm the order of effects is maintained according to the order we // added the fields. $effect_edits_order = array_keys($effect_edits); $effects_order = array_values($style['effects']); $order_correct = TRUE; foreach ($effects_order as $index => $effect) { if ($effect_edits_order[$index] != $effect['name']) { $order_correct = FALSE; } } $this->assertTrue($order_correct, t('The order of the effects is correctly set by default.')); // Test the style overview form. // Change the name of the style and adjust the weights of effects. $style_name = strtolower($this->randomName(10)); $weight = count($effect_edits); $edit = array( 'name' => $style_name, ); foreach ($style['effects'] as $ieid => $effect) { $edit['effects[' . $ieid . '][weight]'] = $weight; $weight--; } // Create an image to make sure it gets flushed after saving. $image_path = $this->createSampleImage($style); $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path))); $this->drupalPost($style_path, $edit, t('Update style')); // Note that after changing the style name, the style path is changed. $style_path = 'admin/config/media/image-styles/edit/' . $style_name; // Check that the URL was updated. $this->drupalGet($style_path); $this->assertResponse(200, t('Image style %original renamed to %new', array('%original' => $style['name'], '%new' => $style_name))); // Check that the image was flushed after updating the style. // This is especially important when renaming the style. Make sure that // the old image directory has been deleted. $this->assertEqual($this->getImageCount($style), 0, t('Image style %style was flushed after renaming the style and updating the order of effects.', array('%style' => $style['name']))); // Load the style by the new name with the new weights. drupal_static_reset('image_styles'); $style = image_style_load($style_name, NULL); // Confirm the new style order was saved. $effect_edits_order = array_reverse($effect_edits_order); $effects_order = array_values($style['effects']); $order_correct = TRUE; foreach ($effects_order as $index => $effect) { if ($effect_edits_order[$index] != $effect['name']) { $order_correct = FALSE; } } $this->assertTrue($order_correct, t('The order of the effects is correctly set by default.')); // Image effect deletion form. // Create an image to make sure it gets flushed after deleting an effect. $image_path = $this->createSampleImage($style); $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path))); // Test effect deletion form. $effect = array_pop($style['effects']); $this->drupalPost($style_path . '/effects/' . $effect['ieid'] . '/delete', array(), t('Delete')); $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $effect['label'])), t('Image effect deleted.')); // Style deletion form. // Delete the style. $this->drupalPost('admin/config/media/image-styles/delete/' . $style_name, array(), t('Delete')); // Confirm the style directory has been removed. $directory = file_directory_path() . '/styles/' . $style_name; $this->assertFalse(is_dir($directory), t('Image style %style directory removed on style deletion.', array('%style' => $style['name']))); drupal_static_reset('image_styles'); $this->assertFalse(image_style_load($style_name), t('Image style %style successfully deleted.', array('%style' => $style['name']))); } /** * Test to override, edit, then revert a style. */ function testDefaultStyle() { // Setup a style to be created and effects to add to it. $style_name = 'thumbnail'; $edit_path = 'admin/config/media/image-styles/edit/' . $style_name; $delete_path = 'admin/config/media/image-styles/delete/' . $style_name; $revert_path = 'admin/config/media/image-styles/revert/' . $style_name; // Ensure deleting a default is not possible. $this->drupalGet($delete_path); $this->assertText(t('Page not found'), t('Default styles may not be deleted.')); // Ensure that editing a default is not possible (without overriding). $this->drupalGet($edit_path); $this->assertNoField('edit-name', t('Default styles may not be renamed.')); $this->assertNoField('edit-submit', t('Default styles may not be edited.')); $this->assertNoField('edit-add', t('Default styles may not have new effects added.')); // Create an image to make sure the default works before overriding. drupal_static_reset('image_styles'); $style = image_style_load($style_name); $image_path = $this->createSampleImage($style); $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path))); // Override the default. $this->drupalPost($edit_path, array(), t('Override defaults')); $this->assertRaw(t('The %style style has been overridden, allowing you to change its settings.', array('%style' => $style_name)), t('Default image style may be overridden.')); // Add sample effect to the overridden style. $this->drupalPost($edit_path, array('new' => 'image_desaturate'), t('Add')); drupal_static_reset('image_styles'); $style = image_style_load($style_name); // The style should now have 2 effect, the original scale provided by core // and the desaturate effect we added in the override. $effects = array_values($style['effects']); $this->assertEqual($effects[0]['name'], 'image_scale', t('The default effect still exists in the overridden style.')); $this->assertEqual($effects[1]['name'], 'image_desaturate', t('The added effect exists in the overridden style.')); // Check that we are unable to rename an overridden style. $this->drupalGet($edit_path); $this->assertNoField('edit-name', t('Overridden styles may not be renamed.')); // Create an image to ensure the override works properly. $image_path = $this->createSampleImage($style); $this->assertEqual($this->getImageCount($style), 1, t('Image style %style image %file successfully generated.', array('%style' => $style['name'], '%file' => $image_path))); // Revert the image style. $this->drupalPost($revert_path, array(), t('Revert')); drupal_static_reset('image_styles'); $style = image_style_load($style_name); // The style should now have the single effect for scale. $effects = array_values($style['effects']); $this->assertEqual($effects[0]['name'], 'image_scale', t('The default effect still exists in the reverted style.')); $this->assertFalse(array_key_exists(1, $effects), t('The added effect has been removed in the reverted style.')); } }