Commit f4947e29 authored by Michael Hamann's avatar Michael Hamann
Browse files

XRENDERING-631: The annotated HTML5 renderer should not render figcaption inside macros

* Refactor BlockStateChainingListenerTest to use parameterized tests.
parent c1a4522e
...@@ -27,8 +27,10 @@ ...@@ -27,8 +27,10 @@
import org.apache.commons.text.CaseUtils; import org.apache.commons.text.CaseUtils;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.stubbing.Stubber; import org.mockito.stubbing.Stubber;
import org.xwiki.rendering.listener.Format; import org.xwiki.rendering.listener.Format;
import org.xwiki.rendering.listener.HeaderLevel; import org.xwiki.rendering.listener.HeaderLevel;
...@@ -40,6 +42,7 @@ ...@@ -40,6 +42,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -50,7 +53,7 @@ ...@@ -50,7 +53,7 @@
* @version $Id$ * @version $Id$
* @since 14.0RC1 * @since 14.0RC1
*/ */
public class BlockStateChainingListenerTest class BlockStateChainingListenerTest
{ {
private BlockStateChainingListener listener; private BlockStateChainingListener listener;
...@@ -67,121 +70,129 @@ void setUpChain() ...@@ -67,121 +70,129 @@ void setUpChain()
} }
/** /**
* Tests for all "begin/end"-methods if they do not modify the parent event (for the next in the chain) in the * @return A stream of {@link Arguments} consisting each all begin-methods of the {@link Listener} interface,
* begin-method, correctly set it afterwards and if the previous event is correctly set, but only after the end * each with the matching end method and suitable parameters.
* event has been forwarded in the chain.
*/ */
@TestFactory static Stream<Arguments> beginEndMethodProvider()
Stream<DynamicTest> beginEndMethods()
{ {
return Arrays.stream(Listener.class.getMethods()) return Arrays.stream(Listener.class.getMethods()).filter(m -> m.getName().startsWith("begin")).map(m -> {
.filter(m -> m.getName().startsWith("begin")) String endMethodName = m.getName().replace("begin", "end");
.map(beginMethod -> Method endMethod = null;
DynamicTest.dynamicTest(getTestName(beginMethod), try {
() -> testBeginEndMethod(beginMethod))); endMethod = Listener.class.getMethod(endMethodName, m.getParameterTypes());
} catch (NoSuchMethodException e) {
fail("Expected end method " + endMethodName + " for " + m.getName() + " not found: " + e.getMessage());
}
return arguments(
Named.of(getTestName(m), m),
Named.of(getTestName(endMethod), endMethod),
getMockParameters(m)
);
});
} }
/** /**
* Tests for all "on..." methods if they do not modify the parent event (for the next in the chain) and if the * Test all begin/end-methods.
* previous event is correctly set, but only after the event has been forwarded in the chain. *
* Tests for all "begin/end"-methods if they do not modify the parent event (for the next in the chain) in the
* begin-method, correctly set it afterwards and if the previous event is correctly set, but only after the end
* event has been forwarded in the chain.
*
* @param beginMethod The method to begin the container.
* @param endMethod The corresponding end method.
* @param parameters Suitable parameters for both methods.
*/ */
@TestFactory @ParameterizedTest(name = "{0} and {1} with {2}")
Stream<DynamicTest> onMethods() @MethodSource("beginEndMethodProvider")
void testBeginEndMethod(Method beginMethod, Method endMethod, Object[] parameters)
throws InvocationTargetException, IllegalAccessException
{ {
return Arrays.stream(Listener.class.getMethods()) boolean isListItem = beginMethod.getName().equals("beginListItem");
.filter(m -> m.getName().startsWith("on")) boolean isDefinitionItem = beginMethod.getName().equals("beginDefinitionTerm") || beginMethod.getName()
.map(beginMethod -> .equals("beginDefinitionDescription");
DynamicTest.dynamicTest(getTestName(beginMethod),
() -> testOnMethod(beginMethod))); BlockStateChainingListener.Event expectedParentEvent;
}
if (isListItem) {
private String getTestName(Method method) this.listener.beginList(ListType.NUMBERED, Listener.EMPTY_PARAMETERS);
{ expectedParentEvent = BlockStateChainingListener.Event.LIST;
return method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(Class::getName) } else if (isDefinitionItem) {
.collect(Collectors.joining(", ")) + ")"; this.listener.beginDefinitionList(Listener.EMPTY_PARAMETERS);
} expectedParentEvent = BlockStateChainingListener.Event.DEFINITION_LIST;
} else {
private void testBeginEndMethod(Method beginMethod) expectedParentEvent = null;
{ }
String endMethodName = beginMethod.getName().replace("begin", "end");
Class<?>[] parameterClasses = beginMethod.getParameterTypes();
try {
Method endMethod = Listener.class.getMethod(endMethodName, parameterClasses);
boolean isListItem = beginMethod.getName().equals("beginListItem");
boolean isDefinitionItem =
beginMethod.getName().equals("beginDefinitionTerm") || beginMethod.getName().equals(
"beginDefinitionDescription");
BlockStateChainingListener.Event expectedParentEvent;
if (isListItem) {
this.listener.beginList(ListType.NUMBERED, Listener.EMPTY_PARAMETERS);
expectedParentEvent = BlockStateChainingListener.Event.LIST;
} else if (isDefinitionItem) {
this.listener.beginDefinitionList(Listener.EMPTY_PARAMETERS);
expectedParentEvent = BlockStateChainingListener.Event.DEFINITION_LIST;
} else {
expectedParentEvent = null;
}
Object[] parameters = Arrays.stream(parameterClasses).map(this::mockParameter).toArray(); this.listener.onId("MockID");
this.listener.onId("MockID"); Stubber verifyPreviousAndParentEventStubber = doAnswer(invocation -> {
assertEquals(BlockStateChainingListener.Event.ID, this.listener.getPreviousEvent());
assertEquals(expectedParentEvent, this.listener.getParentEvent());
return null;
}).doNothing();
Stubber verifyPreviousAndParentEventStubber = doAnswer(invocation -> { // Assert that in the begin method, the parent and the previous event are unchanged.
assertEquals(BlockStateChainingListener.Event.ID, this.listener.getPreviousEvent()); beginMethod.invoke(verifyPreviousAndParentEventStubber.when(this.mockListener), parameters);
assertEquals(expectedParentEvent, this.listener.getParentEvent());
return null;
}).doNothing();
// Assert that in the begin method, the parent and the previous event are unchanged. // Actually call the begin method.
beginMethod.invoke(verifyPreviousAndParentEventStubber.when(this.mockListener), parameters); beginMethod.invoke(this.listener, parameters);
// Actually call the begin method. // Verify the mock listener in the chain has been called.
beginMethod.invoke(this.listener, parameters); beginMethod.invoke(verify(this.mockListener), parameters);
// Verify the mock listener in the chain has been called. BlockStateChainingListener.Event parentEvent = this.listener.getParentEvent();
beginMethod.invoke(verify(this.mockListener), parameters); assertNotNull(parentEvent, "No parent set after calling " + beginMethod.getName());
String parentEventName = parentEvent.name();
String eventNameCamelCase = CaseUtils.toCamelCase(parentEventName, true, '_');
assertEquals(beginMethod.getName(), "begin" + eventNameCamelCase,
"Wrong event " + parentEventName + " generated for " + beginMethod.getName());
BlockStateChainingListener.Event parentEvent = this.listener.getParentEvent(); // Assert that in the end method, the parent has been restored and the previous event is unchanged.
assertNotNull(parentEvent, "No parent set after calling " + beginMethod.getName()); endMethod.invoke(verifyPreviousAndParentEventStubber.when(this.mockListener), parameters);
String parentEventName = parentEvent.name();
String eventNameCamelCase = CaseUtils.toCamelCase(parentEventName, true, '_');
assertEquals(beginMethod.getName(), "begin" + eventNameCamelCase,
"Wrong event " + parentEventName + " generated for " + beginMethod.getName());
// Assert that in the end method, the parent has been restored and the previous event is unchanged. // Actually call the end method.
endMethod.invoke(verifyPreviousAndParentEventStubber.when(this.mockListener), parameters); endMethod.invoke(this.listener, parameters);
// Actually call the end method. // Verify the mock listener in the chain has been called.
endMethod.invoke(this.listener, parameters); endMethod.invoke(verify(this.mockListener), parameters);
// Verify the mock listener in the chain has been called. // Verify that the previous event has been set to the event corresponding to the current methods.
endMethod.invoke(verify(this.mockListener), parameters); assertEquals(parentEvent, this.listener.getPreviousEvent());
// Verify that the previous event has been set to the event corresponding to the current methods. if (isDefinitionItem) {
assertEquals(parentEvent, this.listener.getPreviousEvent()); this.listener.endDefinitionList(Listener.EMPTY_PARAMETERS);
} else if (isListItem) {
this.listener.endList(ListType.NUMBERED, Listener.EMPTY_PARAMETERS);
}
if (isDefinitionItem) { assertNull(this.listener.getParentEvent());
this.listener.endDefinitionList(Listener.EMPTY_PARAMETERS); }
} else if (isListItem) {
this.listener.endList(ListType.NUMBERED, Listener.EMPTY_PARAMETERS);
}
assertNull(this.listener.getParentEvent()); /**
} catch (NoSuchMethodException e) { * @return A stream of {@link Arguments} consisting each of all "on"-method of the {@link Listener} interface and
fail("Expected end method " + endMethodName + " for " + beginMethod.getName() + " not found: " * suitable parameters.
+ e.getMessage()); */
} catch (InvocationTargetException e) { static Stream<Arguments> onMethodsProvider()
fail("Listener method has thrown exception: " + e.getMessage()); {
} catch (IllegalAccessException e) { return Arrays.stream(Listener.class.getMethods())
fail("Listener method not callable: " + e.getMessage()); .filter(m -> m.getName().startsWith("on")).map(m -> arguments(
} Named.of(getTestName(m), m),
getMockParameters(m)
));
} }
private void testOnMethod(Method method) /**
* Test the "on"-methods of the {@link Listener} interface.
*
* Tests for all "on..." methods if they do not modify the parent event (for the next in the chain) and if the
* previous event is correctly set, but only after the event has been forwarded in the chain.
*
* @param method The "on"-method.
* @param parameters Suitable parameters for the method.
*/
@ParameterizedTest(name = "{0} with {1}")
@MethodSource("onMethodsProvider")
void testOnMethod(Method method, Object[] parameters) throws InvocationTargetException, IllegalAccessException
{ {
this.listener.beginDocument(MetaData.EMPTY); this.listener.beginDocument(MetaData.EMPTY);
...@@ -189,31 +200,23 @@ private void testOnMethod(Method method) ...@@ -189,31 +200,23 @@ private void testOnMethod(Method method)
this.listener.beginParagraph(Listener.EMPTY_PARAMETERS); this.listener.beginParagraph(Listener.EMPTY_PARAMETERS);
this.listener.endParagraph(Listener.EMPTY_PARAMETERS); this.listener.endParagraph(Listener.EMPTY_PARAMETERS);
Object[] parameters = Arrays.stream(method.getParameterTypes()).map(this::mockParameter).toArray(); // Verify that the next in the chain still gets the old previous event and that the parent is not
// changed.
try { method.invoke(
// Verify that the next in the chain still gets the old previous event and that the parent is not doAnswer(invocationOnMock -> {
// changed. assertEquals(BlockStateChainingListener.Event.DOCUMENT, this.listener.getParentEvent());
method.invoke( assertEquals(BlockStateChainingListener.Event.PARAGRAPH, this.listener.getPreviousEvent());
doAnswer(invocationOnMock -> { return null;
assertEquals(BlockStateChainingListener.Event.DOCUMENT, this.listener.getParentEvent()); })
assertEquals(BlockStateChainingListener.Event.PARAGRAPH, this.listener.getPreviousEvent()); .doThrow(new AssertionError("Listener must only be called once"))
return null; .when(this.mockListener),
}) parameters);
.doThrow(new AssertionError("Listener must only be called once"))
.when(this.mockListener), // Actually call the listener method.
parameters); method.invoke(this.listener, parameters);
// Actually call the listener method. // Verify that the call has been correctly forwarded.
method.invoke(this.listener, parameters); method.invoke(verify(this.mockListener), parameters);
// Verify that the call has been correctly forwarded.
method.invoke(verify(this.mockListener), parameters);
} catch (InvocationTargetException e) {
fail("Listener method has thrown exception: " + e.getMessage());
} catch (IllegalAccessException e) {
fail("Listener method not callable: " + e.getMessage());
}
// Check if the previous event is the expected event. // Check if the previous event is the expected event.
String previousEventName = this.listener.getPreviousEvent().name(); String previousEventName = this.listener.getPreviousEvent().name();
...@@ -230,11 +233,30 @@ private void testOnMethod(Method method) ...@@ -230,11 +233,30 @@ private void testOnMethod(Method method)
this.listener.endDocument(MetaData.EMPTY); this.listener.endDocument(MetaData.EMPTY);
} }
/**
* @param method The method to get a name for.
* @return The name of the method without class but with all parameter types.
*/
static private String getTestName(Method method)
{
return method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(Class::getName)
.collect(Collectors.joining(", ")) + ")";
}
/**
* @param method The method to get parameters for.
* @return A mock object or value for each expected parameter.
*/
static private Object getMockParameters(Method method)
{
return Arrays.stream(method.getParameterTypes()).map(BlockStateChainingListenerTest::mockParameter).toArray();
}
/** /**
* @param classToMock The class to return a mock object for. * @param classToMock The class to return a mock object for.
* @return Either a mock object or in the case of an enum or primitive type a concrete value. * @return Either a mock object or in the case of an enum or primitive type a concrete value.
*/ */
private Object mockParameter(Class<?> classToMock) static private Object mockParameter(Class<?> classToMock)
{ {
if (classToMock.equals(Format.class)) { if (classToMock.equals(Format.class)) {
return Format.BOLD; return Format.BOLD;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment