diff --git a/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder.css b/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder.css index 7e1957833e..9245dfbac3 100644 --- a/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder.css +++ b/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder.css @@ -3,6 +3,7 @@ --vads-color-divider: #a9aeb1; --color-shadow: #00000040; + color: var(--vads-color-base); font-family: var(--font-family-serif); } @@ -85,6 +86,7 @@ a.form-builder-button { border-radius: var(--units-0p5); cursor: pointer; + font-family: var(--font-source-sans); font-weight: var(--font-weight-bold); padding: var(--units-1p5) var(--units-2p5); text-decoration: none; @@ -106,3 +108,20 @@ a.form-builder-button--primary:hover { color: var(--vads-button-color-text-primary-on-light); text-decoration: none; } + +.form-builder-button--secondary, +.form-builder-button--secondary:focus, +a.form-builder-button--secondary, +a.form-builder-button--secondary:focus { + background: rgba(0, 0, 0, 0); + border: var(--units-0p25) solid var(--vads-color-primary); + color: var(--vads-color-primary); + text-decoration: none; +} + +.form-builder-button--secondary:hover, +a.form-builder-button--secondary:hover { + background: #dce4ef; /*secondary-button hover color missing from tokens*/ + color: var(--vads-color-primary); + text-decoration: none; +} diff --git a/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder__layout.css b/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder__layout.css new file mode 100644 index 0000000000..66ef20f45a --- /dev/null +++ b/docroot/modules/custom/va_gov_form_builder/css/va_gov_form_builder__layout.css @@ -0,0 +1,68 @@ +/* Page content*/ +.form-builder-page-content--layout { + width: 620px; +} + +.form-builder-content__page-help-text { + font-family: var(--font-source-sans); + margin-bottom: var(--units-6); +} + +/* Step groups*/ +.form-builder-layout-step-group { + font-family: var(--font-source-sans); + margin-top: var(--units-4); +} + +.form-builder-layout-step-group__heading { + font-family: var(--font-family-serif); + margin-bottom: var(--units-5); +} + +.form-builder-layout-step-group__heading--viewing-form { + margin-bottom: var(--units-2); +} + +/* Individual steps */ +.form-builder-layout-step { + border-bottom: var(--units-1px) solid var(--vads-color-base-light); + margin: var(--units-3) 0 0 0; + padding: 0 0 var(--units-3) var(--units-3); + position: relative; +} + +.form-builder-layout-step-group > .form-builder-layout-step:last-child { + border-bottom: 0; +} + +.form-builder-layout-step::before { + border-radius: var(--units-0p25); + color: var(--vads-color-white); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-normal); + padding: var(--units-1px) var(--units-1); + position: absolute; + right: 0; + top: 0; +} + +.form-builder-layout-step--complete::before { + background-color: var( + --vads-button-color-background-primary-alt-active-on-light + ); + content: "COMPLETE"; +} + +.form-builder-layout-step--incomplete::before { + background-color: var(--vads-color-error-darker); + content: "INCOMPLETE"; +} + +.form-builder-layout-step__help-text { + color: var(--vads-color-gray-medium); +} + +.form-builder-layout-step__view-link { + display: inline-block; + margin-top: var(--units-3); +} diff --git a/docroot/modules/custom/va_gov_form_builder/src/Controller/VaGovFormBuilderController.php b/docroot/modules/custom/va_gov_form_builder/src/Controller/VaGovFormBuilderController.php index 80c434504c..732edcab76 100644 --- a/docroot/modules/custom/va_gov_form_builder/src/Controller/VaGovFormBuilderController.php +++ b/docroot/modules/custom/va_gov_form_builder/src/Controller/VaGovFormBuilderController.php @@ -36,6 +36,13 @@ class VaGovFormBuilderController extends ControllerBase { */ const LIBRARY_PREFIX = 'va_gov_form_builder/va_gov_form_builder_styles__'; + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + /** * The Drupal form builder. * @@ -51,16 +58,16 @@ class VaGovFormBuilderController extends ControllerBase { protected $digitalFormsService; /** - * The Digital Form node. + * The Digital Form object. * * When the page in question edits or references an existing * digital form node, this property is populated. When the * page creates a new digital form node or otherwise does * not reference a node, this is empty. * - * @var \Drupal\node\Entity\Node|null + * @var \Drupal\va_gov_form_builder\EntityWrapper\DigitalForm|null */ - protected $digitalFormNode; + protected $digitalForm; /** * {@inheritdoc} @@ -68,6 +75,7 @@ class VaGovFormBuilderController extends ControllerBase { public static function create(ContainerInterface $container) { $instance = parent::create($container); + $instance->entityTypeManager = $container->get('entity_type.manager'); $instance->drupalFormBuilder = $container->get('form_builder'); $instance->digitalFormsService = $container->get('va_gov_form_builder.digital_forms_service'); @@ -75,7 +83,7 @@ public static function create(ContainerInterface $container) { } /** - * Loads and sets the Digital Form node. + * Loads and sets the Digital Form object. * * @param string $nid * The node id to load. @@ -83,10 +91,9 @@ public static function create(ContainerInterface $container) { * @return bool * TRUE if successfully loaded. FALSE otherwise. */ - protected function loadDigitalFormNode($nid) { - $digitalFormNode = $this->digitalFormsService->getDigitalForm($nid); - if (!empty($digitalFormNode)) { - $this->digitalFormNode = $digitalFormNode; + protected function loadDigitalForm($nid) { + $this->digitalForm = $this->digitalFormsService->getDigitalForm($nid); + if ($this->digitalForm) { return TRUE; } @@ -108,6 +115,11 @@ protected function getPage($pageContent, $subtitle, $libraries = NULL) { $page = [ '#type' => 'page', 'content' => $pageContent, + '#cache' => [ + // Do not cache Form Builder pages. + // @todo Make caching more granular/contextual. + 'max-age' => 0, + ], // Add custom data. 'form_builder_page_data' => [ 'subtitle' => $subtitle, @@ -142,7 +154,7 @@ protected function getPage($pageContent, $subtitle, $libraries = NULL) { */ protected function getFormPage($formName, $subtitle, $libraries = NULL) { // @phpstan-ignore-next-line - $form = $this->drupalFormBuilder->getForm('Drupal\va_gov_form_builder\Form\\' . $formName, $this->digitalFormNode); + $form = $this->drupalFormBuilder->getForm('Drupal\va_gov_form_builder\Form\\' . $formName, $this->digitalForm); return $this->getPage($form, $subtitle, $libraries); } @@ -184,7 +196,7 @@ public function home() { /** * Form-info page. * - * @param string $nid + * @param string|null $nid * The node id, passed in when the page edits an existing node. */ public function formInfo($nid = NULL) { @@ -194,7 +206,7 @@ public function formInfo($nid = NULL) { if (!empty($nid)) { // This is an edit. - $nodeFound = $this->loadDigitalFormNode($nid); + $nodeFound = $this->loadDigitalForm($nid); if (!$nodeFound) { throw new NotFoundHttpException(); } @@ -203,13 +215,82 @@ public function formInfo($nid = NULL) { return $this->getFormPage($formName, $subtitle, $libraries); } + /** + * Layout page. + * + * @param string $nid + * The node id of the Digital Form. + */ + public function layout($nid) { + $nodeFound = $this->loadDigitalForm($nid); + if (!$nodeFound) { + throw new NotFoundHttpException(); + } + + $pageContent = [ + '#theme' => self::PAGE_CONTENT_THEME_PREFIX . 'layout', + '#form_info' => [ + 'status' => $this->digitalForm->getStepStatus('form_info'), + 'url' => Url::fromRoute('va_gov_form_builder.form_info.edit', ['nid' => $nid])->toString(), + ], + '#intro' => [ + 'status' => $this->digitalForm->getStepStatus('intro'), + 'url' => '', + ], + '#your_personal_info' => [ + 'status' => $this->digitalForm->getStepStatus('your_personal_info'), + 'url' => '', + ], + '#address_info' => [ + 'status' => $this->digitalForm->getStepStatus('address_info'), + 'url' => '', + ], + '#contact_info' => [ + 'status' => $this->digitalForm->getStepStatus('contact_info'), + 'url' => '', + ], + '#additional_steps' => [ + 'steps' => array_map(function ($step) { + return [ + // If an additional step exists, it's complete. + 'type' => $step['type'], + 'title' => $step['fields']['field_title'][0]['value'], + 'status' => 'complete', + 'url' => '', + ]; + }, $this->digitalForm->getNonStandarddSteps()), + 'add_step' => [ + 'url' => '', + ], + ], + '#review_and_sign' => [ + 'status' => $this->digitalForm->getStepStatus('review_and_sign'), + 'url' => '', + ], + '#confirmation' => [ + 'status' => $this->digitalForm->getStepStatus('confirmation'), + 'url' => '', + ], + '#view_form' => [ + 'url' => '', + ], + ]; + $subtitle = $this->digitalForm->getTitle(); + $libraries = ['layout']; + + return $this->getPage($pageContent, $subtitle, $libraries); + } + /** * Name-and-date-of-birth page. + * + * @param string $nid + * The node id of the Digital Form. */ public function nameAndDob($nid) { $formName = 'NameAndDob'; $subtitle = 'Subtitle Placeholder'; - $nodeFound = $this->loadDigitalFormNode($nid); + $nodeFound = $this->loadDigitalForm($nid); if (!$nodeFound) { throw new NotFoundHttpException(); } diff --git a/docroot/modules/custom/va_gov_form_builder/src/EntityWrapper/DigitalForm.php b/docroot/modules/custom/va_gov_form_builder/src/EntityWrapper/DigitalForm.php new file mode 100644 index 0000000000..6c581665ea --- /dev/null +++ b/docroot/modules/custom/va_gov_form_builder/src/EntityWrapper/DigitalForm.php @@ -0,0 +1,206 @@ + 'digital_form_your_personal_info', + 'address_info' => 'digital_form_address', + 'contact_info' => 'digital_form_phone_and_email', + ]; + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The Digital Form node. + * + * @var \Drupal\node\NodeInterface + */ + private $node; + + /** + * Constructs a DigitalForm object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\node\NodeInterface $node + * The Digital Form node to wrap. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, NodeInterface $node) { + $this->entityTypeManager = $entity_type_manager; + + if ($node->getType() !== 'digital_form') { + throw new \InvalidArgumentException('The node must be of type "digital_form".'); + } + + $this->node = $node; + } + + /** + * Magic method to forward other method calls to the node. + * + * This makes it so we can call, for example, $wrappedNode->getTitle(). + * These methods are annotated in the class comment above. + * + * @param string $name + * The name of the method being called. + * @param array $arguments + * The arguments passed to the method. + * + * @return mixed + * The return value of the method called on the node. + */ + public function __call($name, $arguments) { + if (method_exists($this->node, $name)) { + return call_user_func_array([$this->node, $name], $arguments); + } + + throw new \BadMethodCallException("Method $name does not exist on the underlying node class."); + } + + /** + * Returns an array of all steps added to the form. + * + * @return array + * A collection of all steps. + */ + public function getAllSteps() { + $steps = []; + + if ($this->node->hasField('field_chapters')) { + $chapters = $this->node->get('field_chapters')->getValue(); + + foreach ($chapters as $chapter) { + $paragraph = $this->entityTypeManager + ->getStorage('paragraph') + ->load($chapter['target_id']); + + if ($paragraph) { + $steps[] = [ + 'type' => $paragraph->bundle(), + 'fields' => array_map(function ($field) { + return $field->getValue(); + }, $paragraph->getFields()), + ]; + } + } + } + + return $steps; + } + + /** + * Returns an array of all steps that are not standard steps. + * + * @return array + * A collection of non-standard steps. + */ + public function getNonStandarddSteps() { + $allSteps = $this->getAllSteps(); + $nonStandardSteps = array_values(array_filter($allSteps, function ($step) { + return !in_array($step['type'], array_values(self::STANDARD_STEPS)); + })); + + return $nonStandardSteps; + } + + /** + * Determines if the Digital Form node has a chapter of a given type. + * + * If the node has a chapter (paragraph) of the given type, returns TRUE. + * Otherwise, returns FALSE. + * + * @param string $type + * The chapter (paragraph) type. + * + * @return bool + * TRUE if the chapter exists; FALSE if the chapter + * does not exist. + */ + public function hasChapterOfType($type) { + if ($this->node->hasField('field_chapters') && !$this->node->get('field_chapters')->isEmpty()) { + $chapters = $this->node->get('field_chapters')->getValue(); + + foreach ($chapters as $chapter) { + if (isset($chapter['target_id'])) { + $paragraph = $this->entityTypeManager->getStorage('paragraph')->load($chapter['target_id']); + + if ($paragraph) { + if ($paragraph->bundle() === $type) { + return TRUE; + } + } + } + } + } + + return FALSE; + } + + /** + * Returns the status of a step on the Digital Form node. + * + * Completeness of the step varies by step, and is documented + * in the function body. + * + * @param string $stepName + * The step name of the step in question. + * + * @return 'complete'|'incomplete' + * Returns 'complete' if step is complete. + * Returns 'incomplete if step is incomplete + * or if the step name does not exist. + */ + public function getStepStatus($stepName) { + if ($stepName === 'form_info') { + // If the node exists, this will necessarily be complete. + return 'complete'; + } + + if ($stepName === 'review_and_sign') { + // This is added automatically by the Forms Library. + return 'complete'; + } + + if (in_array($stepName, [ + 'intro', + 'confirmation', + ])) { + // These haven't been handled yet. + // Return 'incomplete' for the time being. + return 'incomplete'; + } + + // Standard steps are complete if a corresponding chapter exists. + if (array_key_exists($stepName, self::STANDARD_STEPS)) { + $paragraphName = self::STANDARD_STEPS[$stepName]; + return $this->hasChapterOfType($paragraphName) + ? 'complete' + : 'incomplete'; + } + + return 'incomplete'; + } + +} diff --git a/docroot/modules/custom/va_gov_form_builder/src/Form/Base/FormBuilderBase.php b/docroot/modules/custom/va_gov_form_builder/src/Form/Base/FormBuilderBase.php index 2e3d163837..8303b2cc0e 100644 --- a/docroot/modules/custom/va_gov_form_builder/src/Form/Base/FormBuilderBase.php +++ b/docroot/modules/custom/va_gov_form_builder/src/Form/Base/FormBuilderBase.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\va_gov_form_builder\Service\DigitalFormsService; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -20,41 +21,49 @@ abstract class FormBuilderBase extends FormBase { protected $entityTypeManager; /** - * The Digital Form node created or loaded by this form step. + * The Digital Forms service. * - * @var \Drupal\node\Entity\Node + * @var \Drupal\va_gov_form_builder\Service\DigitalFormsService */ - protected $digitalFormNode; + protected $digitalFormsService; /** - * Flag indicating if the node has been changed. + * The DigitalForm object created or loaded by this form step. * - * Indicates if the node has been changed + * @var \Drupal\va_gov_form_builder\EntityWrapper\DigitalForm + */ + protected $digitalForm; + + /** + * Flag indicating if the Digital Form has been changed. + * + * Indicates if the Digital Form has been changed * since the form was first instantiated. * * @var bool */ - protected $digitalFormNodeIsChanged; + protected $digitalFormIsChanged; /** - * Flag indicating whether this form allows an empty node. + * Flag indicating whether this form allows an empty DigitalForm object. * - * This defaults to FALSE. The only time an empty node + * This defaults to FALSE. The only time an empty object * should be allowed is on the form that creates * the node for the first time. Every other form should - * operate on an existing form and should require a - * node to be populated. + * operate on an existing form and should require an + * object to be populated. * * @var bool */ - protected $allowEmptyDigitalFormNode; + protected $allowEmptyDigitalForm; /** * {@inheritDoc} */ - public function __construct(EntityTypeManagerInterface $entityTypeManager) { + public function __construct(EntityTypeManagerInterface $entityTypeManager, DigitalFormsService $digitalFormsService) { $this->entityTypeManager = $entityTypeManager; - $this->allowEmptyDigitalFormNode = FALSE; + $this->digitalFormsService = $digitalFormsService; + $this->allowEmptyDigitalForm = FALSE; } /** @@ -62,7 +71,8 @@ public function __construct(EntityTypeManagerInterface $entityTypeManager) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('va_gov_form_builder.digital_forms_service') ); } @@ -72,32 +82,32 @@ public static function create(ContainerInterface $container) { abstract protected function getFields(); /** - * Sets (creates or updates) a Digital Form node from the form-state data. + * Sets (creates or updates) a DigitalForm object from the form-state data. */ - abstract protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterface $form_state); + abstract protected function setDigitalFormFromFormState(array &$form, FormStateInterface $form_state); /** - * Returns a field value from the Digital Form node. + * Returns a field value from the Digital Form. * - * If Digital Form node is not set, or `fieldName` + * If Digital Form is not set, or `fieldName` * does not exist, returns NULL. This is primarily * used to populate forms with default values when the - * form edits an existing Digital Form node. + * form edits an existing Digital Form. * * @param string $fieldName * The name of the field whose value should be fetched. */ - protected function getDigitalFormNodeFieldValue($fieldName) { - if (empty($this->digitalFormNode)) { + protected function getDigitalFormFieldValue($fieldName) { + if (empty($this->digitalForm)) { return NULL; } try { if ($fieldName === 'title') { - return $this->digitalFormNode->getTitle(); + return $this->digitalForm->getTitle(); } - return $this->digitalFormNode->get($fieldName)->value; + return $this->digitalForm->get($fieldName)->value; } catch (\Exception $e) { return NULL; @@ -107,58 +117,27 @@ protected function getDigitalFormNodeFieldValue($fieldName) { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $node = NULL) { + public function buildForm(array $form, FormStateInterface $form_state, $digitalForm = NULL) { // When form is first built, initialize flag to false. - $this->digitalFormNodeIsChanged = FALSE; + $this->digitalFormIsChanged = FALSE; - if (empty($node) && !$this->allowEmptyDigitalFormNode) { - throw new \InvalidArgumentException('Digital Form node cannot be null.'); + if (empty($digitalForm) && !$this->allowEmptyDigitalForm) { + throw new \InvalidArgumentException('Digital Form cannot be null.'); } - $this->digitalFormNode = $node; + $this->digitalForm = $digitalForm; return $form; } - /** - * Determines if `digitalFormNode` has a chapter (paragraph) of a given type. - * - * @param string $type - * The chapter (paragraph) type. - * - * @return bool - * TRUE if the chapter exists; FALSE if the chapter - * does not exist or the node does not exist. - */ - protected function digitalFormNodeHasChapterOfType($type) { - if (empty($this->digitalFormNode)) { - return FALSE; - } - - $chapters = $this->digitalFormNode->get('field_chapters')->getValue(); - - foreach ($chapters as $chapter) { - if (isset($chapter['target_id'])) { - $paragraph = $this->entityTypeManager->getStorage('paragraph')->load($chapter['target_id']); - if ($paragraph) { - if ($paragraph->bundle() === $type) { - return TRUE; - } - } - } - } - - return FALSE; - } - /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { - $this->setDigitalFormNodeFromFormState($form, $form_state); + $this->setDigitalFormFromFormState($form, $form_state); // Validate the node entity. /** @var \Symfony\Component\Validator\ConstraintViolationListInterface $violations */ - $violations = $this->digitalFormNode->validate(); + $violations = $this->digitalForm->validate(); // Loop through each violation and set errors on the form. if ($violations->count() > 0) { @@ -179,8 +158,8 @@ public function validateForm(array &$form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - // Save the previously validated node. - $this->digitalFormNode->save(); + // Save the previously validated Digital Form. + $this->digitalForm->save(); } } diff --git a/docroot/modules/custom/va_gov_form_builder/src/Form/FormInfo.php b/docroot/modules/custom/va_gov_form_builder/src/Form/FormInfo.php index 8b54bf3b56..3bcc577bae 100644 --- a/docroot/modules/custom/va_gov_form_builder/src/Form/FormInfo.php +++ b/docroot/modules/custom/va_gov_form_builder/src/Form/FormInfo.php @@ -16,7 +16,7 @@ class FormInfo extends FormBuilderBase { * Flag indicating if the form mode is "create". * * Form mode is "create", and this value is TRUE, - * if no node id is passed in representing an existing node. + * if no Digital Form is passed in representing an existing Digital Form. * * Form mode is "edit" otherwise, and this value is FALSE. * @@ -47,18 +47,18 @@ protected function getFields() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $node = NULL) { - // On this form, the Digital Form node should be allowed to be empty, + public function buildForm(array $form, FormStateInterface $form_state, $digitalForm = NULL) { + // On this form, the Digital Form should be allowed to be empty, // to accommodate the case where it is in "create" mode. - $this->allowEmptyDigitalFormNode = TRUE; - $form = parent::buildForm($form, $form_state, $node); + $this->allowEmptyDigitalForm = TRUE; + $form = parent::buildForm($form, $form_state, $digitalForm); - if (empty($node)) { - // If no node is passed in, this is "create" mode. + if (empty($digitalForm)) { + // If no Digital Form is passed in, this is "create" mode. $this->isCreate = TRUE; } else { - // If a node is passed in, this is "edit" mode. + // If a Digital Form is passed in, this is "edit" mode. $this->isCreate = FALSE; } @@ -68,14 +68,14 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N '#type' => 'textfield', '#title' => $this->t('Form Name'), '#required' => TRUE, - '#default_value' => $this->getDigitalFormNodeFieldValue('title'), + '#default_value' => $this->getDigitalFormFieldValue('title'), ]; $form['field_va_form_number'] = [ '#type' => 'textfield', '#title' => $this->t('Form Number'), '#required' => TRUE, - '#default_value' => $this->getDigitalFormNodeFieldValue('field_va_form_number'), + '#default_value' => $this->getDigitalFormFieldValue('field_va_form_number'), ]; $form['field_omb_number'] = [ @@ -83,7 +83,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N '#title' => $this->t('OMB number'), '#description' => $this->t('Insert the OMB number (format: xxxx-xxxx)'), '#required' => TRUE, - '#default_value' => $this->getDigitalFormNodeFieldValue('field_omb_number'), + '#default_value' => $this->getDigitalFormFieldValue('field_omb_number'), ]; $form['field_respondent_burden'] = [ @@ -91,7 +91,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N '#title' => $this->t('Respondent burden'), '#description' => $this->t('Number of minutes as indicated on the form'), '#required' => TRUE, - '#default_value' => $this->getDigitalFormNodeFieldValue('field_respondent_burden'), + '#default_value' => $this->getDigitalFormFieldValue('field_respondent_burden'), ]; $form['field_expiration_date'] = [ @@ -99,7 +99,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N '#title' => $this->t('Expiration date'), '#description' => $this->t('Form expiration date as indicated on the form'), '#required' => TRUE, - '#default_value' => $this->getDigitalFormNodeFieldValue('field_expiration_date'), + '#default_value' => $this->getDigitalFormFieldValue('field_expiration_date'), ]; $form['actions']['save_and_continue'] = [ @@ -122,7 +122,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N /** * {@inheritdoc} */ - protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterface $form_state) { + protected function setDigitalFormFromFormState(array &$form, FormStateInterface $form_state) { $title = $form_state->getValue('title'); $vaFormNumber = $form_state->getValue('field_va_form_number'); $ombNumber = $form_state->getValue('field_omb_number'); @@ -131,11 +131,14 @@ protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterf if ($this->isCreate) { /* - * This form is creating a new node. + * This form is creating a new Digital Form. * - * We can simply create the new node with the fields from this form. + * We need to create a new Digital Form by doing two things: + * 1. Create the new Digital Form with the fields from this form. + * 2. Add default "Your personal information" step. */ - $this->digitalFormNode = $this->entityTypeManager->getStorage('node')->create([ + // 1. Create the new Digital Form with the fields from this form. + $node = $this->entityTypeManager->getStorage('node')->create([ 'type' => 'digital_form', 'title' => $title, 'field_va_form_number' => $vaFormNumber, @@ -143,19 +146,43 @@ protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterf 'field_respondent_burden' => $respondentBurden, 'field_expiration_date' => $expirationDate, ]); + + // 2. Add "Your personal information" step (paragraph). + // This step contains two sub-steps: + // --> Name and date of birth + $nameAndDobParagraph = $this->entityTypeManager->getStorage('paragraph')->create([ + 'type' => 'digital_form_name_and_date_of_bi', + 'field_title' => 'Name and date of birth', + 'field_include_date_of_birth' => TRUE, + ]); + // --> Identification information + $identificationInfo = $this->entityTypeManager->getStorage('paragraph')->create([ + 'type' => 'digital_form_identification_info', + 'field_title' => 'Identifying information', + 'field_include_veteran_s_service' => TRUE, + ]); + // Your personal information wraps both. + $yourPersonalInformation = $this->entityTypeManager->getStorage('paragraph')->create([ + 'type' => 'digital_form_your_personal_info', + 'field_name_and_date_of_birth' => $nameAndDobParagraph, + 'field_identification_information' => $identificationInfo, + ]); + $node->get('field_chapters')->appendItem($yourPersonalInformation); + + $this->digitalForm = $this->digitalFormsService->wrapDigitalForm($node); } else { /* - * This form is editing an existing node. + * This form is editing an existing Digital Form. * * We need to update only the fields from this form, * ensuring other fields are not changed. */ - $this->digitalFormNode->set('title', $title); - $this->digitalFormNode->set('field_va_form_number', $vaFormNumber); - $this->digitalFormNode->set('field_omb_number', $ombNumber); - $this->digitalFormNode->set('field_respondent_burden', $respondentBurden); - $this->digitalFormNode->set('field_expiration_date', $expirationDate); + $this->digitalForm->set('title', $title); + $this->digitalForm->set('field_va_form_number', $vaFormNumber); + $this->digitalForm->set('field_omb_number', $ombNumber); + $this->digitalForm->set('field_respondent_burden', $respondentBurden); + $this->digitalForm->set('field_expiration_date', $expirationDate); } } @@ -165,8 +192,8 @@ protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterf public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); - $form_state->setRedirect('va_gov_form_builder.name_and_dob', [ - 'nid' => $this->digitalFormNode->id(), + $form_state->setRedirect('va_gov_form_builder.layout', [ + 'nid' => $this->digitalForm->id(), ]); } diff --git a/docroot/modules/custom/va_gov_form_builder/src/Form/NameAndDob.php b/docroot/modules/custom/va_gov_form_builder/src/Form/NameAndDob.php index e1c00d792b..169efc6f37 100644 --- a/docroot/modules/custom/va_gov_form_builder/src/Form/NameAndDob.php +++ b/docroot/modules/custom/va_gov_form_builder/src/Form/NameAndDob.php @@ -42,8 +42,8 @@ protected function getFields() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $node = NULL) { - $form = parent::buildForm($form, $form_state, $node); + public function buildForm(array $form, FormStateInterface $form_state, $digitalForm = NULL) { + $form = parent::buildForm($form, $form_state, $digitalForm); $form['name_and_dob_header'] = [ '#type' => 'html_tag', @@ -124,9 +124,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $node = N /** * {@inheritdoc} */ - protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterface $form_state) { + protected function setDigitalFormFromFormState(array &$form, FormStateInterface $form_state) { // Do not add the name-and-dob chapter if there's one already present. - if ($this->digitalFormNodeHasChapterOfType($this->chapterType)) { + if ($this->digitalForm->hasChapterOfType($this->chapterType)) { return; } @@ -135,10 +135,10 @@ protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterf 'field_title' => $form_state->getValue('step_name'), 'field_include_date_of_birth' => TRUE, ]); - $this->digitalFormNode->get('field_chapters')->appendItem($nameAndDobParagraph); + $this->digitalForm->get('field_chapters')->appendItem($nameAndDobParagraph); - // Node has been changed. - $this->digitalFormNodeIsChanged = TRUE; + // Digital Form has been changed. + $this->digitalFormIsChanged = TRUE; } /** @@ -147,7 +147,7 @@ protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterf public function backButtonSubmitHandler(array &$form, FormStateInterface $form_state) { // This will almost certainly change. $form_state->setRedirect('va_gov_form_builder.form_info.edit', [ - 'nid' => $this->digitalFormNode->id(), + 'nid' => $this->digitalForm->id(), ]); } @@ -155,15 +155,15 @@ public function backButtonSubmitHandler(array &$form, FormStateInterface $form_s * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - // Only save the form if there have been changes to the node. - if ($this->digitalFormNodeIsChanged) { + // Only save the form if there have been changes to the Digital Form. + if ($this->digitalFormIsChanged) { parent::submitForm($form, $form_state); } // For now, redirect to the default node-edit form // to confirm creation of the node. $form_state->setRedirect('entity.node.edit_form', [ - 'node' => $this->digitalFormNode->id(), + 'node' => $this->digitalForm->id(), ]); } diff --git a/docroot/modules/custom/va_gov_form_builder/src/Service/DigitalFormsService.php b/docroot/modules/custom/va_gov_form_builder/src/Service/DigitalFormsService.php index e14482c5ec..81f346afe7 100644 --- a/docroot/modules/custom/va_gov_form_builder/src/Service/DigitalFormsService.php +++ b/docroot/modules/custom/va_gov_form_builder/src/Service/DigitalFormsService.php @@ -3,11 +3,16 @@ namespace Drupal\va_gov_form_builder\Service; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\va_gov_form_builder\EntityWrapper\DigitalForm; /** - * Service for handling Digital Form nodes. + * Service for fetching and creating Digital Forms. + * + * Digital Form nodes are wrapped in DigitalForm + * entity-wrapper objects before being returned. */ class DigitalFormsService { + /** * The entity type manager service. * @@ -46,7 +51,13 @@ public function getDigitalForms($publishedOnly = TRUE) { $nids = $query->execute(); if (!empty($nids)) { - return $this->entityTypeManager->getStorage('node')->loadMultiple($nids); + $nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids); + $digitalForms = []; + foreach ($nodes as $node) { + $digitalForms[] = new DigitalForm($this->entityTypeManager, $node); + } + + return $digitalForms; } return []; } @@ -61,7 +72,31 @@ public function getDigitalForms($publishedOnly = TRUE) { * A node object of type 'digital_form', or NULL if not found. */ public function getDigitalForm($nid) { - return $this->entityTypeManager->getStorage('node')->load($nid); + $node = $this->entityTypeManager->getStorage('node')->load($nid); + + return $this->wrapDigitalForm($node); + } + + /** + * Returns a DigitalForm object from a passed-in Digital Form node. + * + * @param \Drupal\node\NodeInterface $node + * The `digital_form` node to wrap. + * + * @return \Drupal\va_gov_form_builder\EntityWrapper\DigitalForm + * The DigitalForm object wrapping the passed-in $node. + */ + public function wrapDigitalForm($node) { + if (!$node) { + return NULL; + } + + // Only return the node if it is a Digital Form node. + if ($node->getType() !== 'digital_form') { + return NULL; + } + + return new DigitalForm($this->entityTypeManager, $node); } } diff --git a/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--home.html.twig b/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--home.html.twig index 83702f0936..1608478d32 100644 --- a/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--home.html.twig +++ b/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--home.html.twig @@ -24,7 +24,7 @@
  • {{ form.title }} (VA Form {{ form.formNumber }}) diff --git a/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--layout.html.twig b/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--layout.html.twig new file mode 100644 index 0000000000..0f9a22c500 --- /dev/null +++ b/docroot/modules/custom/va_gov_form_builder/templates/page-content/page-content--va-gov-form-builder--layout.html.twig @@ -0,0 +1,88 @@ +
    +

    Build this form

    +

    Use this page to access and edit any content of this form or add additional steps as needed.

    + +
    +

    Form overview

    +
    +

    Form info

    +

    In this section: the form is named, the OMB information is provided and a plain language header is provided for the submitter.

    + View form info +
    +
    +

    Introduction page

    +

    There are two paragraphs used to inform the user of the purpose of this form and the information needed to complete it.

    + View introduction page +
    +
    + +
    +

    Form steps

    +
    +

    Step 1: Your personal information

    +

    No additional editing is needed for this step.

    + View personal information +
    +
    +

    Step 2: Address information

    +

    + Select the Step label for this item.
    + Additional hint text can also be provided. +

    + View address information +
    +
    +

    Step 3: Contact information

    +

    This step includes email and phone number collection from the submitter.

    + View contact information +
    + + {% for additional_step in additional_steps.steps %} +
    +

    Step {{ loop.index + 3 }}: {{ additional_step.title }}

    +

    Click the link below to view this step.

    + View {{ additional_step.title | lower }} +
    + {% endfor %} + +
    +

    Add a step

    +

    To allow this form to collect additional information, select Add a step to access additional questions to add for the submitter.

    + + Add a step + +
    +
    + +
    +

    Submission and next steps

    +
    +

    Review and sign

    +

    No additional editing is needed for this step.

    + View review and sign page +
    +
    +

    Confirmation page

    +

    Upon successful submission of the form a confirmation message is displayed on this page.

    + View confirmation page +
    +
    + +
    +

    Viewing the form

    +

    Once all of the steps are complete, this form can be viewed by selecting View form.

    + + View form + +
    +
    diff --git a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.libraries.yml b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.libraries.yml index 96d60c3973..25b469e12f 100644 --- a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.libraries.yml +++ b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.libraries.yml @@ -15,3 +15,8 @@ va_gov_form_builder_styles__form_info: css: theme: css/va_gov_form_builder__form_info.css: {} +va_gov_form_builder_styles__layout: + version: 1.x + css: + theme: + css/va_gov_form_builder__layout.css: {} diff --git a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.module b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.module index 680a7955a1..5e5b16bdaa 100644 --- a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.module +++ b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.module @@ -33,7 +33,6 @@ function va_gov_form_builder_theme($_existing, $_type, $_theme, $path) { // Add page-content themes. $page_content_theme_prefix = 'page_content__va_gov_form_builder__'; $page_content_theme_path = $path . '/templates/page-content'; - // 1. Home page $theme[$page_content_theme_prefix . 'home'] = [ 'path' => $page_content_theme_path, @@ -42,6 +41,49 @@ function va_gov_form_builder_theme($_existing, $_type, $_theme, $path) { 'build_form_url' => '', ], ]; + // 2. Layout page + $theme[$page_content_theme_prefix . 'layout'] = [ + 'path' => $page_content_theme_path, + 'variables' => [ + 'form_info' => [ + 'status' => '', + 'url' => '', + ], + 'intro' => [ + 'status' => '', + 'url' => '', + ], + 'your_personal_info' => [ + 'status' => '', + 'url' => '', + ], + 'address_info' => [ + 'status' => '', + 'url' => '', + ], + 'contact_info' => [ + 'status' => '', + 'url' => '', + ], + 'additional_steps' => [ + 'steps' => [], + 'add_step' => [ + 'url' => '', + ], + ], + 'review_and_sign' => [ + 'status' => '', + 'url' => '', + ], + 'confirmation' => [ + 'status' => '', + 'url' => '', + ], + 'view_form' => [ + 'url' => '', + ], + ], + ]; // Add form themes. $forms = ['form_info']; diff --git a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.routing.yml b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.routing.yml index b33cb0e121..9d78b624fc 100644 --- a/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.routing.yml +++ b/docroot/modules/custom/va_gov_form_builder/va_gov_form_builder.routing.yml @@ -16,6 +16,10 @@ va_gov_form_builder.form_info.edit: _controller: '\Drupal\va_gov_form_builder\Controller\VaGovFormBuilderController::formInfo' requirements: nid: \d* +va_gov_form_builder.layout: + path: "/form-builder/{nid}/layout" + defaults: + _controller: '\Drupal\va_gov_form_builder\Controller\VaGovFormBuilderController::layout' va_gov_form_builder.name_and_dob: path: "/form-builder/{nid}/name-and-dob" defaults: diff --git a/tests/phpunit/va_gov_form_builder/functional/Controller/VaGovFormBuilderControllerTest.php b/tests/phpunit/va_gov_form_builder/functional/Controller/VaGovFormBuilderControllerTest.php index 8a1fc58029..2ec803ec1c 100644 --- a/tests/phpunit/va_gov_form_builder/functional/Controller/VaGovFormBuilderControllerTest.php +++ b/tests/phpunit/va_gov_form_builder/functional/Controller/VaGovFormBuilderControllerTest.php @@ -38,13 +38,13 @@ class VaGovFormBuilderControllerTest extends VaGovExistingSiteBase { public function setUp(): void { parent::setUp(); - $container = new ContainerBuilder(); - - // Add Drupal's form builder to the service container. - $container->set('form_builder', \Drupal::formBuilder()); + $entityTypeManager = \Drupal::service('entity_type.manager'); + $drupalFormBuilder = \Drupal::service('form_builder'); + $digitalFormsService = new DigitalFormsService($entityTypeManager); - // Add our DigitalFormsService to the service container. - $digitalFormsService = new DigitalFormsService(\Drupal::service('entity_type.manager')); + $container = new ContainerBuilder(); + $container->set('entity_type.manager', $entityTypeManager); + $container->set('form_builder', $drupalFormBuilder); $container->set('va_gov_form_builder.digital_forms_service', $digitalFormsService); // Create the controller instance. @@ -148,6 +148,60 @@ public function testFormInfoException() { $this->controller->formInfo($someNonExistentNodeId); } + /** + * Tests the layout method returns a Layout page. + */ + public function testLayout() { + $title = 'Test Digital Form ' . uniqid(); + $formNumber = '99-9999'; + + // Create a new Digital Form node. + $node = $this->createNode([ + 'type' => 'digital_form', + 'title' => $title, + 'field_chapters' => [], + 'field_va_form_number' => $formNumber, + ]); + + // Add paragraphs. + // Contact information. + $contactInfoParagraph = \Drupal::entityTypeManager()->getStorage('paragraph')->create([ + 'type' => 'digital_form_phone_and_email', + 'field_title' => 'Contact information', + 'field_include_email' => TRUE, + ]); + $node->get('field_chapters')->appendItem($contactInfoParagraph); + // List and loop. + $listAndLoopParagraph = \Drupal::entityTypeManager()->getStorage('paragraph')->create([ + 'type' => 'digital_form_list_loop', + 'field_title' => 'Your employers', + ]); + $node->get('field_chapters')->appendItem($listAndLoopParagraph); + + // Save node. + $node->save(); + + $page = $this->controller->layout($node->id()); + + $this->assertArrayHasKey('content', $page); + $this->assertArrayHasKey('#theme', $page['content']); + $this->assertEquals('page_content__va_gov_form_builder__layout', $page['content']['#theme']); + + // Ensure step statuses are calculated correctly. + // --> Contact info should be "complete" since paragraph exists. + $this->assertEquals('complete', $page['content']['#contact_info']['status'], 'Contact info is complete'); + // --> Address info should be "incomplete" since paragraph does not exist. + $this->assertEquals('incomplete', $page['content']['#address_info']['status'], 'Address info is incomplete'); + + // Ensure additional steps are included and have "complete" status. + $this->assertArrayHasKey('#additional_steps', $page['content']); + $this->assertEquals('complete', $page['content']['#additional_steps']['steps'][0]['status']); + + // Ensure css is added. + $this->assertArrayHasKey('#attached', $page); + $this->assertContains('va_gov_form_builder/va_gov_form_builder_styles__layout', $page['#attached']['library']); + } + /** * Tests the nameAndDob method returns a NameAndDob form. */ diff --git a/tests/phpunit/va_gov_form_builder/functional/Form/FormInfoTest.php b/tests/phpunit/va_gov_form_builder/functional/Form/FormInfoTest.php index c62cdd0c3c..152b7f308d 100644 --- a/tests/phpunit/va_gov_form_builder/functional/Form/FormInfoTest.php +++ b/tests/phpunit/va_gov_form_builder/functional/Form/FormInfoTest.php @@ -2,6 +2,8 @@ namespace tests\phpunit\va_gov_form_builder\functional\Form; +use Drupal\node\Entity\Node; +use Drupal\paragraphs\Entity\Paragraph; use tests\phpunit\va_gov_form_builder\Traits\SharedConstants; use tests\phpunit\va_gov_form_builder\Traits\TestPageLoads; use Tests\Support\Classes\VaGovExistingSiteBase; @@ -136,7 +138,31 @@ public function testFormSubmissionSucceeds() { // Successful submission should take user to next page. $nextPageUrl = $this->getSession()->getCurrentUrl(); - $this->assertStringContainsString('/name-and-dob', $nextPageUrl); + $this->assertStringContainsString('/layout', $nextPageUrl); + + // Form should have default "Your personal information" chapter. + preg_match('|/form-builder\/(\d+)/layout|', $nextPageUrl, $matches); + $createdNodeId = $matches[1]; + $createdNode = Node::load($createdNodeId); + + $steps = $createdNode->get('field_chapters')->getValue(); + $this->assertNotEmpty($steps, 'Default chapter should be added on initial node creation.'); + + $paragraphId = $steps[0]['target_id']; + $paragraph = Paragraph::load($paragraphId); + $this->assertEquals($paragraph->bundle(), 'digital_form_your_personal_info'); + + // That chapter should have two sub-chapters: + // 1. Name and date of birth. + $nameAndDob = $paragraph->get('field_name_and_date_of_birth')->getValue(); + $nameAndDobParagraphId = $nameAndDob[0]['target_id']; + $nameAndDobParagraph = Paragraph::load($nameAndDobParagraphId); + $this->assertEquals($nameAndDobParagraph->bundle(), 'digital_form_name_and_date_of_bi'); + // 2. Identification information. + $identificationInfo = $paragraph->get('field_identification_information')->getValue(); + $identificationInfoParagraphId = $identificationInfo[0]['target_id']; + $identificationInfoParagraph = Paragraph::load($identificationInfoParagraphId); + $this->assertEquals($identificationInfoParagraph->bundle(), 'digital_form_identification_info'); } /** diff --git a/tests/phpunit/va_gov_form_builder/functional/Form/NameAndDobTest.php b/tests/phpunit/va_gov_form_builder/functional/Form/NameAndDobTest.php index 38a615fa77..504fee3ae6 100644 --- a/tests/phpunit/va_gov_form_builder/functional/Form/NameAndDobTest.php +++ b/tests/phpunit/va_gov_form_builder/functional/Form/NameAndDobTest.php @@ -2,7 +2,6 @@ namespace tests\phpunit\va_gov_form_builder\functional\Form; -use Drupal\node\Entity\Node; use tests\phpunit\va_gov_form_builder\Traits\SharedConstants; use tests\phpunit\va_gov_form_builder\Traits\TestPageLoads; use Tests\Support\Classes\VaGovExistingSiteBase; @@ -26,25 +25,33 @@ class NameAndDobTest extends VaGovExistingSiteBase { private static $modules = ['va_gov_form_builder']; /** - * The Digital Form node. + * The DigitalFormsService object. * - * @var \Drupal\node\Entity\Node + * @var \Drupal\va_gov_form_builder\Service\DigitalFormsService */ - private $node; + private $digitalFormsService; /** - * Returns the url for this form (for the given node) + * The Digital Form object. + * + * @var \Drupal\va_gov_form_builder\EntityWrapper\DigitalForm + */ + private $digitalForm; + + /** + * Returns the url for this form (for the given Digital Form) */ private function getFormPageUrl() { - return '/form-builder/' . $this->node->id() . '/name-and-dob'; + return '/form-builder/' . $this->digitalForm->id() . '/name-and-dob'; } /** - * Reloads the node from the database. + * Reloads the Digital Form from the database. */ - private function reloadNode() { - \Drupal::entityTypeManager()->getStorage('node')->resetCache([$this->node->id()]); - $this->node = Node::load($this->node->id()); + private function reloadDigitalForm() { + \Drupal::entityTypeManager()->getStorage('node')->resetCache([$this->digitalForm->id()]); + + $this->digitalForm = $this->digitalFormsService->getDigitalForm($this->digitalForm->id()); } /** @@ -55,11 +62,14 @@ public function setUp(): void { $this->loginFormBuilderUser(); + $this->digitalFormsService = \Drupal::service('va_gov_form_builder.digital_forms_service'); + // Create a node that doesn't have any chapters. - $this->node = $this->createNode([ + $node = $this->createNode([ 'type' => 'digital_form', 'field_chapters' => [], ]); + $this->digitalForm = $this->digitalFormsService->wrapDigitalForm($node); $this->drupalGet($this->getFormPageUrl()); } @@ -87,9 +97,9 @@ public function testFormSubmissionAddsChapter() { ]; $this->submitForm($formInput, 'Continue'); - // Reload node and assert that chapters has been updated. - $this->reloadNode(); - $this->assertCount(1, $this->node->get('field_chapters')->getValue()); + // Reload Digital Form and assert that chapters has been updated. + $this->reloadDigitalForm(); + $this->assertCount(1, $this->digitalForm->get('field_chapters')->getValue()); } /** @@ -102,17 +112,17 @@ public function testFormSubmissionDoesNotAddChapter() { 'field_title' => 'Your personal information', 'field_include_date_of_birth' => TRUE, ]); - $this->node->get('field_chapters')->appendItem($nameAndDobParagraph); - $this->node->save(); + $this->digitalForm->get('field_chapters')->appendItem($nameAndDobParagraph); + $this->digitalForm->save(); $formInput = [ 'step_name' => 'Your Personal Information', ]; $this->submitForm($formInput, 'Continue'); - // Reload node and assert that chapters still has only one item. - $this->reloadNode(); - $this->assertCount(1, $this->node->get('field_chapters')->getValue()); + // Reload Digital Form and assert that chapters still has only one item. + $this->reloadDigitalForm(); + $this->assertCount(1, $this->digitalForm->get('field_chapters')->getValue()); } /** diff --git a/tests/phpunit/va_gov_form_builder/functional/content-pages/LayoutTest.php b/tests/phpunit/va_gov_form_builder/functional/content-pages/LayoutTest.php new file mode 100644 index 0000000000..b2ecdd0587 --- /dev/null +++ b/tests/phpunit/va_gov_form_builder/functional/content-pages/LayoutTest.php @@ -0,0 +1,207 @@ +digitalFormNode->id()}/layout"; + } + + /** + * Helper method to generate a test node. + */ + private function generateTestNode( + $title = NULL, + $formNumber = '99-9999', + $contactInfoTitle = 'Contact information', + $listAndLoopTitle = 'Your employers', + ) { + if (!$title) { + $title = 'Test Digital Form ' . uniqid(); + } + + // Create a new Digital Form node. + $this->digitalFormNode = $this->createNode([ + 'type' => 'digital_form', + 'title' => $title, + 'field_chapters' => [], + 'field_va_form_number' => $formNumber, + ]); + + // Add paragraphs. + // Contact information. + $contactInfoParagraph = \Drupal::entityTypeManager()->getStorage('paragraph')->create([ + 'type' => 'digital_form_phone_and_email', + 'field_title' => $contactInfoTitle, + 'field_include_email' => TRUE, + ]); + $this->digitalFormNode->get('field_chapters')->appendItem($contactInfoParagraph); + // List and loop. + $listAndLoopParagraph = \Drupal::entityTypeManager()->getStorage('paragraph')->create([ + 'type' => 'digital_form_list_loop', + 'field_title' => $listAndLoopTitle, + ]); + $this->digitalFormNode->get('field_chapters')->appendItem($listAndLoopParagraph); + + // Save node. + $this->digitalFormNode->save(); + } + + /** + * Set up the environment for each test. + */ + public function setUp(): void { + parent::setUp(); + + $this->generateTestNode(); + $this->loginFormBuilderUser(); + } + + /** + * Test that the page is accessible to a user with the correct privilege. + */ + public function testPageLoads() { + // Ensure page loads. + $this->sharedTestPageLoads($this->getFormPageUrl($this->digitalFormNode->id()), 'Build this form'); + } + + /** + * Test that the page is not accessible to a user without privilege. + */ + public function testPageDoesNotLoad() { + $this->sharedTestPageDoesNotLoad($this->getFormPageUrl()); + } + + /** + * Test that the page has the expected subtitle. + * + * The subtitle should be the form title. + */ + public function testPageSubtitle() { + $this->sharedTestPageHasExpectedSubtitle( + $this->getFormPageUrl(), + $this->digitalFormNode->getTitle(), + ); + } + + /** + * Test the "Form info" section. + */ + public function testFormInfo() { + $this->drupalGet($this->getFormPageUrl()); + $this->assertSession()->linkExists('View form info'); + + $this->clickLink('View form info'); + $this->assertSession()->addressEquals("/form-builder/{$this->digitalFormNode->id()}/form-info"); + } + + /** + * Test the "Introduction page" section. + */ + public function testIntroductionPage() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View introduction page'); + } + + /** + * Test the "Your personal information" section. + */ + public function testYourPersonalInfo() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View personal information'); + } + + /** + * Test the "Address information" section. + */ + public function testAddressInfo() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View address information'); + } + + /** + * Test the "Contact information" section. + */ + public function testContactInfo() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View contact information'); + } + + /** + * Test that additional (non-standard) steps are rendered. + */ + public function testAdditionalSteps() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View your employers'); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('Add a step'); + } + + /** + * Test the "Review and sign" section. + */ + public function testReviewAndSign() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View review and sign page'); + } + + /** + * Test the "Confirmation page" section. + */ + public function testConfirmationPage() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View confirmation page'); + } + + /** + * Test the "Viewing the form" section. + */ + public function testViewingTheForm() { + $this->drupalGet($this->getFormPageUrl()); + + // There is no destination for this link yet. + $this->assertSession()->linkExists('View form'); + } + +} diff --git a/tests/phpunit/va_gov_form_builder/unit/EntityWrapper/DigitalFormTest.php b/tests/phpunit/va_gov_form_builder/unit/EntityWrapper/DigitalFormTest.php new file mode 100644 index 0000000000..3eff8aae86 --- /dev/null +++ b/tests/phpunit/va_gov_form_builder/unit/EntityWrapper/DigitalFormTest.php @@ -0,0 +1,246 @@ +entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); + } + + /** + * Test the constructor with wrong node type passed in. + * + * Ensures that passing in a node that is not a + * DigitalForm node thorws an error. + */ + public function testConstructorWrongNodeType() { + $node = $this->createMock(NodeInterface::class); + + // Configure the mock to return something other + // than 'digital_form' when getType() is called. + $node->method('getType') + ->willReturn('article'); + + $this->expectException(\InvalidArgumentException::class); + $this->digitalForm = new DigitalForm($this->entityTypeManager, $node); + } + + /** + * Test the __call magic method. + */ + public function testCallMagicMethod() { + $id = '123'; + $title = 'Test Digital Form 1'; + $field = $this->createMock(FieldItemListInterface::class); + $field->value = 'Some field value'; + + $node = $this->createMock(NodeInterface::class); + $node->method('getType') + ->willReturn('digital_form'); + $node->method('getTitle') + ->willReturn($title); + $node->method('id') + ->willReturn('123'); + $node->method('get') + ->with('my_field') + ->willReturn($field); + + $this->digitalForm = new DigitalForm($this->entityTypeManager, $node); + + // id() + $idResult = $this->digitalForm->id(); + $this->assertEquals($id, $idResult); + + // getTitle() + $getTitleResult = $this->digitalForm->getTitle(); + $this->assertEquals($title, $getTitleResult); + + // get()->value + $getValueResult = $this->digitalForm->get('my_field')->value; + $this->assertEquals($field->value, $getValueResult); + + // Unknown method. + $this->expectException(\BadMethodCallException::class); + $this->digitalForm->someUnknownMethod(); + } + + /** + * Helper function to set up a mock query for paragraphs. + */ + private function setUpMockQueryParagraph() { + // Mock the paragraph. + $mockParagraph = $this->createMock(Paragraph::class); + $mockParagraph->expects($this->once()) + ->method('bundle') + ->willReturn('expected_paragraph'); + + // Mock the entity storage. + $entityStorage = $this->createMock(EntityStorageInterface::class); + $entityStorage->expects($this->once()) + ->method('load') + ->willReturnMap([ + ['1', $mockParagraph], + ]); + + // Mock the entity type manager. + $this->entityTypeManager->expects($this->once()) + ->method('getStorage') + ->with('paragraph') + ->willReturn($entityStorage); + } + + /** + * Helper function to create and return a mock a node with a paragraph. + */ + private function createMockNodeWithParagraph() { + // Mock the field_chapters field. + $mockField = $this->createMock(FieldItemListInterface::class); + $mockField->expects($this->once()) + ->method('isEmpty') + ->willReturn(FALSE); + $mockField->expects($this->once()) + ->method('getValue') + ->willReturn([ + ['target_id' => '1'], + ]); + + // Mock the node. + $mockNode = $this->createMock(NodeInterface::class); + $mockNode->expects($this->once()) + ->method('hasField') + ->with('field_chapters') + ->willReturn(TRUE); + $mockNode->expects($this->exactly(2)) + ->method('get') + ->with('field_chapters') + ->willReturn($mockField); + $mockNode->expects($this->once()) + ->method('getType') + ->willReturn('digital_form'); + + return $mockNode; + } + + /** + * Helper function to set up a test for hasChapterOfType. + */ + private function setUpHasChapterOfTypeTest() { + $this->setUpMockQueryParagraph(); + $node = $this->createMockNodeWithParagraph(); + + // Instantiate an object to test. + $this->digitalForm = new DigitalForm($this->entityTypeManager, $node); + } + + /** + * Tests hasChapterOfType() with expected paragraph. + */ + public function testHasChapterOfTypeWithExpectedParagraph() { + $this->setUpHasChapterOfTypeTest(); + + // Assert expected paragraph type returns true. + $resultExpectedParagraphType = $this->digitalForm->hasChapterOfType('expected_paragraph'); + $this->assertTrue($resultExpectedParagraphType); + } + + /** + * Tests hasChapterOfType() with unexpected paragraph. + */ + public function testHasChapterOfTypeWithUnexpectedParagraph() { + $this->setUpHasChapterOfTypeTest(); + + // Assert unexpected paragraph type returns false. + $resultUnexpectedParagraphType = $this->digitalForm->hasChapterOfType('any_other_paragraph_type'); + $this->assertFalse($resultUnexpectedParagraphType); + } + + /** + * Helper function to set up a test for getStepStatus. + */ + private function setUpGetStepStatusTest() { + $this->digitalForm = $this->getMockBuilder(DigitalForm::class) + ->disableOriginalConstructor() + ->onlyMethods(['hasChapterOfType']) + ->getMock(); + + // Mock the behavior of hasChapterOfType method. + $this->digitalForm->method('hasChapterOfType') + ->willReturnMap([ + ['digital_form_phone_and_email', TRUE], + ['digital_form_address', FALSE], + ]); + } + + /** + * Tests getStepStatus() with unknown step name. + */ + public function testGetDigitalFormStepStatusUnknownStepName() { + $this->setUpGetStepStatusTest(); + + // Assert step status is incomplete for unknown name. + $result = $this->digitalForm->getStepStatus('some_unknown_step_name'); + $this->assertEquals('incomplete', $result); + } + + /** + * Tests getStepStatus() with paragraph present. + */ + public function testGetDigitalFormStepStatusParagraphPresent() { + $this->setUpGetStepStatusTest(); + + // Assert the status is 'complete' when paragraph is present. + // Note: `contact_info` step = `digital_form_phone_and_email` paragraph. + $result = $this->digitalForm->getStepStatus('contact_info'); + $this->assertEquals('complete', $result); + } + + /** + * Tests getStepStatus() with paragraph absent. + */ + public function testGetDigitalFormStepStatusParagraphAbsent() { + $this->setUpGetStepStatusTest(); + + // Assert the status is 'incomplete' when paragraph is absent. + // Note: `address_info` step = `digital_form_address` paragraph. + $result = $this->digitalForm->getStepStatus('address_info'); + $this->assertEquals('incomplete', $result); + } + +} diff --git a/tests/phpunit/va_gov_form_builder/unit/Form/Base/FormBuilderBaseTest.php b/tests/phpunit/va_gov_form_builder/unit/Form/Base/FormBuilderBaseTest.php index 2eb4847ac2..0c985a1827 100644 --- a/tests/phpunit/va_gov_form_builder/unit/Form/Base/FormBuilderBaseTest.php +++ b/tests/phpunit/va_gov_form_builder/unit/Form/Base/FormBuilderBaseTest.php @@ -4,8 +4,9 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\node\Entity\Node; +use Drupal\va_gov_form_builder\EntityWrapper\DigitalForm; use Drupal\va_gov_form_builder\Form\Base\FormBuilderBase; +use Drupal\va_gov_form_builder\Service\DigitalFormsService; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use tests\phpunit\va_gov_form_builder\Traits\AnonymousFormClass; @@ -21,6 +22,13 @@ */ class FormBuilderBaseTest extends VaGovUnitTestBase { + /** + * The Digital Forms service. + * + * @var \Drupal\va_gov_form_builder\Service\DigitalFormsService + */ + protected $digitalFormsService; + /** * An instance of an anonymous class that extends the abstract class. * @@ -35,9 +43,10 @@ public function setUp(): void { parent::setUp(); $entityTypeManager = $this->createMock(EntityTypeManagerInterface::class); + $this->digitalFormsService = $this->createMock(DigitalFormsService::class); // Create an anonymous instance of a class that extends our abstract class. - $this->classInstance = new class($entityTypeManager) extends FormBuilderBase { + $this->classInstance = new class($entityTypeManager, $this->digitalFormsService) extends FormBuilderBase { use AnonymousFormClass; /** @@ -51,17 +60,17 @@ protected function getFields() { } /** - * setDigitalFormNodeFromFormState. + * setDigitalFormFromFormState. */ - protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterface $form_state) { - // Do nothing. We'll set the digitalFormNode via reflection. + protected function setDigitalFormFromFormState(array &$form, FormStateInterface $form_state) { + // Do nothing. We'll set the digitalForm via reflection. } }; } /** - * Test that the buildForm method throws error when node not passed. + * Test that the buildForm method throws error when Digital Form not passed. */ public function testBuildFormThrowsError() { $form = []; @@ -77,32 +86,55 @@ public function testBuildFormThrowsError() { */ public function testBuildFormInitializesChangedFlag() { $reflection = new \ReflectionClass($this->classInstance); - $isChangedFlag = $reflection->getProperty('digitalFormNodeIsChanged'); + $isChangedFlag = $reflection->getProperty('digitalFormIsChanged'); $isChangedFlag->setAccessible(TRUE); $form = []; $formStateMock = $this->createMock(FormStateInterface::class); - // Pass a good node id so no error is thrown. - $nid = 1; - $form = $this->classInstance->buildForm($form, $formStateMock, $nid); + // Pass a good Digital Form so no error is thrown. + $digitalForm = $this->createMock(DigitalForm::class); + $form = $this->classInstance->buildForm($form, $formStateMock, $digitalForm); $isChangedFlagValue = $isChangedFlag->getValue($this->classInstance); $this->assertEquals($isChangedFlagValue, FALSE); } /** - * Test the validateForm method with a Digital Form with no violations. + * Helper function to set up tests for violations. + * + * @param \Symfony\Component\Validator\ConstraintViolationList $violationList + * The expected violations. */ - public function testValidateFormWithNoViolations() { - $digitalFormNode = $this->createMock(Node::class); - $violationList = new ConstraintViolationList([]); - $digitalFormNode->method('validate')->willReturn($violationList); + private function setUpViolationTest($violationList = NULL) { + if (!$violationList) { + $violationList = new ConstraintViolationList([]); + } + + $digitalForm = $this->getMockBuilder(DigitalForm::class) + ->disableOriginalConstructor() + ->onlyMethods(['__call']) + ->getMock(); + + $digitalForm->method('__call') + ->willReturnCallback(function ($name, $arguments) use ($violationList) { + if ($name === 'validate') { + return $violationList; + } + return NULL; + }); $reflection = new \ReflectionClass($this->classInstance); - $digitalFormNodeProperty = $reflection->getProperty('digitalFormNode'); - $digitalFormNodeProperty->setAccessible(TRUE); - $digitalFormNodeProperty->setValue($this->classInstance, $digitalFormNode); + $digitalFormProperty = $reflection->getProperty('digitalForm'); + $digitalFormProperty->setAccessible(TRUE); + $digitalFormProperty->setValue($this->classInstance, $digitalForm); + } + + /** + * Test the validateForm method with a Digital Form with no violations. + */ + public function testValidateFormWithNoViolations() { + $this->setUpViolationTest(); $form = []; @@ -114,11 +146,9 @@ public function testValidateFormWithNoViolations() { } /** - * Test the validateForm method with a node with applicable violations. + * Test validateForm method with a Digital Form with applicable violations. */ public function testValidateFormWithApplicableViolations() { - $digitalFormNode = $this->createMock(Node::class); - // Has violations on fields related to this form; // should raise errors. $violationList = new ConstraintViolationList([ @@ -126,12 +156,7 @@ public function testValidateFormWithApplicableViolations() { new ConstraintViolation('Invalid value 2', '', [], '', 'test_field_2', 'Invalid value'), ]); - $digitalFormNode->method('validate')->willReturn($violationList); - - $reflection = new \ReflectionClass($this->classInstance); - $digitalFormNodeProperty = $reflection->getProperty('digitalFormNode'); - $digitalFormNodeProperty->setAccessible(TRUE); - $digitalFormNodeProperty->setValue($this->classInstance, $digitalFormNode); + $this->setUpViolationTest($violationList); $form = []; @@ -147,11 +172,9 @@ public function testValidateFormWithApplicableViolations() { } /** - * Test the validateForm method with a Digital Form with other violations. + * Test validateForm method with a Digital Form with other violations. */ public function testValidateFormWithOtherViolations() { - $digitalFormNode = $this->createMock(Node::class); - // Has violations, but not on fields related to this form; // should not raise errors. $violationList = new ConstraintViolationList([ @@ -159,12 +182,7 @@ public function testValidateFormWithOtherViolations() { new ConstraintViolation('Invalid value 4', '', [], '', 'test_field_4', 'Invalid value'), ]); - $digitalFormNode->method('validate')->willReturn($violationList); - - $reflection = new \ReflectionClass($this->classInstance); - $digitalFormNodeProperty = $reflection->getProperty('digitalFormNode'); - $digitalFormNodeProperty->setAccessible(TRUE); - $digitalFormNodeProperty->setValue($this->classInstance, $digitalFormNode); + $this->setUpViolationTest($violationList); $form = []; @@ -176,23 +194,16 @@ public function testValidateFormWithOtherViolations() { } /** - * Test the validateForm method with a deeply-nested violation path. + * Test validateForm method with a deeply-nested violation path. */ public function testValidateFormWithNestedViolationPath() { - $digitalFormNode = $this->createMock(Node::class); - // Has violation with a nested path; should raise an error the same way // as if the path were not nested (on `test_field_1`). $violationList = new ConstraintViolationList([ new ConstraintViolation('Invalid value 1', '', [], '', 'test_field_1.0.value', 'Invalid value'), ]); - $digitalFormNode->method('validate')->willReturn($violationList); - - $reflection = new \ReflectionClass($this->classInstance); - $digitalFormNodeProperty = $reflection->getProperty('digitalFormNode'); - $digitalFormNodeProperty->setAccessible(TRUE); - $digitalFormNodeProperty->setValue($this->classInstance, $digitalFormNode); + $this->setUpViolationTest($violationList); $form = []; diff --git a/tests/phpunit/va_gov_form_builder/unit/LibrariesTest.php b/tests/phpunit/va_gov_form_builder/unit/LibrariesTest.php index b43f9aa89e..997eeef55d 100644 --- a/tests/phpunit/va_gov_form_builder/unit/LibrariesTest.php +++ b/tests/phpunit/va_gov_form_builder/unit/LibrariesTest.php @@ -76,12 +76,16 @@ public function testLibraryCss() { $this->assertArrayHasKey($homeLibrary, $this->libraries); $homeCssArray = array_keys($this->libraries[$homeLibrary]['css']['theme']); $this->assertContains($cssPrefix . '__home.css', $homeCssArray, 'Home page css is present.'); - // 2. Form Info. $formInfoLibrary = $libraryPrefix . '__form_info'; $this->assertArrayHasKey($formInfoLibrary, $this->libraries); $formInfoCssArray = array_keys($this->libraries[$formInfoLibrary]['css']['theme']); $this->assertContains($cssPrefix . '__form_info.css', $formInfoCssArray, 'Form Info page css is present.'); + // 3. Layout. + $layoutLibrary = $libraryPrefix . '__layout'; + $this->assertArrayHasKey($layoutLibrary, $this->libraries); + $layoutCssArray = array_keys($this->libraries[$layoutLibrary]['css']['theme']); + $this->assertContains($cssPrefix . '__layout.css', $layoutCssArray, 'Layout page css is present.'); } } diff --git a/tests/phpunit/va_gov_form_builder/unit/ModuleTest.php b/tests/phpunit/va_gov_form_builder/unit/ModuleTest.php index f48ab02caa..0d6c3c904c 100644 --- a/tests/phpunit/va_gov_form_builder/unit/ModuleTest.php +++ b/tests/phpunit/va_gov_form_builder/unit/ModuleTest.php @@ -74,11 +74,26 @@ public function testVaGovFormBuilderHookTheme() { $page_content_theme_prefix = 'page_content__va_gov_form_builder__'; $page_content_theme_path = $this->modulePath . '/templates/page-content'; // 1. Home page. - $this->assertArrayHasKey($page_content_theme_prefix . 'home', $result); - $this->assertEquals($page_content_theme_path, $result[$page_content_theme_prefix . 'home']['path']); - $this->assertArrayHasKey('variables', $result[$page_content_theme_prefix . 'home']); - $this->assertArrayHasKey('recent_forms', $result[$page_content_theme_prefix . 'home']['variables']); - $this->assertArrayHasKey('build_form_url', $result[$page_content_theme_prefix . 'home']['variables']); + $homeTheme = $page_content_theme_prefix . 'home'; + $this->assertArrayHasKey($homeTheme, $result); + $this->assertEquals($page_content_theme_path, $result[$homeTheme]['path']); + $this->assertArrayHasKey('variables', $result[$homeTheme]); + $this->assertArrayHasKey('recent_forms', $result[$homeTheme]['variables']); + $this->assertArrayHasKey('build_form_url', $result[$homeTheme]['variables']); + // 2. Layout page. + $layoutTheme = $page_content_theme_prefix . 'layout'; + $this->assertArrayHasKey($layoutTheme, $result); + $this->assertEquals($page_content_theme_path, $result[$layoutTheme]['path']); + $this->assertArrayHasKey('variables', $result[$layoutTheme]); + $this->assertArrayHasKey('form_info', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('intro', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('your_personal_info', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('address_info', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('contact_info', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('additional_steps', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('review_and_sign', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('confirmation', $result[$layoutTheme]['variables']); + $this->assertArrayHasKey('view_form', $result[$layoutTheme]['variables']); // Form themes. $form_theme_prefix = 'form__va_gov_form_builder__'; diff --git a/tests/phpunit/va_gov_form_builder/unit/Service/DigitalFormsServiceTest.php b/tests/phpunit/va_gov_form_builder/unit/Service/DigitalFormsServiceTest.php index 841da5516d..5f1abc55f0 100644 --- a/tests/phpunit/va_gov_form_builder/unit/Service/DigitalFormsServiceTest.php +++ b/tests/phpunit/va_gov_form_builder/unit/Service/DigitalFormsServiceTest.php @@ -77,9 +77,16 @@ private function setUpMockQueryGetDigitalForms($publishedOnly, $hasResults = TRU } if ($hasResults) { - $query->expects($this->once()) - ->method('execute') - ->willReturn([1, 2]); + if ($publishedOnly) { + $query->expects($this->once()) + ->method('execute') + ->willReturn([1]); + } + else { + $query->expects($this->once()) + ->method('execute') + ->willReturn([1, 2]); + } } else { $query->expects($this->once()) @@ -93,13 +100,31 @@ private function setUpMockQueryGetDigitalForms($publishedOnly, $hasResults = TRU ->willReturn($query); if ($hasResults) { - $entityStorage->expects($this->once()) - ->method('loadMultiple') - ->with([1, 2]) - ->willReturn([ - 1 => $this->createMock('Drupal\node\NodeInterface'), - 2 => $this->createMock('Drupal\node\NodeInterface'), - ]); + $node1 = $this->createMock('Drupal\node\NodeInterface'); + $node1->method('getType') + ->willReturn('digital_form'); + + $node2 = $this->createMock('Drupal\node\NodeInterface'); + $node2->method('getType') + ->willReturn('digital_form'); + + if ($publishedOnly) { + $entityStorage->expects($this->once()) + ->method('loadMultiple') + ->with([1]) + ->willReturn([ + 1 => $node1, + ]); + } + else { + $entityStorage->expects($this->once()) + ->method('loadMultiple') + ->with([1, 2]) + ->willReturn([ + 1 => $node1, + 2 => $node2, + ]); + } } // Mock the entity type manager. @@ -130,7 +155,9 @@ public function testGetDigitalFormsPublishedOnlyTrue() { $this->setUpMockQueryGetDigitalForms(TRUE); // Call the method, which asserts expectations set in setup. - $this->digitalFormsService->getDigitalForms(TRUE); + $result = $this->digitalFormsService->getDigitalForms(TRUE); + // Assert one result is returned. + $this->assertCount(1, $result); } /** @@ -146,7 +173,9 @@ public function testGetDigitalFormsPublishedOnlyFalse() { $this->setUpMockQueryGetDigitalForms(FALSE); // Call the method, which asserts expectations set in setup. - $this->digitalFormsService->getDigitalForms(FALSE); + $result = $this->digitalFormsService->getDigitalForms(FALSE); + // Assert two results are returned. + $this->assertCount(2, $result); } /** @@ -162,7 +191,9 @@ public function testGetDigitalFormsPublishedOnlyDefault() { $this->setUpMockQueryGetDigitalForms(TRUE); // Call the method, which asserts expectations set in setup. - $this->digitalFormsService->getDigitalForms(); + $result = $this->digitalFormsService->getDigitalForms(); + // Assert one result is returned. + $this->assertcount(1, $result); } /** @@ -187,21 +218,27 @@ public function testGetDigitalFormsNoResults() { // Call the method, which asserts expectations set in setup. $result = $this->digitalFormsService->getDigitalForms(TRUE); - - // Additionally, assert the function returns no results. + // Assert the function returns no results. $this->assertCount(0, $result); } /** * Helper function to DRY up expectation setup for getDigitalForm. + * + * @param \Drupal\node\NodeInterface $node + * The mock node to return from entity storage. */ - private function setUpMockQueryGetDigitalForm() { + private function setUpMockQueryGetDigitalForm($node = NULL) { + if (!$node) { + $node = $this->createMock('Drupal\node\NodeInterface'); + } + // Mock the entity storage. $entityStorage = $this->createMock(EntityStorageInterface::class); $entityStorage->expects($this->once()) ->method('load') ->willReturnMap([ - ['1', $this->createMock('Drupal\node\NodeInterface')], + ['1', $node], ]); // Mock the entity type manager. @@ -212,14 +249,33 @@ private function setUpMockQueryGetDigitalForm() { } /** - * Tests getDigitalForm() with $nid of existing node. + * Tests getDigitalForm() with $nid of digital_form node. */ - public function testGetDigitalFormReturnsNode() { - $this->setUpMockQueryGetDigitalForm(); + public function testGetDigitalFormWithCorrectNodeType() { + $node = $this->createMock('Drupal\node\NodeInterface'); + $node->method('getType') + ->willReturn('digital_form'); + + $this->setUpMockQueryGetDigitalForm($node); + + // Call the method with a nid that exists. + $digitalForm = $this->digitalFormsService->getDigitalForm('1'); + $this->assertNotEmpty($digitalForm); + } + + /** + * Tests getDigitalForm() with $nid of some other node type. + */ + public function testGetDigitalFormWithIncorrectNodeType() { + $node = $this->createMock('Drupal\node\NodeInterface'); + $node->method('getType') + ->willReturn('some_other_node_type'); + + $this->setUpMockQueryGetDigitalForm($node); // Call the method with a nid that exists. - $node = $this->digitalFormsService->getDigitalForm('1'); - $this->assertNotEmpty($node); + $digitalForm = $this->digitalFormsService->getDigitalForm('1'); + $this->assertEmpty($digitalForm); } /** @@ -229,8 +285,34 @@ public function testGetDigitalFormReturnsNull() { $this->setUpMockQueryGetDigitalForm(); // Call the method with a nid that does not exist. - $node = $this->digitalFormsService->getDigitalForm('99999'); - $this->assertEmpty($node); + $digitalForm = $this->digitalFormsService->getDigitalForm('99999'); + $this->assertEmpty($digitalForm); + } + + /** + * Tests wrapDigitalForm() with `digital_form` $node passed in. + */ + public function testWrapDigitalFormCorrectNodeType() { + $node = $this->createMock('Drupal\node\NodeInterface'); + $node->method('getType') + ->willReturn('digital_form'); + + // Call the method and assert a DigitalForm object is returned. + $digitalForm = $this->digitalFormsService->wrapDigitalForm($node); + $this->assertNotEmpty($digitalForm); + } + + /** + * Tests wrapDigitalForm() with other type of $node passed in. + */ + public function testWrapDigitalFormIncorrectNodeType() { + $node = $this->createMock('Drupal\node\NodeInterface'); + $node->method('getType') + ->willReturn('some_other_node_type'); + + // Call the method and assert a DigitalForm object is returned. + $digitalForm = $this->digitalFormsService->wrapDigitalForm($node); + $this->assertEmpty($digitalForm); } }