10 Commits

12 changed files with 483 additions and 137 deletions

View File

@@ -7,7 +7,7 @@ It can be accessed via:
* Macro Browser
* {markdown} tags
* SOAP API using <ac:macro ac:name="markdown"></ac:macro>
* SOAP API using <ac:macro ac:name="markdown">/ac:macro>
This macro supports the following languages:

36
pom.xml
View File

@@ -9,7 +9,7 @@
</parent>
<groupId>com.atlassian.plugins.confluence.markdown</groupId>
<artifactId>confluence-markdown-macro</artifactId>
<version>1.4.2</version>
<version>1.3.2</version>
<organization>
<name>Atlassian</name>
<url>http://www.atlassian.com/</url>
@@ -51,27 +51,27 @@
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.34.53</version>
<version>0.32.24</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-anchorlink</artifactId>
<version>0.34.53</version>
<version>0.32.24</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-autolink</artifactId>
<version>0.34.53</version>
<version>0.32.24</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-superscript</artifactId>
<version>0.34.53</version>
<version>0.32.24</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-youtube-embedded</artifactId>
<version>0.34.53</version>
<version>0.32.24</version>
</dependency>
<dependency>
<groupId>com.atlassian.plugin</groupId>
@@ -79,6 +79,24 @@
<version>${atlassian.spring.scanner.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.32</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
@@ -90,8 +108,8 @@
<configuration>
<productVersion>${confluence.version}</productVersion>
<productDataVersion>${confluence.data.version}</productDataVersion>
<enableQuickReload>true</enableQuickReload>
<enableFastdev>false</enableFastdev>
<enableQuickReload>true</enableQuickReload>
<enableFastdev>false</enableFastdev>
</configuration>
</plugin>
<plugin>
@@ -106,7 +124,7 @@
<properties>
<confluence.version>6.9.0</confluence.version>
<confluence.data.version>6.9.0</confluence.data.version>
<amps.version>6.3.21</amps.version>
<amps.version>6.3.0</amps.version>
<plugin.testrunner.version>1.1</plugin.testrunner.version>
<atlassian.spring.scanner.version>2.1.7</atlassian.spring.scanner.version>
</properties>

View File

@@ -2,43 +2,50 @@ package com.atlassian.plugins.confluence.markdown;
import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.content.render.xhtml.DefaultConversionContext;
import com.atlassian.confluence.content.render.xhtml.XhtmlException;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.confluence.xhtml.api.MacroDefinition;
import com.atlassian.confluence.xhtml.api.MacroDefinitionHandler;
import com.atlassian.confluence.xhtml.api.XhtmlContent;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;
//import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.webresource.api.assembler.PageBuilderService;
import org.springframework.beans.factory.annotation.Autowired;
import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.ins.InsExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.ins.InsExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
import com.vladsch.flexmark.superscript.SuperscriptExtension;
import com.vladsch.flexmark.ext.youtube.embedded.YouTubeLinkExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.superscript.SuperscriptExtension;
import com.vladsch.flexmark.util.options.MutableDataSet;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.net.*;
import java.io.*;
public class MarkdownFromURLMacro extends BaseMacro implements Macro {
//@Scanned
public class MarkdownFromURLMacro extends BaseMacro implements Macro
{
private final XhtmlContent xhtmlUtils;
@@ -50,37 +57,34 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro {
this.xhtmlUtils = xhtmlUtils;
}
// public MarkdownFromURLMacro(XhtmlContent xhtmlUtils)
// {
// this.xhtmlUtils = xhtmlUtils;
// }
@Override
public BodyType getBodyType() {
public BodyType getBodyType()
{
return BodyType.PLAIN_TEXT;
}
@Override
public OutputType getOutputType() {
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
@Override
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
{
//If the macro was not left blank, run the macro. Otherwise, return an empty string.
if (bodyContent != null) {
//Include highlight.js in the webpage.
//Adds something like <script type="text/javascript" src="path-to-javascript-file.js"></script> to the <head> of the page
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
MutableDataSet options = new MutableDataSet()
.set(HtmlRenderer.GENERATE_HEADER_ID, true)
.set(HtmlRenderer.INDENT_SIZE, 2)
.set(HtmlRenderer.PERCENT_ENCODE_URLS, true)
// for full GFM table compatibility add the following table extension options:
.set(TablesExtension.COLUMN_SPANS, false)
.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
.set(TablesExtension.CLASS_NAME, "confluenceTable")
.set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create()));
//Set options for flexmark. See flexmark documentation for more info.
MutableDataSet options = new MutableDataSet();
options.set(Parser.EXTENSIONS, Arrays.asList(
TablesExtension.create(),
@@ -97,39 +101,35 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro {
));
//JavaScript for syntax highlighting. See highlight.js documentation for more info.
String highlightjs = "<script>\n" +
"AJS.$('[data-macro-name=\"markdown-from-url\"] code').each(function(i, block) {\n" +
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
" hljs.highlightBlock(block);\n" +
" });\n" +
"</script>";
String highlightjscss = "<style>\n"+
".hljs {display: inline;}\n" +
"pre > code {display: block !important;}\n" +
"</style>";
String tableFixJs = "<script>AJS.$('[data-macro-name=\"markdown-from-url\"] table thead th').each(function(i, block) {\n" +
" block.classList.add(\"confluenceTh\");\n" +
"});\n" +
"\n" +
"AJS.$('[data-macro-name=\"markdown-from-url\"] table tbody tr td').each(function(i, block) {\n" +
" block.classList.add(\"confluenceTd\");\n" +
"});</script>";
//Define a custom exception for trying to import from a private Bitbucket repository
class privateRepositoryException extends Exception {
public privateRepositoryException(String message) {
super(message);
}
}
//Set up parser and renderer for flexmark.
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
String exceptionsToReturn = "";
String html = "";
String toParse = "";
//Import markdown from a URL and render it to html.
//Enclosed in a try/catch block in order to catch exceptions and return error messages to the user
try {
//Create a URL object from the URL typed in by the user,
// then fetch the URL content line by line and store each line temporarily in inputLine.
//Concatenate all the lines into toParse and trim leading and trailing whitespace.
URL importFrom = new URL(bodyContent);
BufferedReader in = new BufferedReader(
new InputStreamReader(importFrom.openStream())
@@ -140,12 +140,17 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro {
}
in.close();
toParse = toParse.trim();
//If the content of the URL is a file in a private Bitbucket repository, then throw an exception.
//Otherwise, parse and render the markdown.
if (toParse.startsWith("<html>\n<head>\n <title>OpenID transaction in progress</title>")) {
throw new privateRepositoryException("Cannot import from private repository.");
}else {
Node document = parser.parse(toParse);
html = renderer.render(document) + highlightjs + highlightjscss + tableFixJs;
html = renderer.render(document) + highlightjs;
}
}
catch (MalformedURLException u) {
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Invalid URL.</strong><br>Please enter a valid URL. If you are not trying to import markdown from a URL, use the Markdown macro instead of the Markdown from URL macro.<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
@@ -160,9 +165,12 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro {
exceptionsToReturn = exceptionsToReturn + "<strong>Error with Markdown From URL macro: Unexpected error.</strong><br>" + e.toString() + "<br>For support <a href='https://community.atlassian.com/t5/tag/addon-com.atlassian.plugins.confluence.markdown.confluence-markdown-macro/tg-p'>visit our Q&A in the Atlassian Community</a>. You can ask a new question by clicking the \"Create\" button on the top right of the Q&A.<br>";
}
finally {
//If there were exceptions, set the html to return to an error message (or multiple)
if (exceptionsToReturn != "") {
html = "<p style='background: #ffe0e0; border-radius: 5px; padding: 10px;'>" + exceptionsToReturn + "</p>";
}
//Return the output, which is either rendered markdown or an error message.
return html;
}
}else {
@@ -185,7 +193,7 @@ public class MarkdownFromURLMacro extends BaseMacro implements Macro {
try {
return execute(map, s, new DefaultConversionContext(renderContext));
} catch (MacroExecutionException e) {
throw new MacroException(e.getMessage(), e);
throw new MacroException(e.getMessage(),e);
}
}
}
}

View File

@@ -2,39 +2,50 @@ package com.atlassian.plugins.confluence.markdown;
import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.content.render.xhtml.DefaultConversionContext;
import com.atlassian.confluence.content.render.xhtml.XhtmlException;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.confluence.xhtml.api.MacroDefinition;
import com.atlassian.confluence.xhtml.api.MacroDefinitionHandler;
import com.atlassian.confluence.xhtml.api.XhtmlContent;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;
//import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.webresource.api.assembler.PageBuilderService;
import org.springframework.beans.factory.annotation.Autowired;
import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.ins.InsExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.ins.InsExtension;
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.footnotes.FootnoteExtension;
import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension;
import com.vladsch.flexmark.superscript.SuperscriptExtension;
import com.vladsch.flexmark.ext.youtube.embedded.YouTubeLinkExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.superscript.SuperscriptExtension;
import com.vladsch.flexmark.util.options.MutableDataSet;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.Map;
public class MarkdownMacro extends BaseMacro implements Macro {
//@Scanned
public class MarkdownMacro extends BaseMacro implements Macro
{
private final XhtmlContent xhtmlUtils;
private PageBuilderService pageBuilderService;
@Autowired
@@ -42,81 +53,62 @@ public class MarkdownMacro extends BaseMacro implements Macro {
this.pageBuilderService = pageBuilderService;
this.xhtmlUtils = xhtmlUtils;
}
@Override
public BodyType getBodyType() {
public BodyType getBodyType()
{
return BodyType.PLAIN_TEXT;
}
@Override
public OutputType getOutputType() {
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
@Override
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException {
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
{
//Include highlight.js in the webpage.
//Adds something like <script type="text/javascript" src="path-to-javascript-file.js"></script> to the <head> of the page
pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
MutableDataSet options = new MutableDataSet()
.set(HtmlRenderer.GENERATE_HEADER_ID, true)
.set(HtmlRenderer.INDENT_SIZE, 2)
.set(HtmlRenderer.PERCENT_ENCODE_URLS, true)
// for full GFM table compatibility add the following table extension options:
.set(TablesExtension.COLUMN_SPANS, true)
.set(TablesExtension.APPEND_MISSING_COLUMNS, true)
.set(TablesExtension.DISCARD_EXTRA_COLUMNS, true)
.set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true)
.set(TablesExtension.CLASS_NAME, "confluenceTable")
.set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create()));
//Set options for flexmark. See flexmark documentation for more info.
MutableDataSet options = new MutableDataSet();
options.set(Parser.EXTENSIONS, Arrays.asList(
TablesExtension.create(),
StrikethroughSubscriptExtension.create(),
InsExtension.create(),
TaskListExtension.create(),
FootnoteExtension.create(),
WikiLinkExtension.create(),
DefinitionExtension.create(),
AnchorLinkExtension.create(),
AutolinkExtension.create(),
SuperscriptExtension.create(),
YouTubeLinkExtension.create()
TablesExtension.create(),
StrikethroughSubscriptExtension.create(),
InsExtension.create(),
TaskListExtension.create(),
FootnoteExtension.create(),
WikiLinkExtension.create(),
DefinitionExtension.create(),
AnchorLinkExtension.create(),
AutolinkExtension.create(),
SuperscriptExtension.create(),
YouTubeLinkExtension.create()
));
//JavaScript for syntax highlighting. See highlight.js documentation for more info.
String highlightjs = "<script>\n" +
"AJS.$('[data-macro-name=\"markdown\"] code').each(function(i, block) {\n" +
" hljs.highlightBlock(block);\n" +
" });\n" +
"</script>";
String highlightjscss = "<style>\n"+
".hljs {display: inline;}\n" +
"pre > code {display: block !important;}\n" +
"</style>";
String tableFixJs = "<script> AJS.$('[data-macro-name=\"markdown\"] table thead th').each(function(i, block) {\n" +
" block.classList.add(\"confluenceTh\");\n" +
"});\n" +
"\n" +
"AJS.$('[data-macro-name=\"markdown\"] table tbody tr td').each(function(i, block) {\n" +
" block.classList.add(\"confluenceTd\");\n" +
"});</script>";
//Set up parser and renderer for flexmark.
Parser parser = Parser.builder(options).build();
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
// Parse and render the body content from markdown to HTML,
// then return the output along with the JavaScript for syntax highlighting
Node document = parser.parse(bodyContent);
String html = renderer.render(document ) + highlightjs + highlightjscss + tableFixJs; // "<p>This is <em>Sparta</em></p>\n"
String html = renderer.render(document) + highlightjs;
return html;
}
@Override
@@ -134,7 +126,7 @@ public class MarkdownMacro extends BaseMacro implements Macro {
try {
return execute(map, s, new DefaultConversionContext(renderContext));
} catch (MacroExecutionException e) {
throw new MacroException(e.getMessage(), e);
throw new MacroException(e.getMessage(),e);
}
}
}
}

View File

@@ -1,6 +0,0 @@
package com.atlassian.plugins.confluence.markdown;
public interface MyPluginComponent
{
String getName();
}

View File

@@ -6,7 +6,6 @@
<param name="plugin-icon">images/pluginIcon.png</param>
<param name="plugin-logo">images/pluginLogo.png</param>
<param name="atlassian-data-center-compatible">true</param>
<param name="atlassian-data-center-status">compatible</param>
</plugin-info>
<xhtml-macro name="markdown"

View File

@@ -0,0 +1,172 @@
package ut.com.atlassian.plugins.confluence;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.List;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.*;
import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.webresource.api.assembler.PageBuilderService;
import com.atlassian.webresource.api.assembler.RequiredResources;
import com.atlassian.webresource.api.assembler.WebResourceAssembler;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.atlassian.plugins.confluence.markdown.MarkdownFromURLMacro;
@RunWith (MockitoJUnitRunner.class)
public class MarkdownFromURLUnitTest {
@Mock
ConversionContext conversionContext;
@Mock
PageBuilderService pageBuilderService;
@Mock
WebResourceAssembler webResourceAssembler;
@Mock
RequiredResources requiredResources;
@InjectMocks
MarkdownFromURLMacro markdownMacro;
@Test
public void testMarkdownRendering() throws MacroExecutionException, MalformedURLException {
/*Test that markdown is correctly retrieved from a URL and rendered into HTML*/
String file = new File("src/test/resources/testMarkdown.md").toURI().toURL().toString();
@SuppressWarnings({ "rawtypes", "unchecked" })
String output = markdownMacro.execute(new HashMap(), file, conversionContext);
assertTrue(output.contains("<em>Italic</em>"));
}
@Test
public void testErrorHandling() throws MacroExecutionException, MalformedURLException {
/*Test error handling of nonexistent URLs*/
String input1 = new File("src/test/resources/nonexistentfile.md").toURI().toURL().toString();
@SuppressWarnings({ "rawtypes", "unchecked" })
String output1 = markdownMacro.execute(new HashMap(), input1, conversionContext);
assertTrue(output1.contains("URL does not exist"));
assertTrue(output1.contains(input1));
/*Test error handling of invalid URLs*/
String input2 = "not_a_URL";
@SuppressWarnings({ "rawtypes", "unchecked" })
String output2 = markdownMacro.execute(new HashMap(), input2, conversionContext);
assertTrue(output2.contains("Invalid URL"));
/*Test error handling of importing from a private Bitbucket repository*/
String input3 = new File("src/test/resources/testPrivateBitbucket.html").toURI().toURL().toString();
@SuppressWarnings({ "rawtypes", "unchecked" })
String output3 = markdownMacro.execute(new HashMap(), input3, conversionContext);
assertTrue(output3.contains("Importing from private Bitbucket repositories is not supported"));
assertTrue(output3.contains(input3));
}
@Test
public void testSyntaxHighlighting() throws MacroExecutionException, FailingHttpStatusCodeException, MalformedURLException, IOException {
/*Test that the javascript for syntax highlighting works*/
// Run the macro with an input of a line of code
// Create a temporary HTML file containing the output
// Parse the HTML file with htmlunit
// Assert that the page contains three spans with the correct classes
// Note: Does not test if highlight.js and highlight.css are correctly included in the page
try (final WebClient webClient = new WebClient()) {
String file = new File("src/test/resources/testSyntaxHighlighting.md").toURI().toURL().toString();
@SuppressWarnings({ "rawtypes", "unchecked" })
String output = markdownMacro.execute(new HashMap(), file, conversionContext);
String toWrite = "<!DOCTYPE html>\r\n" +
"<html>\r\n" +
"<head>\r\n" +
" <title>Syntax Highlighting Page</title>\r\n" +
" <script src=\"./jquery-3.3.1.min.js\"></script>\r\n" +
" <script src=\"../../main/resources/js/highlight.min.js\"></script>\r\n" +
" <link href=\"../../main/resources/css/highlight.min.css\" rel=\"stylesheet\" />\r\n" +
"</head>\r\n" +
"<body>\r\n" +
" <script>\r\n" +
" var AJS = {\r\n" +
" $: $\r\n" +
" };\r\n" +
" </script>\r\n" +
"<div data-macro-name='markdown'>" +
output +
"</div>" +
"</body>\r\n" +
"</html>";
File tmpHTMLFile = File.createTempFile("syntax-highlighting-", ".html", new File("src/test/resources"));
FileWriter writer = new FileWriter(tmpHTMLFile);
writer.write(toWrite);
writer.close();
final HtmlPage page = webClient.getPage(tmpHTMLFile.toURI().toURL().toString());
HtmlElement document = page.getDocumentElement();
List<HtmlElement> spans = document.getElementsByTagName("span");
tmpHTMLFile.delete();
HtmlElement hljsClassSpan = null;
int numberOfHljsClassSpans = 0;
HtmlElement hljsKeywordSpan = null;
int numberOfHljsKeywordSpans = 0;
HtmlElement hljsTitleSpan = null;
int numberOfHljsTitleSpans = 0;
//Check the class of every span element and increment the counters accordingly
//Also define the three span objects as the first span of the correct class
for (HtmlElement span : spans) {
if (span.getAttribute("class").contains("hljs-class")) {
numberOfHljsClassSpans++;
if (numberOfHljsClassSpans == 1) {
hljsClassSpan = span;
}
}
if (span.getAttribute("class").contains("hljs-keyword")) {
numberOfHljsKeywordSpans++;
if (numberOfHljsKeywordSpans == 1) {
hljsKeywordSpan = span;
}
}
if (span.getAttribute("class").contains("hljs-title")) {
numberOfHljsTitleSpans++;
if (numberOfHljsTitleSpans == 1) {
hljsTitleSpan = span;
}
}
}
// Test that there is exactly one span with a css class of hljs-class
// and that it's parent is a code element with a css class of hljs
assertThat(numberOfHljsClassSpans, is(1));
assertTrue(hljsClassSpan.getParentNode().getNodeName().equals("code"));
assertTrue(hljsClassSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs"));
// Test that there is exactly one span with a css class of hljs-keyword
// and that it's parent is a span element with a css class of hljs-class
assertThat(numberOfHljsKeywordSpans, is(1));
assertTrue(hljsKeywordSpan.getParentNode().getNodeName().equals("span"));
assertTrue(hljsKeywordSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs-class"));
// Test that there is exactly one span with a css class of hljs-title
// and that it's parent is a span element with a css class of hljs-class
assertThat(numberOfHljsTitleSpans, is(1));
assertTrue(hljsTitleSpan.getParentNode().getNodeName().equals("span"));
assertTrue(hljsTitleSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs-class"));
}
}
@Before
public void setup() {
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
}
}

View File

@@ -0,0 +1,144 @@
package ut.com.atlassian.plugins.confluence;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.*;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;
import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.webresource.api.assembler.PageBuilderService;
import com.atlassian.webresource.api.assembler.RequiredResources;
import com.atlassian.webresource.api.assembler.WebResourceAssembler;
import com.atlassian.plugins.confluence.markdown.MarkdownMacro;
@RunWith (MockitoJUnitRunner.class)
public class MarkdownUnitTest {
@Mock
ConversionContext conversionContext;
@Mock
PageBuilderService pageBuilderService;
@Mock
WebResourceAssembler webResourceAssembler;
@Mock
RequiredResources requiredResources;
@InjectMocks
MarkdownMacro markdownMacro;
@Test
public void testMarkdownRendering() throws MacroExecutionException {
/*Test that markdown is correctly rendered into HTML*/
@SuppressWarnings({ "rawtypes", "unchecked" })
String output = markdownMacro.execute(new HashMap(), "*Italic*", conversionContext);
assertTrue(output.contains("<em>Italic</em>"));
}
@Test
public void testSyntaxHighlighting() throws MacroExecutionException, IOException {
/*Test that the javascript for syntax highlighting works*/
// Run the macro with an input of a line of code
// Create a temporary HTML file containing the output
// Parse the HTML file with htmlunit
// Assert that the page contains three spans with the correct classes
// Note: Does not test if highlight.js and highlight.css are correctly included in the page
try (final WebClient webClient = new WebClient()) {
@SuppressWarnings({ "rawtypes", "unchecked" })
String output = markdownMacro.execute(new HashMap(), "`class className() {}`", conversionContext);
String toWrite = "<!DOCTYPE html>\r\n" +
"<html>\r\n" +
"<head>\r\n" +
" <title>Syntax Highlighting Page</title>\r\n" +
" <script src=\"./jquery-3.3.1.min.js\"></script>\r\n" +
" <script src=\"../../main/resources/js/highlight.min.js\"></script>\r\n" +
" <link href=\"../../main/resources/css/highlight.min.css\" rel=\"stylesheet\" />\r\n" +
"</head>\r\n" +
"<body>\r\n" +
" <script>\r\n" +
" var AJS = {\r\n" +
" $: $\r\n" +
" };\r\n" +
" </script>\r\n" +
"<div data-macro-name='markdown'>" +
output +
"</div>" +
"</body>\r\n" +
"</html>";
File tmpHTMLFile = File.createTempFile("syntax-highlighting-", ".html", new File("src/test/resources"));
FileWriter writer = new FileWriter(tmpHTMLFile);
writer.write(toWrite);
writer.close();
final HtmlPage page = webClient.getPage(tmpHTMLFile.toURI().toURL().toString());
HtmlElement document = page.getDocumentElement();
List<HtmlElement> spans = document.getElementsByTagName("span");
tmpHTMLFile.delete();
HtmlElement hljsClassSpan = null;
int numberOfHljsClassSpans = 0;
HtmlElement hljsKeywordSpan = null;
int numberOfHljsKeywordSpans = 0;
HtmlElement hljsTitleSpan = null;
int numberOfHljsTitleSpans = 0;
//Check the class of every span element and increment the counters accordingly
//Also define the three span objects as the first span of the correct class
for (HtmlElement span : spans) {
if (span.getAttribute("class").contains("hljs-class")) {
numberOfHljsClassSpans++;
if (numberOfHljsClassSpans == 1) {
hljsClassSpan = span;
}
}
if (span.getAttribute("class").contains("hljs-keyword")) {
numberOfHljsKeywordSpans++;
if (numberOfHljsKeywordSpans == 1) {
hljsKeywordSpan = span;
}
}
if (span.getAttribute("class").contains("hljs-title")) {
numberOfHljsTitleSpans++;
if (numberOfHljsTitleSpans == 1) {
hljsTitleSpan = span;
}
}
}
// Test that there is exactly one span with a css class of hljs-class
// and that it's parent is a code element with a css class of hljs
assertThat(numberOfHljsClassSpans, is(1));
assertTrue(hljsClassSpan.getParentNode().getNodeName().equals("code"));
assertTrue(hljsClassSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs"));
// Test that there is exactly one span with a css class of hljs-keyword
// and that it's parent is a span element with a css class of hljs-class
assertThat(numberOfHljsKeywordSpans, is(1));
assertTrue(hljsKeywordSpan.getParentNode().getNodeName().equals("span"));
assertTrue(hljsKeywordSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs-class"));
// Test that there is exactly one span with a css class of hljs-title
// and that it's parent is a span element with a css class of hljs-class
assertThat(numberOfHljsTitleSpans, is(1));
assertTrue(hljsTitleSpan.getParentNode().getNodeName().equals("span"));
assertTrue(hljsTitleSpan.getParentNode().getAttributes().getNamedItem("class").getNodeValue().contains("hljs-class"));
}
}
@Before
public void setup() {
//Mock methods for pageBuilderService.assembler().resources().requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs");
Mockito.when(pageBuilderService.assembler()).thenReturn(webResourceAssembler);
Mockito.when(webResourceAssembler.resources()).thenReturn(requiredResources);
Mockito.when(requiredResources.requireWebResource("com.atlassian.plugins.confluence.markdown.confluence-markdown-macro:highlightjs")).thenReturn(requiredResources);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
*Italic*

View File

@@ -0,0 +1,15 @@
<html>
<head>
<title>OpenID transaction in progress</title>
</head>
<body onload="document.forms[0].submit();">
<form id="openid_message" action="https://id.atlassian.com/openid/v2/op" method="post" accept-charset="UTF-8" enctype="application/x-www-form-urlencoded"><input name="openid.ns.crowdid" type="hidden" value="https://developer.atlassian.com/display/CROWDDEV/CrowdID+OpenID+extensions#CrowdIDOpenIDextensions-login-page-parameters"/><input name="openid.return_to" type="hidden" value="https://bitbucket.org/socialauth/complete/atlassianid/?janrain_nonce=2018-09-03T16%3A44%3A09ZAMqmtD"/><input name="openid.realm" type="hidden" value="https://bitbucket.org"/><input name="openid.ns" type="hidden" value="http://specs.openid.net/auth/2.0"/><input name="openid.sreg.optional" type="hidden" value="fullname,nickname,email"/><input name="openid.claimed_id" type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select"/><input name="openid.ns.sreg" type="hidden" value="http://openid.net/extensions/sreg/1.1"/><input name="openid.crowdid.application" type="hidden" value="bitbucket"/><input name="openid.assoc_handle" type="hidden" value="8063999"/><input name="openid.mode" type="hidden" value="checkid_setup"/><input name="openid.identity" type="hidden" value="http://specs.openid.net/auth/2.0/identifier_select"/><input type="submit" value="Continue"/></form>
<script>
var elements = document.forms[0].elements;
for (var i = 0; i < elements.length; i++) {
elements[i].style.display = "none";
}
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
`class className() {}`