/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.doc; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; import java.util.Properties; import org.apache.velocity.VelocityContext; import org.jmock.Mock; import org.jmock.core.Invocation; import org.jmock.core.stub.CustomStub; import org.junit.Test; import org.xwiki.display.internal.DisplayConfiguration; import org.xwiki.model.reference.DocumentReference; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.test.internal.MockConfigurationSource; import org.xwiki.velocity.VelocityEngine; import org.xwiki.velocity.VelocityFactory; import org.xwiki.velocity.VelocityManager; import org.xwiki.velocity.internal.jmx.JMXVelocityEngine; import org.xwiki.velocity.internal.jmx.JMXVelocityEngineMBean; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.api.Document; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.objects.classes.TextAreaClass; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.store.XWikiVersioningStoreInterface; import com.xpn.xwiki.test.AbstractBridgedXWikiComponentTestCase; import com.xpn.xwiki.user.api.XWikiRightService; /** * Unit tests for {@link XWikiDocument}. * * @version $Id: 9c94c04f8218d6f1935dd1cf9b5516760b0a4e31 $ */ public class XWikiDocumentRenderingTest extends AbstractBridgedXWikiComponentTestCase { private static final String DOCWIKI = "xwiki"; private static final String DOCSPACE = "Space"; private static final String DOCNAME = "Page"; private static final String DOCFULLNAME = DOCSPACE + "." + DOCNAME; private static final String CLASSNAME = DOCFULLNAME; private XWikiDocument document; private XWikiDocument translatedDocument; private Mock mockXWiki; private Mock mockXWikiVersioningStore; private Mock mockXWikiStoreInterface; private Mock mockXWikiRightService; private Mock mockDisplayConfiguration; private BaseClass baseClass; private BaseObject baseObject; @Override protected void setUp() throws Exception { super.setUp(); // Mock xwiki.cfg getComponentManager().registerComponent(MockConfigurationSource.getDescriptor("xwikicfg"), getConfigurationSource()); this.document = new XWikiDocument(new DocumentReference(DOCWIKI, DOCSPACE, DOCNAME)); this.document.setSyntax(Syntax.XWIKI_2_1); this.document.setLanguage("en"); this.document.setDefaultLanguage("en"); this.document.setNew(false); getContext().setDoc(this.document); this.translatedDocument = new XWikiDocument(); this.translatedDocument.setSyntax(Syntax.XWIKI_2_1); this.translatedDocument.setLanguage("fr"); this.translatedDocument.setNew(false); getContext().put("isInRenderingEngine", true); this.mockXWiki = mock(XWiki.class); this.mockXWiki.stubs().method("Param").will(returnValue(null)); this.mockXWikiVersioningStore = mock(XWikiVersioningStoreInterface.class); this.mockXWikiVersioningStore.stubs().method("getXWikiDocumentArchive").will(returnValue(null)); this.mockXWikiRightService = mock(XWikiRightService.class); this.mockXWikiRightService.stubs().method("hasProgrammingRights").will(returnValue(true)); this.mockXWikiStoreInterface = mock(XWikiStoreInterface.class); this.mockXWikiStoreInterface.stubs().method("search").will(returnValue(new ArrayList<XWikiDocument>())); this.document.setStore((XWikiStoreInterface) this.mockXWikiStoreInterface.proxy()); this.mockXWiki.stubs().method("getVersioningStore").will(returnValue(this.mockXWikiVersioningStore.proxy())); this.mockXWiki.stubs().method("getStore").will(returnValue(this.mockXWikiStoreInterface.proxy())); this.mockXWiki.stubs().method("getRightService").will(returnValue(this.mockXWikiRightService.proxy())); this.mockXWiki.stubs().method("getDocument").will(returnValue(this.document)); this.mockXWiki.stubs().method("getLanguagePreference").will(returnValue("en")); this.mockXWiki.stubs().method("exists").will(returnValue(false)); // Called from MessageToolVelocityContextInitializer. this.mockXWiki.stubs().method("prepareResources"); // The next 3 stubs are needed to properly initialize the Velocity engine. this.mockXWiki.stubs().method("getSkin").will(returnValue("default")); this.mockXWiki.stubs().method("getSkinFile").will(returnValue(null)); this.mockXWiki.stubs().method("Param").with(eq("xwiki.render.velocity.macrolist")).will(returnValue("")); this.mockXWiki.stubs().method("exists").will(returnValue(false)); this.mockXWiki.stubs().method("evaluateTemplate").will(returnValue("")); getContext().setWiki((XWiki) this.mockXWiki.proxy()); this.baseClass = this.document.getXClass(); this.baseClass.addTextField("string", "String", 30); this.baseClass.addTextAreaField("area", "Area", 10, 10); this.baseClass.addTextAreaField("puretextarea", "Pure text area", 10, 10); // set the text areas an non interpreted content ((TextAreaClass) this.baseClass.getField("puretextarea")).setContentType("puretext"); this.baseClass.addPasswordField("passwd", "Password", 30); this.baseClass.addBooleanField("boolean", "Boolean", "yesno"); this.baseClass.addNumberField("int", "Int", 10, "integer"); this.baseClass.addStaticListField("stringlist", "StringList", "value1, value2"); this.mockXWiki.stubs().method("getXClass").will(returnValue(this.baseClass)); this.baseObject = this.document.newObject(CLASSNAME, getContext()); this.baseObject.setStringValue("string", "string"); this.baseObject.setLargeStringValue("area", "area"); this.baseObject.setStringValue("passwd", "passwd"); this.baseObject.setIntValue("boolean", 1); this.baseObject.setIntValue("int", 42); this.baseObject.setStringListValue("stringlist", Arrays.asList("VALUE1", "VALUE2")); } @Override protected void registerComponents() throws Exception { super.registerComponents(); // Setup display configuration. this.mockDisplayConfiguration = registerMockComponent(DisplayConfiguration.class); this.mockDisplayConfiguration.stubs().method("getDocumentDisplayerHint").will(returnValue("default")); this.mockDisplayConfiguration.stubs().method("getTitleHeadingDepth").will(returnValue(2)); } public void testCurrentDocumentVariableIsInjectedBeforeRendering() throws XWikiException { // Verifies we can access the doc variable from a groovy macro. this.document.setContent("{{groovy}}print(doc);{{/groovy}}"); this.document.setSyntax(Syntax.XWIKI_2_0); assertEquals("<p>Space.Page</p>", this.document.getRenderedContent(getContext())); } public void testGetRenderedTitleWithTitle() { this.document.setSyntax(Syntax.XWIKI_2_0); this.document.setTitle("title"); assertEquals("title", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setTitle("**title**"); assertEquals("**title**", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setTitle("<strong>ti<em>tle</strong>"); // The title is parsed as plain text after the Velocity code is evaluated so the HTML have no meaning. assertEquals("<strong>ti<em>tle</strong>", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); assertEquals("<strong>ti<em>tle</strong>", this.document.getRenderedTitle(Syntax.PLAIN_1_0, getContext())); this.document.setTitle("#set($key = \"title\")$key"); assertEquals("title", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } public void testGetRenderedTitleWithoutTitleHTML() { this.document.setSyntax(Syntax.XWIKI_2_0); this.document.setContent( "content not in section\n" + "= header 1=\nheader 1 content\n" + "== header 2==\nheader 2 content"); getConfigurationSource().setProperty("xwiki.title.compatibility", "1"); assertEquals("header 1", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setContent( "content not in section\n" + "= **header 1**=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("<strong>header 1</strong>", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setContent( "content not in section\n" + "= [[Space.Page]]=\nheader 1 content\n" + "== header 2==\nheader 2 content"); this.mockXWiki.stubs().method("getURL").will(returnValue("/reference")); assertEquals("<span class=\"wikicreatelink\"><a href=\"/reference\"><span class=\"wikigeneratedlinkcontent\">" + "Page" + "</span></a></span>", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setContent("content not in section\n" + "= #set($var ~= \"value\")=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("#set($var = \"value\")", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setContent("content not in section\n" + "= {{groovy}}print \"value\"{{/groovy}}=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("value", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); this.document.setContent("content not in section\n=== header 3==="); assertEquals("Page", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } public void testGetRenderedTitleWithoutTitlePLAIN() { this.document.setSyntax(Syntax.XWIKI_2_0); this.document.setContent( "content not in section\n" + "= **header 1**=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("Page", this.document.getRenderedTitle(Syntax.PLAIN_1_0, getContext())); getConfigurationSource().setProperty("xwiki.title.compatibility", "1"); this.document.setContent( "content not in section\n" + "= **header 1**=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("header 1", this.document.getRenderedTitle(Syntax.PLAIN_1_0, getContext())); this.document.setContent("content not in section\n" + "= {{groovy}}print \"value\"{{/groovy}}=\nheader 1 content\n" + "== header 2==\nheader 2 content"); assertEquals("value", this.document.getRenderedTitle(Syntax.PLAIN_1_0, getContext())); } public void testGetRenderedTitleNoTitleAndContent() { this.document.setSyntax(Syntax.XWIKI_2_0); assertEquals("Page", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } /** * Make sure title extracted from content is protected from cycles */ public void testGetRenderedTitleRecursive() { this.document.setSyntax(Syntax.XWIKI_2_0); this.document.setContent("= {{groovy}}print doc.getDisplayTitle(){{/groovy}}"); assertEquals("Page", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } /** * See XWIKI-5277 for details. */ public void testGetRenderedContentCleansVelocityMacroCache() throws Exception { // Make sure we start not in the rendering engine since this is what happens in real: a document is // called by a template thus outside of the rendering engine. getContext().remove("isInRenderingEngine"); // We display a text area since we know that rendering a text area will call getRenderedContent inside our top // level getRenderedContent call, thus testing that velocity macros are not removed during nested calls to // getRenderedContent. this.baseObject.setLargeStringValue("area", "{{velocity}}#macro(testmacrocache)ok#end{{/velocity}}"); this.document.setContent("{{velocity}}$doc.display(\"area\")#testmacrocache{{/velocity}}"); this.document.setSyntax(Syntax.XWIKI_2_0); // We need to put the current doc in the Velocity Context since it's normally set before the rendering is // called in the execution flow. VelocityManager originalVelocityManager = getComponentManager().getInstance(VelocityManager.class); VelocityContext vcontext = originalVelocityManager.getVelocityContext(); vcontext.put("doc", new Document(this.document, getContext())); // Register a Mock for the VelocityManager to bypass skin APIs that we would need to mock otherwise. Mock mockVelocityManager = registerMockComponent(VelocityManager.class); mockVelocityManager.stubs().method("getCurrentVelocityContext").will(returnValue(vcontext)); VelocityFactory velocityFactory = getComponentManager().getInstance(VelocityFactory.class); VelocityEngine vengine = velocityFactory.createVelocityEngine("default", new Properties()); // Save the number of cached macro templates in the Velocity engine so that we can compare after the // document is rendered. JMXVelocityEngineMBean mbean = new JMXVelocityEngine(vengine); int cachedMacroNamespaceSize = mbean.getTemplates().values().size(); assertTrue(cachedMacroNamespaceSize > 0); mockVelocityManager.stubs().method("getVelocityEngine").will(returnValue(vengine)); mockVelocityManager.stubs().method("evaluate").will(new CustomStub("evaluate") { @Override public Object invoke(Invocation invocation) throws Throwable { return vengine.evaluate(vcontext, (Writer) invocation.parameterValues.get(0), (String) invocation.parameterValues.get(1), (Reader) invocation.parameterValues.get(2)); } }); // $doc.display will ask for the syntax of the current document so we need to mock it. this.mockXWiki.stubs().method("getCurrentContentSyntaxId").with(eq("xwiki/2.0"), ANYTHING) .will(returnValue("xwiki/2.0")); // Verify that the macro located inside the TextArea has been taken into account when executing the doc's // content. assertEquals("<p>ok</p>", this.document.getRenderedContent(getContext())); // Now verify that the Velocity Engine doesn't contain any more cached macro namespace to prove that // getRenderedContent has correctly cleaned the Velocity macro cache. assertEquals(cachedMacroNamespaceSize, mbean.getTemplates().values().size()); } @Test public void testGetRenderedTitleWhenMatchingTitleHeaderDepth() { this.document.setContent("=== level3"); this.document.setSyntax(Syntax.XWIKI_2_0); getConfigurationSource().setProperty("xwiki.title.compatibility", "1"); // Overwrite the title heading depth. this.mockDisplayConfiguration.stubs().method("getTitleHeadingDepth").will(returnValue(3)); assertEquals("level3", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } @Test public void testGetRenderedTitleWhenNotMatchingTitleHeaderDepth() { this.document.setContent("=== level3"); this.document.setSyntax(Syntax.XWIKI_2_0); assertEquals("Page", this.document.getRenderedTitle(Syntax.XHTML_1_0, getContext())); } public void testGetRenderedContent() throws XWikiException { this.document.setContent("**bold**"); this.document.setSyntax(Syntax.XWIKI_2_0); assertEquals("<p><strong>bold</strong></p>", this.document.getRenderedContent(getContext())); this.translatedDocument = new XWikiDocument(this.document.getDocumentReference(), Locale.FRENCH); this.translatedDocument.setContent("//italic//"); this.translatedDocument.setSyntax(Syntax.XWIKI_1_0); this.translatedDocument.setNew(false); this.mockXWiki.stubs().method("getLanguagePreference").will(returnValue(Locale.FRENCH.toString())); this.mockXWiki.stubs().method("getDocument").with(eq( new DocumentReference(this.translatedDocument.getDocumentReference(), this.translatedDocument.getLocale())), ANYTHING).will(returnValue(this.translatedDocument)); assertEquals("<p><em>italic</em></p>", this.document.getRenderedContent(getContext())); } public void testGetRenderedContentRights() throws XWikiException { getContext().remove("sdoc"); this.document.setContent("{{velocity}}$xcontext.sdoc{{/velocity}}"); this.document.setSyntax(Syntax.XWIKI_2_1); assertEquals("<p>Space.Page</p>", this.document.getRenderedContent(getContext())); } public void testGetRenderedContentTextWithSourceSyntax() { this.document.setSyntax(Syntax.XWIKI_1_0); assertEquals("<p><strong>bold</strong></p>", this.document.getRenderedContent("**bold**", "xwiki/2.0", getContext())); } public void testGetRenderedContentTextRights() throws XWikiException { XWikiDocument otherDocument = new XWikiDocument(new DocumentReference("otherwiki", "otherspace", "otherpage")); otherDocument.setContentAuthorReference(new DocumentReference("otherwiki", "XWiki", "othercontentauthor")); XWikiDocument sdoc = new XWikiDocument(new DocumentReference("callerwiki", "callerspace", "callerpage")); sdoc.setContentAuthorReference(new DocumentReference("callerwiki", "XWiki", "calleruser")); Document apiDocument = this.document.newDocument(getContext()); getContext().setDoc(null); String content = "{{velocity}}" + "$xcontext.sdoc.contentAuthorReference $xcontext.doc $xcontext.doc.contentAuthorReference" + "{{/velocity}}"; this.document.setContentAuthorReference(new DocumentReference("authorwiki", "XWiki", "contentauthor")); assertEquals("<p>$xcontext.sdoc.contentAuthorReference Space.Page authorwiki:XWiki.contentauthor</p>", this.document.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), getContext())); assertEquals("<p>$xcontext.sdoc.contentAuthorReference Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString())); assertEquals("<p>$xcontext.sdoc.contentAuthorReference Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), Syntax.XHTML_1_0.toIdString())); assertEquals("<p>otherwiki:XWiki.othercontentauthor Space.Page authorwiki:XWiki.contentauthor</p>", this.document.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), false, otherDocument, getContext())); getContext().setDoc(otherDocument); assertEquals("<p>$xcontext.sdoc.contentAuthorReference Space.Page authorwiki:XWiki.contentauthor</p>", this.document.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), getContext())); assertEquals("<p>otherwiki:XWiki.othercontentauthor Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString())); assertEquals("<p>otherwiki:XWiki.othercontentauthor Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), Syntax.XHTML_1_0.toIdString())); getContext().put("sdoc", sdoc); getContext().setDoc(this.document); assertEquals("<p>callerwiki:XWiki.calleruser Space.Page authorwiki:XWiki.contentauthor</p>", this.document.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), getContext())); assertEquals("<p>callerwiki:XWiki.calleruser Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString())); assertEquals("<p>callerwiki:XWiki.calleruser Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), Syntax.XHTML_1_0.toIdString())); getContext().setDoc(otherDocument); assertEquals("<p>$xcontext.sdoc.contentAuthorReference Space.Page authorwiki:XWiki.contentauthor</p>", this.document.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), getContext())); assertEquals("<p>callerwiki:XWiki.calleruser Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString())); assertEquals("<p>callerwiki:XWiki.calleruser Space.Page authorwiki:XWiki.contentauthor</p>", apiDocument.getRenderedContent(content, Syntax.XWIKI_2_1.toIdString(), Syntax.XHTML_1_0.toIdString())); } }