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