View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.apache.myfaces.trinidad.component;
20  
21  import java.beans.BeanInfo;
22  import java.beans.IntrospectionException;
23  import java.beans.Introspector;
24  import java.beans.PropertyDescriptor;
25  
26  import java.io.IOException;
27  
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  
31  import java.util.Collections;
32  import java.util.Map;
33  
34  import javax.faces.component.EditableValueHolder;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.UIViewRoot;
37  import javax.faces.component.ValueHolder;
38  import javax.faces.context.FacesContext;
39  import javax.faces.convert.Converter;
40  import javax.faces.el.ValueBinding;
41  import javax.faces.event.FacesEvent;
42  import javax.faces.event.ValueChangeListener;
43  import javax.faces.render.RenderKit;
44  import javax.faces.render.RenderKitFactory;
45  import javax.faces.render.Renderer;
46  import javax.faces.validator.Validator;
47  
48  import junit.framework.Test;
49  import junit.framework.TestSuite;
50  
51  import org.apache.myfaces.trinidad.context.MockRequestContext;
52  import org.apache.myfaces.trinidad.event.AttributeChangeEvent;
53  import org.apache.myfaces.trinidadbuild.test.FacesTestCase;
54  
55  import org.jmock.Mock;
56  import org.jmock.core.Constraint;
57  
58  
59  /**
60   * Base class for JavaServer Faces UIComponent unit tests.
61   *
62   */
63  public class UIComponentTestCase extends FacesTestCase
64  {
65    /**
66     * Creates a new UIComponentTestCase.
67     *
68     * @param testName  the unit test name
69     */
70    public UIComponentTestCase(
71      String testName)
72    {
73      super(testName);
74    }
75  
76    @Override
77    protected void setUp() throws Exception
78    {
79      _mockRequestContext = new MockRequestContext();
80      super.setUp();
81    }
82  
83    @Override
84    protected void tearDown() throws Exception
85    {
86      super.tearDown();
87      _mockRequestContext.release();
88    }
89  
90    public static Test suite()
91    {
92      return new TestSuite(UIComponentTestCase.class);
93    }
94  
95    /**
96     * Tests the transparency of the component attribute by comparing
97     * bean accessor and mutator methods with attribute map accessor
98     * and mutator methods.
99     *
100    * @param component   the component with attribute map
101    * @param attrName    the attribute name to test
102    * @param attrValue   the value for use by the attribute map mutator
103    * @param propValue   the value for use by the bean mutator
104    */
105   @SuppressWarnings("unchecked")
106   protected void doTestAttributeTransparency(
107     UIComponent component,
108     String      attrName,
109     Object      attrValue,
110     Object      propValue)
111   {
112     assertFalse("Test values for attribute \"" + attrName + "\" must differ",
113                 (attrValue == propValue ||
114                  (attrValue != null &&
115                   attrValue.equals(propValue))));
116 
117     Map<String, Object> attrMap = component.getAttributes();
118     try
119     {
120       boolean foundProperty = false;
121       // bean info is cached
122       BeanInfo info = Introspector.getBeanInfo(component.getClass());
123       PropertyDescriptor[] pds = info.getPropertyDescriptors();
124       for (int i=0; i < pds.length; i++)
125       {
126         String propName = pds[i].getName();
127         if (attrName.equals(propName))
128         {
129           if (pds[i].getPropertyType().isPrimitive())
130           {
131             assertNotNull("Primitive \"" + attrName +
132                           "\" attribute value must be non-null",
133                           attrValue);
134             assertNotNull("Primitive \"" + propName +
135                           "\" property value must be non-null",
136                           propValue);
137           }
138 
139           foundProperty = true;
140 
141           Method reader = pds[i].getReadMethod();
142           Method writer = pds[i].getWriteMethod();
143           writer.invoke(component, new Object[] { propValue });
144           assertEquals("Property set not visible in attribute map",
145                        attrMap.get(attrName), propValue);
146           attrMap.put(attrName, attrValue);
147           assertEquals("Attribute put not visible in property value",
148                        reader.invoke(component, new Object[0]), attrValue);
149           break;
150         }
151       }
152 
153       if (!foundProperty)
154         fail("Unable to find attribute property \"" + attrName + "\"");
155     }
156     catch (IntrospectionException e)
157     {
158       e.printStackTrace();
159       fail("Unable to access attribute property \"" + attrName + "\"");
160     }
161     catch (InvocationTargetException e)
162     {
163       e.printStackTrace();
164       fail("Unable to access attribute property \"" + attrName + "\"");
165     }
166     catch (IllegalAccessException e)
167     {
168       e.printStackTrace();
169       fail("Unable to access attribute property \"" + attrName + "\"");
170     }
171   }
172 
173   /**
174    * Tests the transparency of the component facet by comparing
175    * bean accessor and mutator methods with facet map accessor
176    * and mutator methods.
177    *
178    * @param component   the component with attribute map
179    * @param facetName   the facet name to test
180    * @param facetValue  the value for use by the facet map mutator
181    * @param propValue   the value for use by the bean mutator
182    */
183   @SuppressWarnings("unchecked")
184   protected void doTestFacetTransparency(
185     UIComponent component,
186     String      facetName)
187   {
188     Mock mockFacetValue = mock(UIComponent.class);
189     UIComponent facetValue = (UIComponent) mockFacetValue.proxy();
190     mockFacetValue.stubs().method("getParent").will(returnValue(null));
191     mockFacetValue.stubs().method("setParent");
192 
193     Mock mockPropValue = mock(UIComponent.class);
194     UIComponent propValue = (UIComponent) mockPropValue.proxy();
195     mockPropValue.stubs().method("getParent").will(returnValue(null));
196     mockPropValue.stubs().method("setParent");
197 
198     Map<String, UIComponent> facetMap = component.getFacets();
199     try
200     {
201       // bean info is cached
202       BeanInfo info = Introspector.getBeanInfo(component.getClass());
203       PropertyDescriptor[] pds = info.getPropertyDescriptors();
204       boolean foundProperty = false;
205       for (int i=0; i < pds.length; i++)
206       {
207         String propName = pds[i].getName();
208         if (facetName.equals(propName))
209         {
210           assertTrue("Facet bean accessor must return UIComponent or subclass",
211             UIComponent.class.isAssignableFrom(pds[i].getPropertyType()));
212 
213           foundProperty = true;
214 
215           Method reader = pds[i].getReadMethod();
216           Method writer = pds[i].getWriteMethod();
217           writer.invoke(component, new Object[] { propValue });
218           assertEquals("Property set not visible in facet map",
219                        facetMap.get(facetName), propValue);
220           facetMap.put(facetName, facetValue);
221           assertEquals("Facet put not visible in property value",
222                        reader.invoke(component, new Object[0]), facetValue);
223           break;
224         }
225       }
226 
227       if (!foundProperty)
228         fail("Unable to find facet property \"" + facetName + "\"");
229     }
230     catch (IntrospectionException e)
231     {
232       e.printStackTrace();
233       fail("Unable to access facet property \"" + facetName + "\"");
234     }
235     catch (InvocationTargetException e)
236     {
237       e.printStackTrace();
238       fail("Unable to access facet property \"" + facetName + "\"");
239     }
240     catch (IllegalAccessException e)
241     {
242       e.printStackTrace();
243       fail("Unable to access facet property \"" + facetName + "\"");
244     }
245     finally
246     {
247       mockFacetValue.verify();
248       mockPropValue.verify();
249     }
250   }
251 
252   /**
253    * Tests the apply-request-values lifecycle phase.
254    */
255   protected void doTestApplyRequestValues(
256     UIComponent component)
257   {
258     UIViewRoot root = new UIViewRoot();
259     doTestApplyRequestValues(root, component);
260   }
261 
262   /**
263    * Tests the apply-request-values lifecycle phase.
264    */
265   protected void doTestApplyRequestValues(
266     UIViewRoot  root,
267     UIComponent component)
268   {
269 
270     Mock mockRenderKitFactory = mock(RenderKitFactory.class);
271 
272     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
273     RenderKit renderkit = getMockRenderKitWrapper().getRenderKit();
274 
275     Mock mockRenderer = mock(Renderer.class);
276     Renderer renderer = (Renderer) mockRenderer.proxy();
277 
278     mockRenderKitFactory.stubs().method("getRenderKit").will(returnValue(renderkit));
279     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
280 
281     if (isRendererUsed() && component.isRendered())
282     {
283       mockRenderer.expects(once()).method("decode");
284     }
285     else
286     {
287       mockRenderer.expects(never()).method("decode");
288     }
289 
290     try
291     {
292       setCurrentContext(facesContext);
293       doTestApplyRequestValues(facesContext, root, component);
294     }
295     finally
296     {
297       setCurrentContext(null);
298     }
299 
300     mockRenderKitFactory.verify();
301     mockRenderkit.verify();
302     mockRenderer.verify();
303   }
304 
305 
306   @SuppressWarnings("unchecked")
307   protected void doTestApplyRequestValues(
308     FacesContext context,
309     UIViewRoot   root,
310     UIComponent  component)
311   {
312 
313     Mock mock = createMockUIComponent();
314     UIComponent child = (UIComponent) mock.proxy();
315 
316     // JavaServer Faces 1.0 Specification, section 2.2.2
317     // During the apply-request-values phase,
318     // only the processDecodes lifecycle method may be called.
319     if (willChildrenBeProcessed(component))
320       mock.expects(once()).method("processDecodes");
321 
322     // construct the UIComponent tree and
323     // execute the apply-request-values lifecycle phase
324     if (component.getParent() == null)
325       root.getChildren().add(component);
326 
327     component.getChildren().add(child);
328 
329     AttributeChangeTester attributeChangeTester = null;
330     if (component instanceof UIXComponent)
331     {
332       attributeChangeTester = new AttributeChangeTester();
333       ((UIXComponent) component).setAttributeChangeListener(attributeChangeTester);
334       ((UIXComponent) component).addAttributeChangeListener(attributeChangeTester);
335       AttributeChangeEvent ace =
336         new AttributeChangeEvent(component, "testProperty",
337                                  Boolean.FALSE, Boolean.TRUE);
338       ace.queue();
339     }
340 
341     root.processDecodes(context);
342 
343     if (attributeChangeTester != null)
344       attributeChangeTester.verify();
345 
346     mock.verify();
347   }
348 
349   /**
350    * Tests the process-validations lifecycle phase.
351    */
352   protected void doTestProcessValidations(
353     UIComponent component)
354   {
355     doTestProcessValidations(component, "submittedValue", "convertedValue");
356   }
357 
358   /**
359    * Tests the apply-request-values lifecycle phase.
360    */
361   protected void doTestProcessValidations(
362     UIComponent component,
363     Object      submittedValue,
364     Object      convertedValue)
365   {
366     UIViewRoot root = new UIViewRoot();
367     doTestProcessValidations(root, component, submittedValue, convertedValue);
368   }
369 
370   /**
371    * Tests the process-validations lifecycle phase.
372    */
373   protected void doTestProcessValidations(
374     UIViewRoot  root,
375     UIComponent component,
376     Object      submittedValue,
377     Object      convertedValue)
378   {
379 
380     Mock mockRenderKit = getMockRenderKitWrapper().getMock();
381 
382     Mock mockRenderer = mock(Renderer.class);
383     Renderer renderer = (Renderer) mockRenderer.proxy();
384     mockRenderKit.stubs().method("getRenderer").will(returnValue(renderer));
385 
386     Mock mockConverter = mock(Converter.class);
387     Converter converter = (Converter) mockConverter.proxy();
388 
389     Mock mockValidator = mock(Validator.class);
390     Validator validator = (Validator) mockValidator.proxy();
391 
392     Mock mockListener = mock(ValueChangeListener.class);
393     ValueChangeListener listener = (ValueChangeListener) mockListener.proxy();
394 
395     setCurrentContext(facesContext);
396 
397     // if the component is an EditableValueHolder, then the submitted value
398     // must be converted and validated before this phase completes.
399     if (component instanceof EditableValueHolder)
400     {
401 
402       EditableValueHolder editable = (EditableValueHolder)component;
403       mockConverter.expects(never()).method("getAsObject");
404       mockConverter.expects(never()).method("getAsString");
405       mockRenderer.expects(once()).method("getConvertedValue").will(returnValue(convertedValue));
406       editable.setConverter(converter);
407       editable.setSubmittedValue(submittedValue);
408       editable.addValidator(validator);
409       editable.addValueChangeListener(listener);
410 
411       mockListener.expects(once()).method("processValueChange");
412       mockValidator.expects(once()).method("validate").with(new Constraint[]  { eq(facesContext), eq(component), eq(convertedValue) });
413 
414     }
415     // if the component is a ValueHolder, then the value is not updated or
416     // validated and no value change event occurs.
417     else if (component instanceof ValueHolder)
418     {
419       ValueHolder holder = (ValueHolder)component;
420       holder.setConverter(converter);
421       mockConverter.expects(never()).method("getAsObject");//setExpectedGetAsObjectCalls(0);
422       mockConverter.expects(never()).method("getAsString");
423     }
424 
425     doTestProcessValidations(facesContext, root, component);
426 
427     mockRenderKit.verify();
428     mockRenderer.verify();
429     mockConverter.verify();
430     mockValidator.verify();
431     mockListener.verify();
432 
433     setCurrentContext(null);
434   }
435 
436 
437   @SuppressWarnings("unchecked")
438   protected void doTestProcessValidations(
439     FacesContext context,
440     UIViewRoot   root,
441     UIComponent  component)
442   {
443 
444     Mock mock = createMockUIComponent();
445     UIComponent child = (UIComponent) mock.proxy();
446 
447     // JavaServer Faces 1.0 Specification, section 2.2.3
448     // During the process-validations phase,
449     // only the processValidators lifecycle method may be called.
450     if (willChildrenBeProcessed(component))
451       mock.expects(once()).method("processValidators");
452 
453     // construct the UIComponent tree and
454     // execute the apply-request-values lifecycle phase
455     if (component.getParent() == null)
456       root.getChildren().add(component);
457     component.getChildren().add(child);
458 
459     root.processValidators(context);
460 
461     mock.verify();
462   }
463 
464   /**
465    * Tests the update-model-values lifecycle phase.
466    */
467   protected void doTestUpdateModelValues(
468     UIComponent component)
469   {
470     UIViewRoot root = new UIViewRoot();
471     doTestUpdateModelValues(root, component);
472   }
473 
474   /**
475    * Tests the update-model-values lifecycle phase.
476    */
477   protected void doTestUpdateModelValues(
478     UIViewRoot  root,
479     UIComponent component)
480   {
481 
482     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
483 
484     Mock mockRenderer = mock(Renderer.class);
485     Renderer renderer = (Renderer) mockRenderer.proxy();
486     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
487 
488     Mock mockBinding = mock(ValueBinding.class);
489     ValueBinding binding = (ValueBinding) mockBinding.proxy();
490 
491     setCurrentContext(facesContext);
492 
493     // if the component is an EditableValueHolder, then the value binding
494     // must be updated with the new value before this phase completes.
495     if (component instanceof EditableValueHolder)
496     {
497       EditableValueHolder editable = (EditableValueHolder)component;
498       component.setValueBinding("value", binding);
499       editable.setValue("newValue");
500       mockBinding.expects(atLeastOnce()).method("setValue").with(eq(facesContext), eq("newValue"));
501 
502       assertEquals(true, editable.isLocalValueSet());
503     }
504 
505     doTestUpdateModelValues(facesContext, root, component);
506 
507     setCurrentContext(null);
508 
509     mockRenderer.verify();
510     mockBinding.verify();
511   }
512 
513 
514   @SuppressWarnings("unchecked")
515   protected void doTestUpdateModelValues(
516     FacesContext context,
517     UIViewRoot   root,
518     UIComponent  component)
519   {
520     Mock mock = createMockUIComponent();
521     UIComponent child = (UIComponent) mock.proxy();
522 
523     // JavaServer Faces 1.0 Specification, section 2.2.4
524     // During the update-model-values phase,
525     // only the processUpdates lifecycle method may be called.
526     if (willChildrenBeProcessed(component))
527       mock.expects(once()).method("processUpdates");
528 
529     // construct the UIComponent tree and
530     // execute the apply-request-values lifecycle phase
531     if (component.getParent() == null)
532       root.getChildren().add(component);
533     component.getChildren().add(child);
534     root.processUpdates(context);
535 
536     mock.verify();
537   }
538 
539   /**
540    * Tests the invoke-application lifecycle phase.
541    */
542   protected void doTestInvokeApplication(
543     UIComponent   component,
544     FacesEvent    event)
545   {
546     try
547     {
548       setCurrentContext(facesContext);
549 
550       doTestInvokeApplication(facesContext, facesContext.getViewRoot(), component, event);
551 
552     }
553     finally
554     {
555       setCurrentContext(null);
556     }
557   }
558 
559 
560 
561   @SuppressWarnings("unchecked")
562   protected void doTestInvokeApplication(
563     FacesContext context,
564     UIViewRoot   root,
565     UIComponent  component,
566     FacesEvent   event)
567   {
568 
569     Mock mock = createMockUIComponent();
570     UIComponent child = (UIComponent) mock.proxy();
571     // JavaServer Faces 1.0 Specification, section 2.2.5
572     // During the invoke-application phase,
573     // no per-component lifecycle methods may be called.
574 
575     // construct the UIComponent tree and
576     // execute the apply-request-values lifecycle phase
577     root.getChildren().add(component);
578     if (event != null)
579       event.queue();
580 
581     component.getChildren().add(child);
582     root.processApplication(context);
583 
584     mock.verify();
585   }
586 
587   /**
588    * Tests the render-response lifecycle phase.
589    *
590    * @throws IOException  when test fails
591    */
592   @SuppressWarnings("unchecked")
593   protected void doTestRenderResponse(
594     UIComponent component) throws IOException
595   {
596 
597 //    MockRenderKitFactory factory = setupMockRenderKitFactory();
598 
599     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
600 
601     Mock mockRenderer = mock(Renderer.class);
602     Renderer renderer = (Renderer) mockRenderer.proxy();
603     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
604 
605     Mock mockChild = createMockUIComponent(); //mock(UIComponent.class);
606     UIComponent child = (UIComponent) mockChild.proxy();
607 
608     mockChild.expects(atLeastOnce()).method("getParent").will(returnValue(null));
609     mockChild.expects(atLeastOnce()).method("isTransient").will(returnValue(false));
610     mockChild.expects(atLeastOnce()).method("getRendersChildren").will(returnValue(true));
611 
612     UIViewRoot root = new UIViewRoot();
613 
614     mockRenderer.expects(atLeastOnce()).method("getRendersChildren").will(returnValue(false));
615     mockRenderer.expects(never()).method("decode");
616     mockRenderer.expects(never()).method("getConvertedValue");
617     mockRenderer.expects(never()).method("encodeChildren");
618 
619     if (isRendererUsed())
620     {
621       mockRenderer.expects(once()).method("encodeBegin");
622       mockRenderer.expects(once()).method("encodeEnd");
623     }
624     else
625     {
626       mockRenderer.expects(never()).method("encodeBegin");
627       mockRenderer.expects(never()).method("encodeEnd");
628     }
629 
630     // JavaServer Faces 1.0 Specification, section 2.2.6
631     // During the render-response phase,
632     // only the encodeBegin, encodeEnd, encodeChildren
633     // and processSaveState lifecycle methods may be called.
634     mockChild.expects(never()).method("processRestoreState");
635     mockChild.expects(never()).method("processDecodes");
636     mockChild.expects(never()).method("processValidators");
637     mockChild.expects(never()).method("processUpdates");
638     mockChild.expects(once()).method("processSaveState");
639 
640     //fix this!
641     mockChild.expects(once()).method("encodeBegin");
642     mockChild.expects(once()).method("encodeChildren");
643     mockChild.expects(once()).method("encodeEnd");
644 
645     root.getChildren().add(component);
646     component.getChildren().add(child);
647 
648     FacesContext current = FacesContext.getCurrentInstance();
649     try
650     {
651       TestFacesContext.setCurrentInstance(facesContext);
652       root.processSaveState(facesContext);
653       doRenderResponse(facesContext, root);
654     }
655     finally
656     {
657       TestFacesContext.setCurrentInstance(current);
658     }
659 
660     mockRenderer.verify();
661     mockChild.verify();
662   }
663 
664   protected void doTestValidateFailure(
665     UIViewRoot root)
666   {
667     // -= Simon =-
668     // All those variables do not seem to be used and do not seem
669     // to test anything either
670     /*Mock mockRenderkit = getMockRenderKitWrapper().getMock();
671     RenderKit renderkit = getMockRenderKitWrapper().getRenderKit();
672     */
673     Mock mockRenderer = mock(Renderer.class);
674     /*Renderer renderer = (Renderer) mockRenderer.proxy();
675 
676     Mock mockValidator = mock(Validator.class);
677     Validator validator = (Validator) mockValidator.proxy();
678 
679     ViewHandler viewhandler = this.facesContext.getApplication().getViewHandler();*/
680 
681     setCurrentContext(facesContext);
682 
683     root.processValidators(facesContext);
684 
685     mockRenderer.verify();
686 
687     setCurrentContext(null);
688   }
689 
690   /**
691    * Creates a MockUIComponent that does not expect to have
692    * any of its lifecycle methods invoked;  if you expect to
693    * have any invoked, override the "expected calls" for
694    * that lifecycle method.
695    */
696   protected Mock createMockUIComponent()
697   {
698     Mock mock = mock(UIComponent.class);
699 
700     mock.stubs().method("getParent").will(returnValue(null));
701     mock.stubs().method("setParent");
702     mock.stubs().method("getFacetsAndChildren").will(returnIterator(Collections.emptyList()));
703     mock.stubs().method("isRendered").will(returnValue(true));
704     
705     mock.stubs().method("getAttributes").will(returnValue(Collections.emptyMap()));
706     mock.stubs().method("pushComponentToEL").withAnyArguments();
707     mock.stubs().method("popComponentFromEL").withAnyArguments();
708     
709     mock.expects(never()).method("processRestoreState");
710     mock.expects(never()).method("processDecodes");
711     mock.expects(never()).method("processValidators");
712     mock.expects(never()).method("processUpdates");
713     mock.expects(never()).method("processSaveState");
714     mock.expects(never()).method("encodeBegin");
715     mock.expects(never()).method("encodeChildren");
716     mock.expects(never()).method("encodeEnd");
717 
718     return mock;
719   }
720 
721   protected boolean willChildrenBeProcessed(UIComponent component)
722   {
723     return (component.isRendered());
724   }
725 
726   protected boolean willChildrenBeRendered(UIComponent component)
727   {
728     return true;
729   }
730 
731   protected boolean isRendererUsed()
732   {
733     return _isRendererUsed;
734   }
735 
736   protected void setRendererUsed(boolean isRendererUsed)
737   {
738     _isRendererUsed = isRendererUsed;
739   }
740 
741   private boolean _isRendererUsed = true;
742   private MockRequestContext _mockRequestContext;
743 }