blob: 8e331b050c56b034316ad78cd168941f71052332 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package jakarta.faces.component;
import jakarta.faces.application.StateManager;
import jakarta.faces.component.html.HtmlPanelGroup;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.PhaseId;
import jakarta.faces.render.Renderer;
import org.apache.myfaces.test.base.junit.AbstractFacesTestCase;
import org.apache.myfaces.test.mock.MockRenderedValueExpression;
import org.apache.myfaces.test.mock.visit.MockVisitContext;
import org.easymock.classextension.EasyMock;
import org.easymock.classextension.IMocksControl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.*;
public class UIDataTest extends AbstractFacesTestCase
{
private IMocksControl _mocksControl;
private UIData _testImpl;
@Override
@BeforeEach
public void setUp() throws Exception
{
super.setUp();
_mocksControl = EasyMock.createControl();
_testImpl = new UIData();
}
/**
* Test method for
* {@link jakarta.faces.component.UIData#setValueExpression(java.lang.String, jakarta.el.ValueExpression)}.
*/
@Test
public void testValueExpression()
{
assertSetValueExpressionException(IllegalArgumentException.class, "rowIndex");
assertSetValueExpressionException(NullPointerException.class, null);
}
private void assertSetValueExpressionException(Class<? extends Throwable> expected, final String name)
{
Assertions.assertThrows(expected,
() -> _testImpl.setValueExpression(name, null));
}
/**
* Test method for {@link jakarta.faces.component.UIData#getClientId(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testGetClientId()
{
_testImpl.setId("xxx");
Renderer renderer = _mocksControl.createMock(Renderer.class);
renderKit.addRenderer(UIData.COMPONENT_FAMILY, UIData.COMPONENT_TYPE, renderer);
Assertions.assertEquals("xxx", _testImpl.getClientId(facesContext));
_testImpl.setRowIndex(99);
//MYFACES-2744 UIData.getClientId() should not append rowIndex, instead use UIData.getContainerClientId()
Assertions.assertEquals("xxx", _testImpl.getClientId(facesContext));
Assertions.assertEquals("xxx:99", _testImpl.getContainerClientId(facesContext));
}
/**
* Test method for
* {@link jakarta.faces.component.UIData#invokeOnComponent(jakarta.faces.context.FacesContext, java.lang.String, jakarta.faces.component.ContextCallback)}
* .
* Tests, if invokeOnComponent also checks the facets of the h:column children (MYFACES-2370)
*/
@Test
public void testInvokeOnComponentFacesContextStringContextCallback()
{
/**
* Concrete class of ContextCallback needed to test invokeOnComponent.
*/
final class MyContextCallback implements ContextCallback
{
private boolean invoked = false;
public void invokeContextCallback(FacesContext context,
UIComponent target)
{
this.invoked = true;
}
}
UIComponent facet = new UIOutput();
facet.setId("facet");
UIColumn column = new UIColumn();
column.setId("column");
column.getFacets().put("header", facet);
_testImpl.setId("uidata");
_testImpl.getChildren().add(column);
MyContextCallback callback = new MyContextCallback();
_testImpl.invokeOnComponent(facesContext, facet.getClientId(facesContext), callback);
// the callback should have been invoked
Assertions.assertTrue(callback.invoked);
}
/**
* Test method for {@link jakarta.faces.component.UIData#broadcast(jakarta.faces.event.FacesEvent)}.
*/
@Test
public void testBroadcastFacesEvent()
{
// create event mock
final FacesEvent originalEvent = _mocksControl.createMock(FacesEvent.class);
// create the component for the event
UIComponent eventComponent = new UICommand()
{
@Override
public void broadcast(FacesEvent event)
throws AbortProcessingException
{
// the event must be the originalEvent
Assertions.assertEquals(originalEvent, event);
// the current row index must be the row index from the time the event was queued
Assertions.assertEquals(5, _testImpl.getRowIndex());
// the current component must be this (pushComponentToEL() must have happened)
Assertions.assertEquals(this, UIComponent.getCurrentComponent(facesContext));
// to be able to verify that broadcast() really has been called
getAttributes().put("broadcastCalled", Boolean.TRUE);
}
};
// set component on event
EasyMock.expect(originalEvent.getComponent()).andReturn(eventComponent).anyTimes();
// set phase on event
EasyMock.expect(originalEvent.getPhaseId()).andReturn(PhaseId.INVOKE_APPLICATION).anyTimes();
_mocksControl.replay();
// set PhaseId for event processing
facesContext.setCurrentPhaseId(PhaseId.INVOKE_APPLICATION);
// set row index for event
_testImpl.setRowIndex(5);
// UIData must be a child of UIViewRoot to queue and event
facesContext.getViewRoot().getChildren().add(_testImpl);
// queue event (this will create a FacesEventWrapper with the current row index)
_testImpl.queueEvent(originalEvent);
// change the current row index
_testImpl.setRowIndex(0);
// now broadcast the event (this will call UIData.broadcast())
facesContext.getViewRoot().broadcastEvents(facesContext, PhaseId.INVOKE_APPLICATION);
// -= Assertions =-
// the current component must be null (popComponentFromEL() must have happened)
Assertions.assertNull(UIComponent.getCurrentComponent(facesContext));
// the row index must now be 0 (at broadcast() it must be 5)
Assertions.assertEquals(0, _testImpl.getRowIndex());
// verify mock behavior
_mocksControl.verify();
// verify that broadcast() really has been called
Assertions.assertEquals(Boolean.TRUE, eventComponent.getAttributes().get("broadcastCalled"));
}
/**
* Test method for {@link jakarta.faces.component.UIData#encodeBegin(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testEncodeBeginFacesContext()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#encodeEnd(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testEncodeEndFacesContext()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#queueEvent(jakarta.faces.event.FacesEvent)}.
*/
@Test
public void testQueueEventFacesEvent()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#processDecodes(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testProcessDecodesFacesContext()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#processValidators(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testProcessValidatorsFacesContext()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#processUpdates(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testProcessUpdatesFacesContext()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#saveState(jakarta.faces.context.FacesContext)}.
*/
@Test
public void testSaveState()
{
// TODO
}
/**
* Test method for
* {@link jakarta.faces.component.UIData#restoreState(jakarta.faces.context.FacesContext, java.lang.Object)}.
*/
@Test
public void testRestoreState()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#UIData()}.
*/
@Test
public void testUIData()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setFooter(jakarta.faces.component.UIComponent)}.
*/
@Test
public void testSetFooter()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getFooter()}.
*/
@Test
public void testGetFooter()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setHeader(jakarta.faces.component.UIComponent)}.
*/
@Test
public void testSetHeader()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getHeader()}.
*/
@Test
public void testGetHeader()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#isRowAvailable()}.
*/
@Test
public void testIsRowAvailable()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getRowCount()}.
*/
@Test
public void testGetRowCount()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getRowData()}.
*/
@Test
public void testGetRowData()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getRowIndex()}.
*/
@Test
public void testGetRowIndex()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setRowIndex(int)}.
*/
@Test
public void testSetRowIndex()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getDataModel()}.
*/
@Test
public void testGetDataModel()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setDataModel(jakarta.faces.model.DataModel)}.
*/
@Test
public void testSetDataModel()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setValue(java.lang.Object)}.
*/
@Test
public void testSetValue()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setRows(int)}.
*/
@Test
public void testSetRows()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setFirst(int)}.
*/
@Test
public void testSetFirst()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getValue()}.
*/
@Test
public void testGetValue()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getVar()}.
*/
@Test
public void testGetVar()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#setVar(java.lang.String)}.
*/
@Test
public void testSetVar()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getRows()}.
*/
@Test
public void testGetRows()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getFirst()}.
*/
@Test
public void testGetFirst()
{
// TODO
}
/**
* Test method for {@link jakarta.faces.component.UIData#getFamily()}.
*/
@Test
public void testGetFamily()
{
// TODO
}
/**
* Test method for
* {@link jakarta.faces.component.UIData#visitTree(jakarta.faces.component.visit.VisitContext, jakarta.faces.component.visit.VisitCallback)}.
*/
@Test
public void testVisitTree() {
UIData uidata = new UIData();
// value
Collection<String> value = new ArrayList<String>();
value.add("value#1");
value.add("value#2");
uidata.setValue(value);
// header facet
UIComponent headerFacet = new HtmlPanelGroup();
headerFacet.setId("headerFacet");
uidata.setHeader(headerFacet);
// footer facet
UIComponent footerFacet = new HtmlPanelGroup();
footerFacet.setId("footerFacet");
uidata.setFooter(footerFacet);
// first child
UIComponent child1 = new UIColumn();
// facet of first child
UIComponent facetOfChild1 = new HtmlPanelGroup();
child1.getFacets().put("someFacet", facetOfChild1);
// child of first child
UIOutput childOfChild1 = new UIOutput();
childOfChild1.setId("childOfColumn");
child1.getChildren().add(childOfChild1);
uidata.getChildren().add(child1);
// second child (should not be processed --> != UIColumn)
UIComponent child2 = new HtmlPanelGroup();
uidata.getChildren().add(child2);
VisitCallback callback = null;
IMocksControl control = EasyMock.createControl();
VisitContext visitContextMock = control.createMock(VisitContext.class);
EasyMock.expect(visitContextMock.getFacesContext()).andReturn(facesContext).anyTimes();
EasyMock.expect(visitContextMock.getHints()).andReturn(Collections.emptySet()).anyTimes();
Collection<String> subtreeIdsToVisit = new ArrayList<String>();
subtreeIdsToVisit.add("1");
EasyMock.expect(visitContextMock.getSubtreeIdsToVisit(uidata)).andReturn(subtreeIdsToVisit);
EasyMock.expect(visitContextMock.invokeVisitCallback(uidata, callback)).andReturn(VisitResult.ACCEPT);
EasyMock.expect(visitContextMock.invokeVisitCallback(headerFacet, callback)).andReturn(VisitResult.ACCEPT);
EasyMock.expect(visitContextMock.invokeVisitCallback(footerFacet, callback)).andReturn(VisitResult.ACCEPT);
EasyMock.expect(visitContextMock.invokeVisitCallback(facetOfChild1, callback)).andReturn(VisitResult.ACCEPT);
EasyMock.expect(visitContextMock.invokeVisitCallback(child1, callback)).andReturn(VisitResult.ACCEPT);
EasyMock.expect(visitContextMock.invokeVisitCallback(childOfChild1, callback)).andReturn(VisitResult.ACCEPT).times(2);
control.replay();
uidata.visitTree(visitContextMock, callback);
control.verify();
// VisitHint.SKIP_ITERATION test:
// (1) uiData with two rows - should iterate over row twice
MockVisitContext mockVisitContext = new MockVisitContext(facesContext, null);
CountingVisitCallback countingVisitCallback = new CountingVisitCallback(2);
uidata.visitTree(mockVisitContext, countingVisitCallback);
countingVisitCallback.verify();
// (2) uiData with two values - should iterate over row ones - SKIP_ITERATION is used
mockVisitContext = new MockVisitContext(facesContext, EnumSet.of(VisitHint.SKIP_ITERATION));
countingVisitCallback = new CountingVisitCallback(1);
uidata.visitTree(mockVisitContext, countingVisitCallback);
countingVisitCallback.verify();
// (3) uiData with five values - should iterate over row five times
value = new ArrayList<String>();
value.add("1");
value.add("2");
value.add("3");
value.add("4");
value.add("5");
uidata.setValue(value);
mockVisitContext = new MockVisitContext(facesContext, null);
countingVisitCallback = new CountingVisitCallback(5);
uidata.visitTree(mockVisitContext, countingVisitCallback);
countingVisitCallback.verify();
// (4) uiData with five values - should iterate over child ones - SKIP_ITERATION is used
mockVisitContext = new MockVisitContext(facesContext, EnumSet.of(VisitHint.SKIP_ITERATION));
countingVisitCallback = new CountingVisitCallback(1);
uidata.visitTree(mockVisitContext, countingVisitCallback);
countingVisitCallback.verify();
}
public static class RowData
{
private String text;
public RowData(String text, String style)
{
super();
this.text = text;
this.style = style;
}
private String style;
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
public String getStyle()
{
return style;
}
public void setStyle(String style)
{
this.style = style;
}
}
@Test
public void testPreserveRowComponentState1() throws Exception
{
List<RowData> model = new ArrayList<RowData>();
model.add(new RowData("text1","style1"));
model.add(new RowData("text1","style2"));
model.add(new RowData("text1","style3"));
model.add(new RowData("text1","style4"));
//Put on request map to be resolved later
request.setAttribute("list", model);
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
UIColumn column = new UIColumn();
UIOutput text = new UIOutput();
//This is only required if markInitiaState fix is not used
root.setId(root.createUniqueId());
table.setId(root.createUniqueId());
column.setId(root.createUniqueId());
text.setId(root.createUniqueId());
table.setVar("row");
table.setRowStatePreserved(true);
table.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{list}",List.class));
text.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{row.text}",String.class));
root.getChildren().add(table);
table.getChildren().add(column);
column.getChildren().add(text);
//Simulate markInitialState call.
facesContext.getAttributes().put(StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
root.markInitialState();
table.markInitialState();
column.markInitialState();
text.markInitialState();
facesContext.getAttributes().remove(StateManager.IS_BUILDING_INITIAL_STATE);
//Check the value expressions are working and change the component state
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals(rowData.getText(), text.getValue());
text.getAttributes().put("style", rowData.getStyle());
}
//Reset row index
table.setRowIndex(-1);
//Check the values were not lost
for (int i = 0; i < model.size(); i++)
{
table.setRowIndex(i);
Assertions.assertEquals(model.get(i).getStyle(), text.getAttributes().get("style"));
}
}
@Test
public void testCollectionDataModel() throws Exception
{
SimpleCollection<RowData> model = new SimpleCollection<RowData>();
model.add(new RowData("text1","style1"));
model.add(new RowData("text1","style2"));
model.add(new RowData("text1","style3"));
model.add(new RowData("text1","style4"));
//Put on request map to be resolved later
request.setAttribute("list", model);
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
UIColumn column = new UIColumn();
UIOutput text = new UIOutput();
//This is only required if markInitiaState fix is not used
root.setId(root.createUniqueId());
table.setId(root.createUniqueId());
column.setId(root.createUniqueId());
text.setId(root.createUniqueId());
table.setVar("row");
table.setRowStatePreserved(true);
table.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{list}", Collection.class));
text.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{row.text}",String.class));
root.getChildren().add(table);
table.getChildren().add(column);
column.getChildren().add(text);
//Check the value expressions are working and change the component state
int i = 0;
for (Iterator<RowData> it = model.iterator(); it.hasNext();)
{
RowData rowData = it.next();
table.setRowIndex(i);
Assertions.assertEquals(rowData.getText(), text.getValue());
i++;
}
//Reset row index
table.setRowIndex(-1);
}
private final class SimpleCollection<T> extends AbstractCollection<T>
{
private List<T> delegate = new ArrayList();
public boolean add(T e)
{
return delegate.add(e);
}
@Override
public Iterator<T> iterator()
{
return delegate.iterator();
}
@Override
public int size()
{
return delegate.size();
}
}
private final class CountingVisitCallback implements VisitCallback {
public final int expectedVisits;
public CountingVisitCallback(int expectedRowVisits) {
super();
this.expectedVisits = expectedRowVisits;
}
public int headerFacetVisits = 0;
public int footerFacetVisits = 0;
public int rowVisits = 0;
public VisitResult visit(VisitContext context, UIComponent target) {
if ("headerFacet".equals(target.getId())) {
headerFacetVisits++;
} else if ("footerFacet".equals(target.getId())) {
footerFacetVisits++;
} else if ("childOfColumn".equals(target.getId())) {
rowVisits++;
}
return VisitResult.ACCEPT;
}
public void verify() {
Assertions.assertEquals(1, headerFacetVisits);
Assertions.assertEquals(1, footerFacetVisits);
Assertions.assertEquals(expectedVisits, rowVisits);
}
}
@Test
public void testProcessDecodesRenderedFalse() throws Exception {
UIData uiData = new VerifyNoLifecycleMethodComponent();
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, uiData, false);
uiData.processDecodes(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processDecodes must not change currentComponent");
}
@Test
public void testProcessDecodesRenderedTrue() throws Exception {
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, _testImpl, true);
_addColumn();
_testImpl.processDecodes(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processDecodes must not change currentComponent");
}
@Test
public void testProcessValidatorsRenderedFalse() throws Exception {
UIData uiData = new VerifyNoLifecycleMethodComponent();
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, uiData, false);
uiData.processValidators(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processDecodes must not change currentComponent");
}
@Test
public void testProcessValidatorsRenderedTrue() throws Exception {
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, _testImpl, true);
_addColumn();
_testImpl.processValidators(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processValidators must not change currentComponent");
}
@Test
public void testProcessUpdatesRenderedFalse() throws Exception {
UIData uiData = new VerifyNoLifecycleMethodComponent();
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, uiData, false);
uiData.processUpdates(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processUpdates must not change currentComponent");
}
@Test
public void testProcessUpdatesRenderedTrue() throws Exception {
UIComponent parent = MockRenderedValueExpression.setUpComponentStack(facesContext, _testImpl, true);
_addColumn();
_testImpl.processUpdates(facesContext);
Assertions.assertEquals(parent, UIComponent.getCurrentComponent(facesContext), "processUpdates must not change currentComponent");
}
private void _addColumn() {
UIColumn uiColumn = new UIColumn();
uiColumn.setId("testId");
_testImpl.getChildren().add(uiColumn);
MockRenderedValueExpression ve = new MockRenderedValueExpression("#{component.is eq 'testId'}", Boolean.class, uiColumn, true);
uiColumn.setValueExpression("rendered", ve);
}
/** Verifies no call to encode* and process* methods */
public class VerifyNoLifecycleMethodComponent extends UIData
{
@Override
public void setRowIndex(int rowIndex) {
Assertions.fail();
}
@Override
public void decode(FacesContext context) {
Assertions.fail();
}
public void validate(FacesContext context) {
Assertions.fail();
}
public void updateModel(FacesContext context) {
Assertions.fail();
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
Assertions.fail();
}
@Override
public void encodeChildren(FacesContext context) throws IOException {
Assertions.fail();
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
Assertions.fail();
}
}
/**
* Test state save and restore cycle taking in consideration portlet case.
*
* In portlets, saveState() could be called on INVOKE_APPLICATION phase and
* restoreState() could be called in RENDER_RESPONSE phase.
*
* This test is active when PSS is disabled.
*
* @throws Exception
*/
@Test
public void testSaveAndRestorePortletLifecycleWithoutPss1() throws Exception
{
List<RowData> model = new ArrayList<RowData>();
model.add(new RowData("text1","style1"));
model.add(new RowData("text2","style2"));
model.add(new RowData("text3","style3"));
model.add(new RowData("text4","style4"));
//Put on request map to be resolved later
request.setAttribute("list", model);
UIViewRoot root = facesContext.getViewRoot();
createSimpleTable(root);
UIData table = (UIData) root.getChildren().get(0);
UIColumn column = (UIColumn) table.getChildren().get(0);
UIInput text = (UIInput) column.getChildren().get(0);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
//Check the value expressions are working and change the component state
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals(rowData.getText(), text.getValue());
text.setSubmittedValue("value"+(i+1));
//text.getAttributes().put("style", rowData.getStyle());
}
//Reset row index
table.setRowIndex(-1);
facesContext.setCurrentPhaseId(PhaseId.INVOKE_APPLICATION);
Object state = table.saveState(facesContext);
ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(state);
oos.flush();
baos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object restoredState = ois.readObject();
oos.close();
ois.close();
facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
facesContext.setViewRoot(new UIViewRoot());
root = facesContext.getViewRoot();
createSimpleTable(root);
table = (UIData) root.getChildren().get(0);
column = (UIColumn) table.getChildren().get(0);
text = (UIInput) column.getChildren().get(0);
table.restoreState(facesContext, restoredState);
//Check the values were not lost
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals("value"+(i+1), text.getSubmittedValue());
//assertEquals(model.get(i).getStyle(), text.getAttributes().get("style"));
}
}
// -------------------------------------------------------------------------
// Stateful (EVH) flat-list save / restore — UIData-specific
// -------------------------------------------------------------------------
/**
* Core correctness test for the flat-list stateful path in UIData.
*
* <p>UIData → UIColumn → UIInput. Sets a per-row submitted value, then revisits each row
* and verifies the saved state is correctly restored via
* {@code saveChildStates} / {@code restoreChildStates}.
*/
@Test
public void testStatefulRowStateIsPreservedOnRevisit()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("a", "b", "c"));
UIColumn column = new UIColumn();
column.setId("col");
UIInput input = new UIInput();
input.setId("inp");
column.getChildren().add(input);
table.getChildren().add(column);
root.getChildren().add(table);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
// First pass: visit every row and set a per-row submitted value.
for (int i = 0; i < 3; i++)
{
table.setRowIndex(i);
input.setSubmittedValue("submitted_" + i);
}
table.setRowIndex(-1);
// Second pass: revisit rows and verify the submitted values were preserved.
for (int i = 0; i < 3; i++)
{
table.setRowIndex(i);
Assertions.assertEquals("submitted_" + i, input.getSubmittedValue(),
"Row " + i + ": submitted value should be restored from Integer row-index-keyed _rowStates");
}
table.setRowIndex(-1);
}
/**
* After visiting rows with state, moving to index -1 must reset all EVH
* components to their initial (empty) state.
*/
@Test
public void testStatefulResetToInitialStateAtIndexMinusOne()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("a", "b"));
UIColumn column = new UIColumn();
column.setId("col");
UIInput input = new UIInput();
input.setId("inp");
column.getChildren().add(input);
table.getChildren().add(column);
root.getChildren().add(table);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
table.setRowIndex(0);
input.setSubmittedValue("dirty");
table.setRowIndex(1);
input.setSubmittedValue("also_dirty");
// Reset to -1: components must be in their clean template state.
table.setRowIndex(-1);
Assertions.assertNull(input.getSubmittedValue(),
"After reset to -1, submitted value must be cleared to initial state");
Assertions.assertTrue(input.isValid(),
"After reset to -1, valid flag must be restored to true");
}
/**
* Multiple UIColumn/UIInput siblings must each have independent per-row state.
*/
@Test
public void testStatefulMultipleEVHColumnsHaveIndependentState()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("x", "y"));
UIColumn colA = new UIColumn();
colA.setId("colA");
UIInput inputA = new UIInput();
inputA.setId("a");
colA.getChildren().add(inputA);
UIColumn colB = new UIColumn();
colB.setId("colB");
UIInput inputB = new UIInput();
inputB.setId("b");
colB.getChildren().add(inputB);
table.getChildren().add(colA);
table.getChildren().add(colB);
root.getChildren().add(table);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
table.setRowIndex(0);
inputA.setSubmittedValue("a0");
inputB.setSubmittedValue("b0");
table.setRowIndex(1);
inputA.setSubmittedValue("a1");
inputB.setSubmittedValue("b1");
table.setRowIndex(-1);
// Revisit row 0 — both inputs must have their row-0 values.
table.setRowIndex(0);
Assertions.assertEquals("a0", inputA.getSubmittedValue());
Assertions.assertEquals("b0", inputB.getSubmittedValue());
// Revisit row 1 — both inputs must have their row-1 values.
table.setRowIndex(1);
Assertions.assertEquals("a1", inputA.getSubmittedValue());
Assertions.assertEquals("b1", inputB.getSubmittedValue());
}
/**
* UIInput nested two levels deep (UIColumn → UIPanel → UIInput) must still be
* found in {@code _iterationEVHList} via depth-first traversal.
*/
@Test
public void testStatefulNestedEVHStateIsPreserved()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("x", "y"));
UIColumn column = new UIColumn();
column.setId("col");
UIOutput panel = new UIOutput();
panel.setId("panel");
UIInput input = new UIInput();
input.setId("inp");
panel.getChildren().add(input);
column.getChildren().add(panel);
table.getChildren().add(column);
root.getChildren().add(table);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
table.setRowIndex(0);
input.setSubmittedValue("nested_0");
table.setRowIndex(1);
input.setSubmittedValue("nested_1");
table.setRowIndex(-1);
table.setRowIndex(0);
Assertions.assertEquals("nested_0", input.getSubmittedValue(),
"Nested UIInput row-0 state must be restored");
table.setRowIndex(1);
Assertions.assertEquals("nested_1", input.getSubmittedValue(),
"Nested UIInput row-1 state must be restored");
}
/**
* A UIData nested inside another UIData keeps its per-row state per enclosing row: the inner table's row 0 in
* outer row 0 is a different cell from the inner table's row 0 in outer row 1. Keying the inner table's state by
* its own row index alone lets each enclosing row overwrite the previous one, which silently drops the submitted
* values of every enclosing row but the last.
*/
@Test
public void testNestedTableRowStateIsScopedPerOuterRow()
{
UIViewRoot root = facesContext.getViewRoot();
UIData outer = new UIData();
outer.setId("outer");
outer.setVar("group");
outer.setValue(java.util.Arrays.asList("g0", "g1"));
UIColumn outerColumn = new UIColumn();
outerColumn.setId("outerCol");
UIData inner = new UIData();
inner.setId("inner");
inner.setVar("row");
inner.setValue(java.util.Arrays.asList("r0", "r1"));
UIColumn innerColumn = new UIColumn();
innerColumn.setId("innerCol");
UIInput input = new UIInput();
input.setId("inp");
innerColumn.getChildren().add(input);
inner.getChildren().add(innerColumn);
outerColumn.getChildren().add(inner);
outer.getChildren().add(outerColumn);
root.getChildren().add(outer);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
// Submit a distinct value into every cell of the 2x2 grid.
for (int outerRow = 0; outerRow < 2; outerRow++)
{
outer.setRowIndex(outerRow);
for (int innerRow = 0; innerRow < 2; innerRow++)
{
inner.setRowIndex(innerRow);
input.setSubmittedValue("v" + outerRow + innerRow);
}
inner.setRowIndex(-1);
}
outer.setRowIndex(-1);
// Every cell must still hold its own value.
for (int outerRow = 0; outerRow < 2; outerRow++)
{
outer.setRowIndex(outerRow);
for (int innerRow = 0; innerRow < 2; innerRow++)
{
inner.setRowIndex(innerRow);
Assertions.assertEquals("v" + outerRow + innerRow, input.getSubmittedValue(),
"outer row " + outerRow + ", inner row " + innerRow
+ " must keep the value submitted into it");
}
inner.setRowIndex(-1);
}
outer.setRowIndex(-1);
}
// -------------------------------------------------------------------------
// Read-only (no EVH) flat-list clientId reset
// -------------------------------------------------------------------------
/**
* For a read-only table (UIColumn → UIOutput only), {@code setRowIndex} must
* reset clientIds correctly so each row produces unique clientIds.
*/
@Test
public void testReadOnlyClientIdResetPerRow()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("x", "y", "z"));
UIColumn column = new UIColumn();
column.setId("col");
UIOutput out = new UIOutput();
out.setId("out");
column.getChildren().add(out);
table.getChildren().add(column);
root.getChildren().add(table);
table.setRowIndex(0);
String clientId0 = out.getClientId(facesContext);
table.setRowIndex(1);
String clientId1 = out.getClientId(facesContext);
table.setRowIndex(2);
String clientId2 = out.getClientId(facesContext);
Assertions.assertNotEquals(clientId0, clientId1, "ClientIds must differ between rows");
Assertions.assertNotEquals(clientId1, clientId2, "ClientIds must differ between rows");
// After resetting, the IDs must match the first pass.
table.setRowIndex(-1);
table.setRowIndex(0);
Assertions.assertEquals(clientId0, out.getClientId(facesContext),
"ClientId for row 0 must be stable across iteration sequences");
}
/**
* Transient components must also have their clientId reset even though their
* state is not saved ({@code _iterationResetList} includes them).
*/
@Test
public void testReadOnlyTransientComponentClientIdIsReset()
{
UIViewRoot root = facesContext.getViewRoot();
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("x", "y"));
UIColumn column = new UIColumn();
column.setId("col");
UIOutput transientOut = new UIOutput();
transientOut.setId("tout");
transientOut.setTransient(true);
column.getChildren().add(transientOut);
table.getChildren().add(column);
root.getChildren().add(table);
table.setRowIndex(0);
String id0 = transientOut.getClientId(facesContext);
table.setRowIndex(1);
String id1 = transientOut.getClientId(facesContext);
Assertions.assertNotEquals(id0, id1,
"Transient component clientId must be reset per row");
}
// -------------------------------------------------------------------------
// Iteration list lifecycle
// -------------------------------------------------------------------------
/**
* After {@code encodeBegin} the iteration lists are cleared. A subsequent
* iteration sequence must still work correctly (no NPE, correct clientIds).
*/
@Test
public void testIterationListsAreRebuiltAfterEncodeBegin() throws Exception
{
UIViewRoot root = facesContext.getViewRoot();
root.setRenderKitId("HTML_BASIC");
UIData table = new UIData();
table.setId("table");
table.setValue(java.util.Arrays.asList("a", "b"));
UIColumn column = new UIColumn();
column.setId("col");
UIInput input = new UIInput();
input.setId("inp");
column.getChildren().add(input);
table.getChildren().add(column);
root.getChildren().add(table);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
// First iteration sequence (simulates APPLY_REQUEST_VALUES)
table.setRowIndex(0);
input.setSubmittedValue("v0");
table.setRowIndex(1);
input.setSubmittedValue("v1");
table.setRowIndex(-1);
// encodeBegin clears the iteration lists and _rowStates
facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
table.encodeBegin(facesContext);
// Second iteration sequence (simulates RENDER_RESPONSE row walk)
// must rebuild lists without NPE and show clean initial state
table.setRowIndex(0);
Assertions.assertNull(input.getSubmittedValue(),
"After encodeBegin, _rowStates is cleared and initial state is clean");
table.setRowIndex(1);
Assertions.assertNull(input.getSubmittedValue());
table.setRowIndex(-1);
}
private void createSimpleTable(UIViewRoot root)
{
createSimpleTable(root, false);
}
private void createSimpleTable(UIViewRoot root, boolean rowStatePreserved)
{
UIData table = new UIData();
UIColumn column = new UIColumn();
UIInput text = new UIInput();
//This is only required if markInitiaState fix is not used
root.setId(root.createUniqueId());
table.setId(root.createUniqueId());
column.setId(root.createUniqueId());
text.setId(root.createUniqueId());
table.setVar("row");
if (rowStatePreserved)
{
table.setRowStatePreserved(true);
}
table.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{list}",List.class));
text.setValueExpression("value", application.
getExpressionFactory().createValueExpression(
facesContext.getELContext(),"#{row.text}",String.class));
root.getChildren().add(table);
table.getChildren().add(column);
column.getChildren().add(text);
}
/**
* Test state save and restore cycle taking in consideration portlet case.
*
* In portlets, saveState() could be called on INVOKE_APPLICATION phase and
* restoreState() could be called in RENDER_RESPONSE phase.
*
* This test is active when PSS is enabled.
*
* @throws Exception
*/
@Test
public void testSaveAndRestorePortletLifecycleWithPss1() throws Exception
{
facesContext.getRenderKit().addRenderer("jakarta.faces.Data", "jakarta.faces.Table",new Renderer(){});
List<RowData> model = new ArrayList<RowData>();
model.add(new RowData("text1","style1"));
model.add(new RowData("text2","style2"));
model.add(new RowData("text3","style3"));
model.add(new RowData("text4","style4"));
//Put on request map to be resolved later
request.setAttribute("list", model);
UIViewRoot root = facesContext.getViewRoot();
createSimpleTable(root);
UIData table = (UIData) root.getChildren().get(0);
UIColumn column = (UIColumn) table.getChildren().get(0);
UIInput text = (UIInput) column.getChildren().get(0);
//Simulate markInitialState call.
facesContext.getAttributes().put(StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
root.markInitialState();
table.markInitialState();
column.markInitialState();
text.markInitialState();
facesContext.getAttributes().remove(StateManager.IS_BUILDING_INITIAL_STATE);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
//Check the value expressions are working and change the component state
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals(rowData.getText(), text.getValue());
text.setSubmittedValue("value"+(i+1));
//text.getAttributes().put("style", rowData.getStyle());
}
//Reset row index
table.setRowIndex(-1);
facesContext.setCurrentPhaseId(PhaseId.INVOKE_APPLICATION);
Object state = table.saveState(facesContext);
ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(state);
oos.flush();
baos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object restoredState = ois.readObject();
oos.close();
ois.close();
facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
facesContext.setViewRoot(new UIViewRoot());
root = facesContext.getViewRoot();
root.setRenderKitId("HTML_BASIC");
createSimpleTable(root);
table = (UIData) root.getChildren().get(0);
column = (UIColumn) table.getChildren().get(0);
text = (UIInput) column.getChildren().get(0);
//Simulate markInitialState call.
facesContext.getAttributes().put(StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
root.markInitialState();
table.markInitialState();
column.markInitialState();
text.markInitialState();
facesContext.getAttributes().remove(StateManager.IS_BUILDING_INITIAL_STATE);
table.restoreState(facesContext, restoredState);
//Check the values were not lost
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals("value"+(i+1), text.getSubmittedValue());
//assertEquals(model.get(i).getStyle(), text.getAttributes().get("style"));
}
}
/**
* Test state save and restore cycle taking in consideration portlet case.
*
* In portlets, saveState() could be called on INVOKE_APPLICATION phase and
* restoreState() could be called in RENDER_RESPONSE phase.
*
* This test is active when PSS is enabled.
*
* @throws Exception
*/
@Test
public void testSaveAndRestorePortletLifecycleWithPss2() throws Exception
{
facesContext.getRenderKit().addRenderer("jakarta.faces.Data", "jakarta.faces.Table",new Renderer(){});
List<RowData> model = new ArrayList<RowData>();
model.add(new RowData("text1","style1"));
model.add(new RowData("text2","style2"));
model.add(new RowData("text3","style3"));
model.add(new RowData("text4","style4"));
//Put on request map to be resolved later
request.setAttribute("list", model);
UIViewRoot root = facesContext.getViewRoot();
createSimpleTable(root, true);
UIData table = (UIData) root.getChildren().get(0);
UIColumn column = (UIColumn) table.getChildren().get(0);
UIInput text = (UIInput) column.getChildren().get(0);
//Simulate markInitialState call.
facesContext.getAttributes().put(StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
root.markInitialState();
table.markInitialState();
column.markInitialState();
text.markInitialState();
facesContext.getAttributes().remove(StateManager.IS_BUILDING_INITIAL_STATE);
facesContext.setCurrentPhaseId(PhaseId.APPLY_REQUEST_VALUES);
//Check the value expressions are working and change the component state
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals(rowData.getText(), text.getValue());
text.setSubmittedValue("value"+(i+1));
text.getTransientStateHelper().putTransient("key", "value"+(i+1));
//text.getAttributes().put("style", rowData.getStyle());
}
//Reset row index
table.setRowIndex(-1);
facesContext.setCurrentPhaseId(PhaseId.INVOKE_APPLICATION);
Object state = table.saveState(facesContext);
ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(state);
oos.flush();
baos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object restoredState = ois.readObject();
oos.close();
ois.close();
facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
facesContext.setViewRoot(new UIViewRoot());
root = facesContext.getViewRoot();
root.setRenderKitId("HTML_BASIC");
createSimpleTable(root, true);
table = (UIData) root.getChildren().get(0);
column = (UIColumn) table.getChildren().get(0);
text = (UIInput) column.getChildren().get(0);
//Simulate markInitialState call.
facesContext.getAttributes().put(StateManager.IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
root.markInitialState();
table.markInitialState();
column.markInitialState();
text.markInitialState();
facesContext.getAttributes().remove(StateManager.IS_BUILDING_INITIAL_STATE);
table.restoreState(facesContext, restoredState);
//Check the values were not lost
for (int i = 0; i < model.size(); i++)
{
RowData rowData = model.get(i);
table.setRowIndex(i);
Assertions.assertEquals("value"+(i+1), text.getSubmittedValue());
Assertions.assertEquals("value"+(i+1), text.getTransientStateHelper().getTransient("key"));
//assertEquals(model.get(i).getStyle(), text.getAttributes().get("style"));
}
}
}