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.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  
25  import javax.faces.component.EditableValueHolder;
26  import javax.faces.component.StateHolder;
27  import javax.faces.component.UIComponent;
28  import javax.faces.component.UIViewRoot;
29  import javax.faces.context.FacesContext;
30  import javax.faces.el.EvaluationException;
31  import javax.faces.el.PropertyNotFoundException;
32  import javax.faces.el.ValueBinding;
33  import javax.faces.render.Renderer;
34  
35  import junit.framework.AssertionFailedError;
36  import junit.framework.Test;
37  import junit.framework.TestSuite;
38  
39  import junit.textui.TestRunner;
40  
41  import org.apache.myfaces.trinidad.model.ModelUtils;
42  import org.apache.myfaces.trinidad.model.SortableModel;
43  import org.apache.myfaces.trinidad.context.MockRequestContext;
44  
45  /**
46   * Unit tests for UIXTable
47   *
48   */
49  public class UIXTableTest extends UIComponentTestCase
50  {
51    /**
52     * @param testName  the unit test name
53     */
54    public UIXTableTest(
55      String testName)
56    {
57      super(testName);
58    }
59  
60  
61    private MockRequestContext _mafct;
62  
63    @Override
64    public void setUp() throws Exception
65    {
66      super.setUp();
67      _mafct = new MockRequestContext();
68    }
69  
70    @Override
71    public void tearDown() throws Exception
72    {
73      _mafct.release();
74      _mafct = null;
75      super.tearDown();
76    }
77  
78  
79    public static Test suite()
80    {
81      return new TestSuite(UIXTableTest.class);
82    }
83  
84    /**
85     * Tests the initial values for the component attributes.
86     */
87    public void testInitialAttributeValues()
88    {
89      UIXTable table = _createTable();
90      assertEquals(25, table.getRows());
91      assertEquals(0, table.getFirst());
92      assertFalse(table.isImmediate());
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   public void testAttributeTransparency()
101   {
102     UIXTable table = _createTable();
103     doTestAttributeTransparency(table, "var", "row", "emp");
104     doTestAttributeTransparency(table, "value", "row", "emp");
105     doTestAttributeTransparency(table, "first", new Integer(0), new Integer(1));
106     doTestAttributeTransparency(table, "immediate", Boolean.TRUE, Boolean.FALSE);
107     doTestAttributeTransparency(table, "rows", new Integer(30), new Integer(10));
108   }
109 
110   public void testModelMethods()
111   {
112     TestTable table = _createTable();
113     SortableModel model = table.model;
114     int sz = model.getRowCount();
115     assertEquals(sz, table.getRowCount());
116     for(int i=0; i<sz; i++)
117     {
118       table.setRowIndex(i);
119       assertEquals(i, model.getRowIndex());
120       assertEquals(model.getRowKey(), table.getRowKey());
121       assertEquals(model.isRowAvailable(), table.isRowAvailable());
122       assertEquals(model.getRowData(), table.getRowData());
123     }
124     table.setRowIndex(-1);
125     assertEquals(-1, model.getRowIndex());
126     assertEquals(model.getRowKey(), table.getRowKey());
127     assertEquals(model.isRowAvailable(), table.isRowAvailable());
128   }
129 
130   public void testProcessDecodes()
131   {
132     TestTable table = _createTable();
133     table.setRows(10);
134     table.setFirst(3);
135     doTestApplyRequestValues(table);
136     _testColumnChild(table, table.column1Child.getDecodesRowData());
137     _testColumnChild(table, table.column2Child.getDecodesRowData());
138 
139     assertEquals(1, table.column1Header.getDecodesRowData().size());
140     assertEquals(1, table.column2Header.getDecodesRowData().size());
141 
142     List<Object> detailData = table.detailStamp.getDecodesRowData();
143     _testDetailStamp(table, detailData);
144 
145   }
146 
147   public void testProcessValidators()
148   {
149     TestTable table = _createTable();
150     table.setRows(10);
151     table.setFirst(3);
152     doTestProcessValidations(table);
153     _testColumnChild(table, table.column1Child.getValidatesRowData());
154     _testColumnChild(table, table.column2Child.getValidatesRowData());
155 
156     assertEquals(1, table.column1Header.getValidatesRowData().size());
157     assertEquals(1, table.column2Header.getValidatesRowData().size());
158 
159     List<Object> detailData = table.detailStamp.getValidatesRowData();
160     _testDetailStamp(table, detailData);
161 
162   }
163 
164   public void testProcessUpdates()
165   {
166     TestTable table = _createTable();
167     table.setRows(10);
168     table.setFirst(3);
169     doTestUpdateModelValues(table);
170     _testColumnChild(table, table.column1Child.getUpdatesRowData());
171     _testColumnChild(table, table.column2Child.getUpdatesRowData());
172 
173     assertEquals(1, table.column1Header.getUpdatesRowData().size());
174     assertEquals(1, table.column2Header.getUpdatesRowData().size());
175 
176     List<Object> detailData = table.detailStamp.getUpdatesRowData();
177     _testDetailStamp(table, detailData);
178 
179   }
180 
181   public void testEditableValueHolderChildren()
182   {
183     TestTable table = _createTable();
184     UIXInput testComp = table.column1Child;
185     UIXInput detailStamp = table.detailStamp;
186 
187     // initialize:
188     table.setRowIndex(0);
189     _setEVHData(testComp, "Foo", true);
190     _setEVHData(detailStamp, "Foo-ds", false);
191 
192     table.setRowIndex(1);
193     _setEVHData(testComp, "Bar", false);
194     _setEVHData(detailStamp, "Bar-ds", true);
195 
196     // now test:
197     table.setRowIndex(0);
198     _testEVHData(testComp, "Foo", true);
199     _testEVHData(detailStamp, "Foo-ds", false);
200 
201     table.setRowIndex(1);
202     _testEVHData(testComp, "Bar", false);
203     _testEVHData(detailStamp, "Bar-ds", true);
204 
205   }
206 
207 
208   public void testSaveRestoreState()
209   {
210     final Object state;
211     {
212       TestTable table = _createTable();
213 
214       UIXInput testComp = table.column1Child;
215 
216       // initialize:
217       table.setRowIndex(0);
218       _setEVHData(testComp, "Foo", true);
219 
220       table.setRowIndex(1);
221       _setEVHData(testComp, "Bar", false);
222 
223       // note that we did not set rowIndex back to -1.
224       // processSaveState should store the stamp data even though rowIndex was
225       // not changed.
226 
227       state = table.processSaveState(facesContext);
228     }
229 
230     TestTable table = _createTable();
231     UIXInput testComp = table.column1Child;
232     table.processRestoreState(facesContext, state);
233 
234     // now test:
235     table.setRowIndex(0);
236     _testEVHData(testComp, "Foo", true);
237 
238     table.setRowIndex(1);
239     _testEVHData(testComp, "Bar", false);
240 
241   }
242 
243   /**
244    * make sure that the model is not executed at invalid or unnecessary times.
245    * valueBindings cannot be called during restoreState.
246    * Also table model must not be executed if rendered="false".
247    * However, saveState is called even if rendered="false" on a component.
248    * Therefore, saveState should not call getValue() on the table.
249    */
250   public void testSaveRestoreStateGetValue()
251   {
252     // make sure that getValue() is not called during restoreState:
253     DoNotCallBinding doNotCall = new DoNotCallBinding();
254     doNotCall.doNotCall = true;
255     final Object state;
256     {
257       TestTable table = _createTable(false);
258       table.setValue(null); // instead use the valueBinding below:
259       table.setValueBinding("value", doNotCall);
260       state = table.processSaveState(facesContext);
261     }
262 
263     TestTable table = _createTable(false);
264     table.setValue(null); // instead use the valueBinding below:
265     // this value binding should be restored during processRestoreState;
266     // however, set it anyway just to catch any getValue() calls prior to
267     // that.
268     table.setValueBinding("value", doNotCall);
269     table.processRestoreState(facesContext, state);
270 
271     assertTrue(table.getValueBinding("value") instanceof DoNotCallBinding);
272 
273   }
274 
275   @SuppressWarnings("unchecked")
276   public void testNotRenderedSaveRestoreState()
277   {
278     // this test is to make sure no exceptions are thrown during save/restore
279     // state of the table. Exceptions were being thrown earlier because
280     // model and certain initial state were computed lazily.
281     final Object state;
282     {
283       UIXPanel panel = new UIXPanel();
284       TestTable table = _createTable(false);
285       table.setRendered(false);
286       panel.getChildren().add(table);
287       state = panel.processSaveState(facesContext);
288     }
289 
290     UIXPanel panel = new UIXPanel();
291     TestTable table = _createTable(false);
292     panel.getChildren().add(table);
293 
294     panel.processRestoreState(facesContext, state);
295 
296   }
297 
298   @SuppressWarnings("unchecked")
299   @Override
300   protected void doTestUpdateModelValues(
301     FacesContext context,
302     UIViewRoot   root,
303     UIComponent  component)
304   {
305     root.getChildren().add(component);
306     root.processUpdates(context);
307   }
308 
309   @SuppressWarnings("unchecked")
310   @Override
311   protected void doTestProcessValidations(
312     FacesContext context,
313     UIViewRoot   root,
314     UIComponent  component)
315   {
316     root.getChildren().add(component);
317     root.processValidators(context);
318   }
319 
320   @Override
321   public void setCurrentContext(FacesContext fc)
322   {
323     // prevent removal of facesContext before we are done testing:
324     if (fc != null)
325       super.setCurrentContext(fc);
326   }
327 
328   private void _setEVHData(
329     EditableValueHolder testComp,
330     String value,
331     boolean isValid)
332   {
333     testComp.setSubmittedValue("submitedValue-"+value);
334     testComp.setValue("Value-"+value);
335     testComp.setValid(isValid);
336   }
337 
338   private void _testEVHData(
339     EditableValueHolder testComp,
340     String value,
341     boolean isValid)
342   {
343     assertEquals("submitedValue-"+value, testComp.getSubmittedValue());
344     assertEquals("Value-"+value, testComp.getLocalValue());
345     assertEquals(isValid, testComp.isValid());
346   }
347 
348   private void _testDetailStamp(TestTable table, List<Object> detailData)
349   {
350     assertEquals(1, detailData.size());
351     table.setRowIndex(_DISCLOSED_INDEX);
352     assertEquals(table.getRowData(), detailData.get(0));
353   }
354 
355   private void _testColumnChild(TestTable table, List<Object> rowData)
356   {
357     // make sure that the rowData values that were seen during this phase
358     // were the correct values:
359     int rows = table.getRows();
360     assertEquals(rows, rowData.size());
361     int first = table.getFirst();
362     for(int i=0; i<rows; i++)
363     {
364       table.setRowIndex(i+first);
365       assertEquals(table.getRowData(), rowData.get(i));
366     }
367   }
368 
369   public static void main(String[] args)
370   {
371     TestRunner.run(UIXTableTest.class);
372 //    UIXTableTest test = new UIXTableTest("aasd");
373 //    test.testSaveRestoreStateGetValue();
374   }
375 
376   @SuppressWarnings("unchecked")
377   @Override
378   protected void doTestApplyRequestValues(
379     FacesContext context,
380     UIViewRoot   root,
381     UIComponent  component)
382   {
383     root.getChildren().add(component);
384     root.processDecodes(context);
385   }
386 
387   @Override
388   protected boolean isRendererUsed()
389   {
390     // we use our own MockRenderer; not the one created by our super class:
391     return false;
392   }
393 
394   private TestTable _createTable()
395   {
396     return _createTable(true);
397   }
398 
399   private TestTable _createTable(boolean useModel)
400   {
401     SortableModel model = useModel ? _createTableData() : null;
402     TestTable table = new TestTable(model);
403     return table;
404   }
405 
406   private static SortableModel _createTableData()
407   {
408     final int sz = 25;
409     List<Object> data = new ArrayList<Object>(sz);
410     for(int i=0; i<sz; i++)
411     {
412       data.add(new Integer(i));
413     }
414     return new SortableModel(ModelUtils.toDataModel(data));
415   }
416 
417   // must be public static so that it can be state-saved:
418   public static final class DoNotCallBinding extends ValueBinding
419     implements StateHolder
420   {
421     @Override
422     public Class<?> getType(FacesContext context) throws EvaluationException, PropertyNotFoundException
423     {
424       throw new AssertionFailedError("This method should not be called");
425     }
426 
427     @Override
428     public Object getValue(FacesContext context) throws EvaluationException, PropertyNotFoundException
429     {
430       if (doNotCall)
431         throw new AssertionFailedError("This method should not be called");
432 
433       return _createTableData();
434     }
435 
436     @Override
437     public boolean isReadOnly(FacesContext context) throws EvaluationException, PropertyNotFoundException
438     {
439       throw new AssertionFailedError("This method should not be called");
440     }
441 
442     @Override
443     public void setValue(FacesContext context, Object value) throws EvaluationException, PropertyNotFoundException
444     {
445       throw new AssertionFailedError("This method should not be called");
446     }
447 
448     public Object saveState(FacesContext context)
449     {
450       return Boolean.FALSE;
451     }
452 
453     public void restoreState(FacesContext context, Object state)
454     {
455       // apon restoring state, to not allow getValue() calls:
456       doNotCall = true;
457     }
458 
459     public boolean isTransient()
460     {
461       return false;
462     }
463 
464     public void setTransient(boolean newTransientValue)
465     {
466     }
467 
468     public boolean doNotCall = false;
469   }
470 
471   private static final class TestComponent extends UIXInput
472   {
473     public TestComponent(String id)
474     {
475       _decodes   = Collections.emptyList();
476       _updates   = Collections.emptyList();
477       _validates = Collections.emptyList();
478       setId(id);
479     }
480 
481     @Override
482     public void processDecodes(FacesContext fc)
483     {
484       _decodes = _addRowData(fc, _decodes);
485     }
486 
487     @Override
488     public void processUpdates(FacesContext fc)
489     {
490       _updates = _addRowData(fc, _updates);
491     }
492 
493     @Override
494     public void processValidators(FacesContext fc)
495     {
496       _validates = _addRowData(fc, _validates);
497     }
498 
499     public List<Object> getDecodesRowData()
500     {
501       return _decodes;
502     }
503 
504     public List<Object> getValidatesRowData()
505     {
506       return _validates;
507     }
508 
509     public List<Object> getUpdatesRowData()
510     {
511       return _updates;
512     }
513 
514     @Override
515     public String toString()
516     {
517       return getId();
518     }
519 
520     @Override
521     protected Renderer getRenderer(FacesContext fc)
522     {
523       // for testing purposes must return null. Otherwise a renderer
524       // will be needed to compute the clientID:
525       return null;
526     }
527 
528     private List<Object> _addRowData(FacesContext fc, List<Object> currList)
529     {
530       if (currList == Collections.emptyList())
531         currList = new ArrayList<Object>(10);
532 
533       Object rowData = fc.getExternalContext().getRequestMap().get(_VAR);
534       currList.add(rowData);
535       return currList;
536     }
537 
538     private List<Object> _decodes;
539     private List<Object> _updates;
540     private List<Object> _validates;
541   }
542 
543   private static final String _VAR = "row";
544 
545   private static final class TestTable extends UIXTable
546   {
547     @SuppressWarnings("unchecked")
548     public TestTable(SortableModel model)
549     {
550       super();
551       setValue(model);
552       this.model = model;
553       setVar(_VAR);
554       setId("table1");
555 
556       UIXColumn column1 = new UIXColumn()
557       {
558         @Override
559         protected Renderer getRenderer(FacesContext fc)
560         {
561           // for testing purposes must return null. Otherwise a renderer
562           // will be needed to compute the clientID:
563           return null;
564         }
565       };
566       column1.setId("col1");
567       column1Header = new TestComponent("col1Header");
568       column1.setHeader(column1Header);
569       column1Child = new TestComponent("col1Child");
570       column1.getChildren().add(column1Child);
571 
572       UIXColumn column2 = new UIXColumn()
573       {
574         @Override
575         protected Renderer getRenderer(FacesContext fc)
576         {
577           // for testing purposes must return null. Otherwise a renderer
578           // will be needed to compute the clientID:
579           return null;
580         }
581       };
582       column2.setId("col2");
583       column2Header = new TestComponent("col2Header");
584       column2.setHeader(column2Header);
585       column2Child = new TestComponent("col2Child");
586       column2.getChildren().add(column2Child);
587 
588       List<UIComponent> kids = getChildren();
589       kids.add(column1);
590       kids.add(column2);
591 
592       detailStamp = new TestComponent("detail");
593       setDetailStamp(detailStamp);
594 
595       if (model != null)
596       {
597         setRowIndex(_DISCLOSED_INDEX);
598         getDisclosedRowKeys().add();
599         setRowIndex(-1);
600       }
601     }
602 
603     @Override
604     protected Renderer getRenderer(FacesContext fc)
605     {
606       // for testing purposes must return null. Otherwise a renderer
607       // will be needed to compute the clientID:
608       return null;
609     }
610 
611     public final SortableModel model;
612     public final TestComponent column1Header, column1Child,
613       column2Header, column2Child, detailStamp;
614   }
615 
616   private static final int _DISCLOSED_INDEX = 5;
617 }