001// Generated by delombok at Sun Jul 30 17:21:56 UTC 2023
002package de.cuioss.test.jsf.renderer;
003
004import static de.cuioss.tools.string.MoreStrings.emptyToNull;
005import static java.util.Objects.requireNonNull;
006import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
007import static org.junit.jupiter.api.Assertions.assertNotNull;
008import static org.junit.jupiter.api.Assertions.assertNull;
009import static org.junit.jupiter.api.Assertions.assertThrows;
010import java.io.IOException;
011import java.io.StringWriter;
012import javax.faces.component.UIComponent;
013import javax.faces.context.FacesContext;
014import javax.faces.render.FacesRenderer;
015import javax.faces.render.Renderer;
016import org.apache.myfaces.test.mock.MockResponseWriter;
017import org.jdom2.Document;
018import org.junit.jupiter.api.BeforeEach;
019import org.junit.jupiter.api.Test;
020import de.cuioss.test.jsf.junit5.EnableJsfEnvironment;
021import de.cuioss.test.jsf.junit5.JsfEnabledTestEnvironment;
022import de.cuioss.test.jsf.renderer.util.DomUtils;
023import de.cuioss.test.jsf.renderer.util.HtmlTreeAsserts;
024import de.cuioss.test.valueobjects.objects.ConfigurationCallBackHandler;
025import de.cuioss.test.valueobjects.objects.impl.DefaultInstantiator;
026import de.cuioss.tools.reflect.MoreReflection;
027import de.cuioss.tools.string.MoreStrings;
028
029/**
030 * Base class for testing implementations of {@link Renderer}. It focuses on
031 * conveniences and the basic-api contracts.
032 * <h3>Configuration</h3>
033 * <p>
034 * Documentation on the setup of the JSF-related test-infrastructure can be
035 * found at {@link EnableJsfEnvironment}
036 * </p>
037 * <p>
038 * It acts as an {@link ConfigurationCallBackHandler}, saying after
039 * initialization and prior to testing the method {@link #configure(Object)}
040 * will be called allowing the concrete test-class to do some specific
041 * configuration e.g. calling init-methods and such.
042 * </p>
043 * <p>
044 * You can easily access pre-configured instance by calling
045 * {@link #getRenderer()}.
046 * </p>
047 * <h3>API-Tests</h3> Base {@linkplain Renderer} Test. Verify API contract of
048 * Renderer for
049 * <ul>
050 * <li>{@linkplain Renderer#decode(FacesContext, UIComponent)}</li>
051 * <li>{@linkplain Renderer#encodeBegin(FacesContext, UIComponent)}</li>
052 * <li>{@linkplain Renderer#encodeChildren(FacesContext, UIComponent)}</li>
053 * <li>{@linkplain Renderer#encodeEnd(FacesContext, UIComponent)}</li>
054 * <li>{@linkplain Renderer#convertClientId(FacesContext, String)}</li>
055 * </ul>
056 * <h3>Contracts</h3>
057 * <ul>
058 * <li>{@link #assertRenderResult(UIComponent, Document)} and
059 * {@link #assertRenderResult(UIComponent, String)} are the main 'business'
060 * methods for explicit testing</li>
061 * </ul>
062 *
063 * @author Oliver Wolff
064 * @param <R> The renderer being tested
065 */
066public abstract class AbstractRendererTestBase<R extends Renderer> extends JsfEnabledTestEnvironment implements ConfigurationCallBackHandler<R> {
067    private static final String NPE_ON_MISSING_CLIENT_ID_EXPECTED = "NullPointerException expected on missing ClientId parameter. Use inheritance or implement own check.";
068    private static final String NPE_ON_MISSING_PARAMETER_EXPECTED = "NullPointerException expected on missing UIComponent parameter. Use inheritance or implement own check.";
069    private static final String NPE_ON_MISSING_FACESCONTEXT_EXPECTED = "NullPointerException expected on missing FacesContext. Use inheritance or implement own check.";
070    private R renderer;
071
072    /**
073     * Instantiates and initially configures a concrete {@link Renderer}
074     */
075    @BeforeEach
076    void initRenderer() {
077        final Class<R> klazz = MoreReflection.extractFirstGenericTypeArgument(getClass());
078        renderer = new DefaultInstantiator<>(klazz).newInstance();
079        configure(renderer);
080        if (klazz.isAnnotationPresent(FacesRenderer.class)) {
081            getComponentConfigDecorator().registerRenderer(klazz);
082        }
083    }
084
085    /**
086     * @return the configured {@link UIComponent}. <em>Caution: </em> you must
087     *         always create a new instance of the component on each call
088     */
089    protected abstract UIComponent getComponent();
090
091    /**
092     * Renders the given component / renderer into a String representation
093     *
094     * @param toBeRendered the component to be passed to the renderer, must not be
095     *                     null
096     * @return the String-result of the rendering
097     * @throws IOException
098     */
099    public String renderToString(final UIComponent toBeRendered) throws IOException {
100        requireNonNull(toBeRendered);
101        var output = new StringWriter();
102        getFacesContext().setResponseWriter(new MockResponseWriter(output));
103        final Renderer testRenderer = getRenderer();
104        testRenderer.encodeBegin(getFacesContext(), toBeRendered);
105        testRenderer.encodeChildren(getFacesContext(), toBeRendered);
106        testRenderer.encodeEnd(getFacesContext(), toBeRendered);
107        return output.toString();
108    }
109
110    /**
111     * Calls the renderer and checks the result against the given expected
112     * {@link Document}
113     *
114     * @param toBeRendered the component to be passed to the renderer, must not be
115     *                     null
116     * @param expected     must not be null
117     */
118    public void assertRenderResult(final UIComponent toBeRendered, final Document expected) {
119        var rendered = assertDoesNotThrow(() -> renderToString(toBeRendered));
120        assertNotNull(emptyToNull(rendered), "Render result must not be empty.");
121        HtmlTreeAsserts.assertHtmlTreeEquals(expected, DomUtils.htmlStringToDocument(rendered));
122    }
123
124    /**
125     * Shorthand for {@link #assertRenderResult(UIComponent, Document)} and
126     * {@link DomUtils#htmlStringToDocument(String)}
127     *
128     * @param toBeRendered the component to be passed to the renderer, must not be
129     *                     null
130     * @param expected     must not be null
131     */
132    public void assertRenderResult(final UIComponent toBeRendered, final String expected) {
133        assertNotNull(emptyToNull(expected), "Render result must not be empty.");
134        assertRenderResult(toBeRendered, DomUtils.htmlStringToDocument(expected));
135    }
136
137    /**
138     * Assert, that the given component does not render any output.
139     *
140     * @param toBeRendered the component to be passed to the renderer, must not be
141     *                     null
142     */
143    public void assertEmptyRenderResult(final UIComponent toBeRendered) {
144        var rendered = assertDoesNotThrow(() -> renderToString(toBeRendered));
145        assertNull(MoreStrings.emptyToNull(rendered), "Render result must be empty, but is:\n" + rendered);
146    }
147
148    // API tests
149    @Test
150    void shouldThrowNPEOnMissingParameterForDecode() {
151        assertThrows(NullPointerException.class, () -> renderer.decode(null, getComponent()), NPE_ON_MISSING_FACESCONTEXT_EXPECTED);
152        assertThrows(NullPointerException.class, () -> renderer.decode(getFacesContext(), null), NPE_ON_MISSING_PARAMETER_EXPECTED);
153    }
154
155    @Test
156    void shouldThrowNPEOnMissingParameterForEncodeBegin() {
157        assertThrows(NullPointerException.class, () -> renderer.encodeBegin(null, getComponent()), NPE_ON_MISSING_FACESCONTEXT_EXPECTED);
158        assertThrows(NullPointerException.class, () -> renderer.encodeBegin(getFacesContext(), null));
159    }
160
161    @Test
162    void shouldThrowNPEOnMissingParameterForEncodeChildren() {
163        assertThrows(NullPointerException.class, () -> renderer.encodeChildren(null, getComponent()), NPE_ON_MISSING_FACESCONTEXT_EXPECTED);
164        assertThrows(NullPointerException.class, () -> renderer.encodeChildren(getFacesContext(), null), NPE_ON_MISSING_PARAMETER_EXPECTED);
165    }
166
167    @Test
168    void shouldThrowNPEOnMissingParameterForConvertClientId() {
169        assertThrows(NullPointerException.class, () -> renderer.convertClientId(null, "SomeId"), NPE_ON_MISSING_FACESCONTEXT_EXPECTED);
170        assertThrows(NullPointerException.class, () -> renderer.convertClientId(getFacesContext(), null), NPE_ON_MISSING_CLIENT_ID_EXPECTED);
171    }
172
173    @Test
174    void shouldThrowNPEOnMissingParameterForEncodeEnd() {
175        assertThrows(NullPointerException.class, () -> renderer.encodeEnd(null, getComponent()), NPE_ON_MISSING_FACESCONTEXT_EXPECTED);
176        assertThrows(NullPointerException.class, () -> renderer.encodeEnd(getFacesContext(), null), NPE_ON_MISSING_PARAMETER_EXPECTED);
177    }
178
179    @java.lang.SuppressWarnings("all")
180    @lombok.Generated
181    public R getRenderer() {
182        return this.renderer;
183    }
184}