/ */ class Workflow_Test_ContentIntegrationTest extends ModuleControllerTest { /** * All tests here need content types, workflows and 'editor' access. */ public function setUp() { parent::setUp(); // install default content types and workflows. P4Cms_Content_Type::installDefaultTypes(); Workflow_Model_Workflow::installDefaultWorkflows(); } /** * Ensure workflow sub-form is present when adding content * that has associated workflow */ public function testContentSubForm() { $this->utility->impersonate('editor'); $this->dispatch('/content/add/type/basic-page'); // ensure workflow sub-form is here. $this->assertQuery('.workflow-sub-form'); // basic page uses 'simple workflow', default state is 'draft'. $this->assertQuery('#workflow-state-draft[checked="checked"]'); // ensure transitions are present also. $workflow = Workflow_Model_Workflow::fetch('simple'); $state = $workflow->getStateModel('draft'); foreach ($state->getTransitions() as $transition => $details) { $this->assertQuery('#workflow-state-' . $transition); } } /** * Ensure that if there are elements in the sub-forms with names matching * entry fields, then they are not populated with those values. */ public function testSubFormElementsCollision() { $this->utility->impersonate('editor'); // save entry with a comment field that collides with // comment input element in the workflow sub-form $entry = P4Cms_Content::store( array( 'id' => 'test', 'contentType' => 'basic-page', 'title' => 'test-title', 'workflowState' => 'published', 'comment' => 'comment_test' ) ); // ensure workflow comment field is not populated with comment_test value // when editing a content that has associated workflow $this->dispatch('/content/edit/id/' . $entry->getId()); $this->assertNotQueryContentContains( "textarea#workflow-comment", 'comment_test', "Expected workflow comment doesn't contain entry's comment value." ); } /** * Ensure saving content, saves state. */ public function testContentSave() { $this->utility->impersonate('editor'); $this->request->setMethod('post') ->setPost('format', 'json') ->setPost('contentType', 'basic-page') ->setPost('title', 'test-title') ->setPost('body', 'test-body') ->setPost( 'workflow', array( 'state' => 'review', 'scheduled' => 'false', 'comment' => 'test' ) ); $this->dispatch('/content/add/'); $data = Zend_Json::decode($this->getResponse()->getBody()); $content = P4Cms_Content::fetch($data['contentId']); $change = $content->toP4File()->getChange(); $this->assertSame('review', $content->getValue('workflowState')); } /** * Ensure scheduled data are saved when saving entry with a scheduled transition. */ public function testContentSaveScheduled() { $this->utility->impersonate('editor'); // set a timestamp that is in the future where the day, month and year are not the // same as today (i.e. it must be at least one day from now); we set today + 2 days $timestamp = time() + 172800; $this->request->setMethod('post') ->setPost('format', 'json') ->setPost('contentType', 'basic-page') ->setPost('title', 'test-title') ->setPost('body', 'test-body') ->setPost( 'workflow', array( 'state' => 'review', 'scheduled' => 'true', 'scheduledDate' => date('Y-m-d', $timestamp), 'scheduledTime' => '07:00' ) ); $this->dispatch('/content/add/'); $data = Zend_Json::decode($this->getResponse()->getBody()); $content = P4Cms_Content::fetch($data['contentId']); $this->assertSame('draft', $content->getValue('workflowState')); $this->assertSame('review', $content->getValue('workflowScheduledState')); $this->assertSame( strtotime(date('Y-m-d 07:00', $timestamp)), (int) $content->getValue('workflowScheduledTime') ); } /** * Ensure editing shows current state. */ public function testContentEdit() { $this->utility->impersonate('editor'); // ensure current state is checked if entry has no scheduled transition $entry = $this->_makeContentEntry('published'); $this->dispatch('/content/edit/id/' . $entry->getId()); $this->assertQuery('#workflow-state-published[checked="checked"]'); } /** * Ensure edition shows scheduled state if entry has scheduled transition. */ public function testScheduledContentEdit() { $this->utility->impersonate('editor'); // if entry has scheduled transition, ensure that target state of scheduled transition is // checked $entry = $this->_makeContentEntry('review', '', 'draft', '1'); $this->dispatch('/content/edit/id/' . $entry->getId()); $this->assertQuery('#workflow-state-draft[checked="checked"]'); } /** * Test content access for anonymous role. */ public function testAccessAnonymous() { $published = $this->_makeContentEntry('published'); $unPublished = $this->_makeContentEntry(); // ensure anonymous users can see published content // but cannot see un-published content $this->utility->impersonate('anonymous'); $this->assertTrue(P4Cms_Content::exists($published->getId())); $this->assertFalse(P4Cms_Content::exists($unPublished->getId())); } /** * Test content access for editor role. */ public function testAccessEditor() { $unPublished = $this->_makeContentEntry(); // ensure un-published content is accessible to editors $this->utility->impersonate('editor'); $acl = P4Cms_Site::fetchActive()->getAcl(); $this->assertTrue(P4Cms_Content::exists($unPublished->getId())); } /** * Test content access for member role. */ public function testAccessMember() { $published = $this->_makeContentEntry('published', 'foo'); $unPublished = $this->_makeContentEntry(); $this->utility->impersonate('member'); $userId = P4Cms_User::fetchActive()->getId(); // ensure un-published content is accessible by members only if they own it $this->assertFalse(P4Cms_Content::exists($unPublished->getId())); $unPublished->setOwner($userId)->save(); $this->assertTrue(P4Cms_Content::exists($unPublished->getId())); // ensure that member cannot see 'publish' option if not having 'publish' permission $acl = P4Cms_Site::fetchActive()->getAcl(); $acl->allow('member', 'content', array('add', 'edit', 'edit-all')); $this->dispatch('/add/type/basic-page'); $this->_verifyWorkflowStatesRadio(array('draft', 'review'), 'draft'); // add publish permission and verify that user can see publish option $acl->allow('member', 'content', array('publish')); $this->resetRequest()->resetResponse(); $this->dispatch('/add/type/basic-page'); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'draft'); // ensure that member, although lacking 'access-unpublished' permission, can // unpublish content if is an owner $this->resetRequest()->resetResponse(); $this->dispatch('/content/index/edit/id/' . $published->getId()); // not an owner - should see only published state $this->_verifyWorkflowStatesRadio(array('published'), 'published'); // add 'access-unpublished' and verify again $acl->allow('member', 'content', array('access-unpublished')); $this->resetRequest()->resetResponse(); $this->dispatch('/content/index/edit/id/' . $published->getId()); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'published'); // remove access-published permission, but make user owner and verify again $acl->deny('member', 'content', array('access-unpublished')); $published->setOwner($userId)->save(); $this->resetRequest()->resetResponse(); $this->dispatch('/content/index/edit/id/' . $published->getId()); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'published'); } /** * Verify transition conditions. Dispatch to content add/edit action to simulate * creating/updating content entry when certain transition conditions apply. */ public function testWorkflowConditions() { // create workflow we will test against - // modify simple workflow to remove published to draft transition $workflow = Workflow_Model_Workflow::fetch('simple'); $workflowStates = $workflow->getStates(); unset($workflowStates['published']['transitions']['draft']); $workflow->setStates($workflowStates) ->save(); // set necessary permissions $this->utility->impersonate('editor'); // verify all transitions are available when adding a new content $this->dispatch('/add/type/basic-page'); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'draft'); // disable promoting to review $states = $workflowStates; $states['draft']['transitions']['review']['conditions'] = array('false'); $workflow->setStates($states) ->save(); $this->resetRequest() ->resetResponse(); $this->dispatch('/add/type/basic-page'); $this->_verifyWorkflowStatesRadio(array('draft', 'published'), 'draft'); // test with edit action $entry = $this->_makeContentEntry('review'); $workflow->setStates($workflowStates) ->save(); $this->resetRequest() ->resetResponse(); $this->dispatch('/content/index/edit/id/' . $entry->getId()); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'review'); // add Contains condition $states = $workflowStates; $states['review']['transitions']['published']['conditions'] = array( 'contains' => array( 'string' => 'find me' ) ); $workflow->setStates($states) ->save(); $this->resetRequest() ->resetResponse(); $this->dispatch('/content/index/edit/id/' . $entry->getId()); $this->_verifyWorkflowStatesRadio(array('draft', 'review'), 'review'); // modify entry to satisfy the condition $entry = P4Cms_Content::fetch($entry->getId()); $entry->setValue('body', 'Now body contains find me string.') ->save(); $this->resetRequest() ->resetResponse(); $this->dispatch('/content/index/edit/id/' . $entry->getId()); $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'review'); // try to modify content via post to ensure transition conditions must be met // also ensure conditions are evaluated agains posted data $this->resetRequest() ->resetResponse(); $params = array( 'contentType' => 'basic-page', 'title' => 'test-title', 'body' => 'new body', 'workflow' => array( 'state' => 'published', 'scheduled' => 'false' ) ); $this->request->setParam('id', $entry->getId()); $this->request->setMethod('POST'); $this->request->setPost($params); $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId()); // verify entry has not been modified $entry = P4Cms_Content::fetch($entry->getId()); $this->assertSame( 'review', $entry->getValue('workflowState'), "Expected workflow state has not changed as transition is invalid." ); // try again with no changes made so it should work $this->resetRequest() ->resetResponse(); $params = array( 'contentType' => 'basic-page', 'title' => 'test-title', 'body' => 'changed body to contain find me string', 'workflow' => array( 'state' => 'published', 'scheduled' => 'false' ) ); $this->request->setMethod('POST'); $this->request->setPost($params); $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId()); $responseBody = $this->response->getBody(); $this->assertModule('content', 'Last module run should be content module.'. $responseBody); $this->assertController('index', 'Expected controller.'. $responseBody); $this->assertAction('edit', 'Expected action.'. $responseBody); // verify entry has been modified $entry = P4Cms_Content::fetch($entry->getId()); $this->assertSame( 'published', $entry->getValue('workflowState'), "Expected workflow state has been changed as transition is valid." ); // check state options $this->resetRequest() ->resetResponse(); $this->dispatch('/content/index/edit/id/' . $entry->getId()); $this->_verifyWorkflowStatesRadio(array('review', 'published'), 'published'); } /** * Verify transition actions. Dispatch to content edit action to simulate * updating content entry and ensure transition actions are invoked. */ public function testWorkflowActions() { // test with simple workflow added by SendEmail action to a certain transitions $workflow = Workflow_Model_Workflow::fetch('simple'); $workflowStates = $workflow->getStates(); // add sendEmail action to the review->published and review->draft transitions $workflowStates['review']['transitions']['published']['actions'] = array( 'sendEmail' => array( 'to' => 'published@email.com', 'subject' => 'content published' ) ); $workflowStates['review']['transitions']['draft']['actions'] = array( 'sendEmail' => array( 'to' => 'draft@email.com', 'subject' => 'content demoted to draft' ) ); $workflow->setStates($workflowStates) ->save(); // set custom email transport to prevent from sending real emails and to allow // checking what emails would be sent $transport = new Workflow_Test_MailTransport; Zend_Mail::setDefaultTransport($transport); // set necessary permissions $this->utility->impersonate('editor'); // create new entry $entry = $this->_makeContentEntry(); // edit entry to promote to review state $params = array( 'contentType' => 'basic-page', 'title' => 'test-title', 'workflow' => array( 'state' => 'review', 'scheduled' => 'false' ) ); $this->request->setMethod('POST'); $this->request->setPost($params); $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId()); // ensure entry has been modified $entry = P4Cms_Content::fetch($entry->getId()); $this->assertSame( 'review', $entry->getValue('workflowState'), "Expected workflow state has been changed to review." ); // ensure email has not been sent $this->assertSame( 0, count($transport->getSentMails()), "Expected no sendEmail action has occured." ); // publish content $transport->reset(); $this->resetRequest() ->resetResponse(); $params = array( 'contentType' => 'basic-page', 'title' => 'test-title', 'workflow' => array( 'state' => 'published', 'scheduled' => 'false' ) ); $this->request->setMethod('POST'); $this->request->setPost($params); $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId()); // ensure entry has been updated $entry = P4Cms_Content::fetch($entry->getId()); $this->assertSame( 'published', $entry->getValue('workflowState'), "Expected workflow state has been changed to published." ); // ensure correct email has been sent $sentEmails = $transport->getSentMails(); $this->assertSame( 1, count($sentEmails), "Expected sendEmail action has occured." ); // verify email recipient and subject $this->assertSame( 'published@email.com', $sentEmails[0]['to'], "Expected email recipient." ); $this->assertSame( 'content published', $sentEmails[0]['subject'], "Expected email subject." ); // ensure actions are not invoked if transition conditions are not met $workflowStates['published']['transitions']['review']['actions'] = array( 'sendEmail' => array( 'to' => 'demote@email.com', 'subject' => 'published -> review' ) ); $workflowStates['published']['transitions']['review']['conditions'] = array('false'); $workflow->setStates($workflowStates) ->save(); // try to demote content to review $transport->reset(); $this->resetRequest() ->resetResponse(); $params = array( 'contentType' => 'basic-page', 'title' => 'test-title', 'workflow' => array( 'state' => 'review', 'scheduled' => 'false' ) ); $this->request->setMethod('POST'); $this->request->setPost($params); $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId()); // ensure entry has not been modified $entry = P4Cms_Content::fetch($entry->getId()); $this->assertSame( 'published', $entry->getValue('workflowState'), "Expected workflow state has not been changed to review." ); // ensure email has not been sent $sentEmails = $transport->getSentMails(); $this->assertSame( 0, count($sentEmails), "Expected sendEmail action has not occured." ); } /** * Verify filtering content data grid by workflow states. */ public function testFilterByWorkflow() { $this->utility->impersonate('administrator'); $published = Workflow_Model_State::PUBLISHED; $time = time() + 1000; // remove workflows/content types foreach (Workflow_Model_Workflow::fetchAll() as $workflow) { $workflow->delete(); } foreach (P4Cms_Content_Type::fetchAll() as $type) { $type->delete(); } // create workflows and content types with the following mapping: // -------------------------------------------------------- // | CONTENT TYPE | WORKFLOW | STATES | // -------------------------------------------------------- // | basic | simple | draft, published | // | basic2 | simple | draft, published | // | simple | | - | // | private | private | draft | // | blog | blog | draft, review, published | // | | unused | draft, review, published | // -------------------------------------------------------- Workflow_Model_Workflow::store( array( 'id' => "simple", 'label' => "simple workflow", 'states' => "[draft]\n" . "label=Draft\n" . "[$published]\n" . "label=Published" ) ); Workflow_Model_Workflow::store( array( 'id' => "private", 'label' => "private workflow", 'states' => "[draft]\n" . "label=Draft\n" ) ); Workflow_Model_Workflow::store( array( 'id' => "blog", 'label' => "blog workflow", 'states' => "[draft]\n" . "label=Draft\n" . "[review]\n" . "label=Review\n" . "[$published]\n" . "label=Published" ) ); Workflow_Model_Workflow::store( array( 'id' => "unused", 'label' => "unused workflow", 'states' => "[draft]\n" . "label=Draft\n" . "[review]\n" . "label=Review\n" . "[$published]\n" . "label=Published" ) ); P4Cms_Content_Type::store( array( 'id' => "basic", 'label' => "basic content type", 'group' => "misc", 'workflow' => "simple", 'elements' => "[title]\n" . "type=text" ) ); P4Cms_Content_Type::store( array( 'id' => "basic2", 'label' => "basic2 content type", 'group' => "misc", 'workflow' => "simple", 'elements' => "[title]\n" . "type=text" ) ); P4Cms_Content_Type::store( array( 'id' => "simple", 'label' => "simple content type", 'group' => "misc", 'elements' => "[title]\n" . "type=text" ) ); P4Cms_Content_Type::store( array( 'id' => "private", 'label' => "private content type", 'group' => "misc", 'workflow' => "private", 'elements' => "[title]\n" . "type=text" ) ); P4Cms_Content_Type::store( array( 'id' => "blog", 'label' => "blog content type", 'group' => "misc", 'workflow' => "blog", 'elements' => "[title]\n" . "type=text" ) ); // create contents with following relations to the workflows (dot denotes the same value as in the cell above): // ------------------------------------------------------------------------------------------------------- // | CONTENT TITLE | CONTENT TYPE | WORKFLOW | STATE ASSIGNED | STATE SCHEDULED | STATE AS IT APPEARS | // ------------------------------------------------------------------------------------------------------- // | blog-draft | blog | blog | draft | - | draft | // | blog-draft2 | . | . | draft | review | draft -> review | // | blog-review | . | . | review | published | review -> published | // | blog-pub | . | . | published | - | published | // | blog-abc | . | . | abc | | draft | // | simple | simple | - | - | - | published | // | simple-draft | simple | - | draft | review | published | // | simple2-draft | simple | - | draft | published | published | // | basic-draft | basic | simple | draft | published | draft -> published | // | basic-pub | . | . | published | draft | published -> draft | // | basic-review | . | . | review | - | draft | // | basic-xyz | . | . | xyz | - | draft | // | basic2-draft | basic2 | . | draft | - | draft | // | private-draft | private | private | draft | - | draft | // | private-pub | . | . | published | - | draft | // ------------------------------------------------------------------------------------------------------- P4Cms_Content::store( array( 'contentType' => 'private', 'title' => 'private-draft', 'workflowState' => 'draft' ) ); P4Cms_Content::store( array( 'contentType' => 'private', 'title' => 'private-pub', 'workflowState' => $published ) ); P4Cms_Content::store( array( 'contentType' => 'basic2', 'title' => 'basic2-draft', 'workflowState' => 'draft' ) ); P4Cms_Content::store( array( 'contentType' => 'basic', 'title' => 'basic-draft', 'workflowState' => 'draft', 'workflowScheduledState' => $published, 'workflowScheduledTime' => $time ) ); P4Cms_Content::store( array( 'contentType' => 'basic', 'title' => 'basic-pub', 'workflowState' => $published, 'workflowScheduledState' => 'draft', 'workflowScheduledTime' => $time ) ); P4Cms_Content::store( array( 'contentType' => 'basic', 'title' => 'basic-review', 'workflowState' => 'review' ) ); P4Cms_Content::store( array( 'contentType' => 'basic', 'title' => 'basic-xyz', 'workflowState' => 'xyz' ) ); P4Cms_Content::store( array( 'contentType' => 'blog', 'title' => 'blog-draft', 'workflowState' => 'draft' ) ); P4Cms_Content::store( array( 'contentType' => 'blog', 'title' => 'blog-draft2', 'workflowState' => 'draft', 'workflowScheduledState' => 'review', 'workflowScheduledTime' => $time ) ); P4Cms_Content::store( array( 'contentType' => 'blog', 'title' => 'blog-pub', 'workflowState' => $published ) ); P4Cms_Content::store( array( 'contentType' => 'blog', 'title' => 'blog-review', 'workflowState' => 'review', 'workflowScheduledState' => $published, 'workflowScheduledTime' => $time ) ); P4Cms_Content::store( array( 'contentType' => 'blog', 'title' => 'blog-abc', 'workflowState' => 'abc' ) ); P4Cms_Content::store( array( 'contentType' => 'simple', 'title' => 'simple', ) ); P4Cms_Content::store( array( 'contentType' => 'simple', 'title' => 'simple-draft', 'workflowState' => 'draft', 'workflowScheduledState' => 'review', 'workflowScheduledTime' => $time, ) ); P4Cms_Content::store( array( 'contentType' => 'simple', 'title' => 'simple2-draft', 'workflowState' => 'draft', 'workflowScheduledState' => $published, 'workflowScheduledTime' => $time, ) ); // test json data for browse action when provided with various options for filtering by workflow $this->dispatch('/browse/format/json'); $body = $this->getResponse()->getBody(); $data = Zend_Json::decode($body); $expected = P4Cms_Content::fetchAll()->invoke('getTitle'); $this->_verifyTitleValuesMatch($data['items'], $expected); // filter to keep only published $this->_verifyWorkflowFilter( array( 'workflow' => 'onlyPublished' ), array( array('blog-pub', 'simple', 'simple-draft', 'basic-pub', 'simple2-draft'), array('blog-review', 'basic-draft') ) ); // filter to keep only unpublished $this->_verifyWorkflowFilter( array( 'workflow' => 'onlyUnpublished' ), array( array('blog-draft', 'blog-draft2', 'blog-review', 'blog-abc', 'basic-draft', 'basic-review', 'basic-xyz', 'basic2-draft', 'private-draft', 'private-pub'), array('blog-draft2', 'basic-pub') ) ); // filter to keep only items under simple workflow $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array('simple/*') ), array( array('basic-draft', 'basic-pub', 'basic-review', 'basic-xyz', 'basic2-draft'), array('basic-draft', 'basic-pub') ) ); // filter to keep only items under blog workflow with published state $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array('blog/' . $published), ), array( array('blog-pub'), array('blog-review') ) ); // filter to keep only items under blog workflow with draft state // or simple workflow with published state $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array( 'blog/draft', 'simple/' . $published ) ), array( array('blog-draft', 'blog-draft2', 'blog-abc', 'basic-pub'), array('basic-draft') ) ); // filter to keep only items under 'unused' workflow $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array('unused/*'), ), array( array(), array() ) ); // filter to keep only items under private workflow having draft state $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array('private/draft'), ), array( array('private-draft', 'private-pub'), array() ) ); // filter to keep only items under simple or private workflow with draft state $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected', 'states' => array( 'simple/draft', 'private/draft' ) ), array( array('basic-draft', 'basic-review', 'basic-xyz', 'basic2-draft', 'private-draft', 'private-pub'), array('basic-pub') ) ); // if no selection is provided, ensure all items are shown $this->_verifyWorkflowFilter( array( 'workflow' => 'userSelected' ), array( P4Cms_Content::fetchAll()->invoke('getTitle'), P4Cms_Content::fetchAll()->invoke('getTitle') ) ); } /** * Test that proper icon for category is rendered (i.e. depending on category content). */ public function testCategoryIcons() { // create entry in unpublished state $entry = $this->_makeContentEntry('draft', 'foo'); // create category A with sub-category B containing the entry $categoryA = Category_Model_Category::store( array( 'id' => 'a', 'title' => 'A', ) ); $categoryB = Category_Model_Category::create( array( 'id' => 'a/b', 'title' => 'B', ) )->addEntry($entry->getId())->save(); // verify that member see icon representing empty folder for category B // as member cannot see unpublished content $this->utility->impersonate('member'); $this->dispatch('/category/view/a'); $this->assertQueryContentRegex("a[href='/category/view/a/b']", "/src=.+folder\-64x64/"); // add access to unpublished content and verify category icon again $acl = P4Cms_Site::fetchActive()->getAcl(); $acl->allow('member', 'content', array('access-unpublished')); $this->resetRequest()->resetResponse(); $this->dispatch('/category/view/a'); $this->assertQueryContentRegex("a[href='/category/view/a/b']", "/src=.+folder\-content\-64x64/"); } /** * Helper function to verify filtering by workflow. * It automatically processes 3 cases - when filters are applied to the current, * scheduled or both states. * * Workflow data passed in the parameter are assumed to contain values from the * worfklow form, except 'targetState' that is added automatically. * * Expected entries are assumed to contain 2 or 3 lists of expected entry titles * when filters (represented by workflow data) are applied to the current state, * to the scheduled state and optionally to the both. If thirs list is omitted then * it will be calculated as union of the two for current and scheduled. * * @param array $workflowData values of the workflow form representing filters. * @param array $expectedEntries 2- or 3-dimensional array containing lists * of expected entry titles when filters are applied * to the, in order, current state, scheduled state * and both. */ protected function _verifyWorkflowFilter(array $workflowData, array $expectedEntries) { $expectedEntries = array_values($expectedEntries); if (count($expectedEntries) === 2) { // add expected result for applied to 'current or scheduled' state as // union of the two $expectedEntries[] = array_merge( $expectedEntries[0], array_diff($expectedEntries[1], $expectedEntries[0]) ); } $targetStates = array('current', 'scheduled', 'either'); foreach ($expectedEntries as $key => $expected) { // prepare post data and inject target state $postData = array('workflow' => $workflowData); $postData['workflow']['targetState'] = $targetStates[$key]; // prepare request $this->resetRequest()->resetResponse(); $this->request ->setMethod('post') ->setPost($postData); // dispatch $this->dispatch('/browse/format/json'); $body = $this->getResponse()->getBody(); $data = Zend_Json::decode($body); // verify response $this->_verifyTitleValuesMatch($data['items'], $expected); } } /** * Verifies that titles in $source array match values in $titleValues. * * @param array $source array to compare with $titleValues, assuming array items contain * 'title' key * @param array $titleValues array to compare to $source */ protected function _verifyTitleValuesMatch(array $source, array $titleValues) { // extract title values from source $sourceTitle = array_map( function($item) { return isset($item['title']) ? $item['title'] : null; }, $source ); $match = (count($source) === count($titleValues)) && count(array_diff($titleValues, $sourceTitle)) === 0; $this->assertTrue( $match, "Expected items after filtering by worfklow:" . "\nFiltered entry titles: " . print_r($sourceTitle, true) . "\nExpected entry titles: " . print_r($titleValues, true) ); } /** * Create a test content entry in the given state and optionally set scheduled transition data. * * @param string $state optional - the state to put the entry in. * @param string $owner optional - username to set as entry owner. * @param string $scheduledState optional - state of the scheduled transition to * set the entry on. * @param string $scheduledTime optional - time of the scheduled transition to * set the entry on. * @return P4Cms_Content created content entry instance */ protected function _makeContentEntry($state = 'draft', $owner = '', $scheduledState = null, $scheduledTime = null ) { return P4Cms_Content::store( array( 'contentType' => 'basic-page', 'contentOwner' => $owner, 'title' => 'test-title', 'body' => 'test-body', 'workflowState' => $state, 'workflowScheduledState' => $scheduledState, 'workflowScheduledTime' => $scheduledTime ) ); } /** * Verify that all expected states are present in the workflow sub-form as state radio options. * If selected state parameter is present, also ensure selected option. * * @param array $expectedStates list with states for state radio element. * @param string $selectedState state option assumed to be selected. */ protected function _verifyWorkflowStatesRadio(array $expectedStates, $selectedState = null) { $statesImploded = implode(', ', $expectedStates); // verify number of radio options $expectedTransitionsCount = count($expectedStates); $this->assertQueryCount( '#workflow-state-element input[type="radio"]', $expectedTransitionsCount, "Expected state radio has " . $expectedTransitionsCount . " options: " . $statesImploded . "." ); // verify radio option values $message = 'Expected states: [' . $statesImploded . ']'; if ($selectedState) { $message .= ' with [' . $selectedState . '] selected'; } foreach ($expectedStates as $state) { $query = '#workflow-state-element input[type="radio"][value="' . $state . '"]'; if ($selectedState === $state) { $query .= '[checked="checked"]'; } $this->assertQuery($query, $message); } } }