diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-08-28 19:55:24 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-08-28 19:55:38 +0100 |
| commit | b1ae23f544c479d2b71347bbf67df7baac40d78f (patch) | |
| tree | 383e694dae42ba544f690c3b0ab388a7931c0793 | |
| parent | 7dba15069f9a606b9e248d7e4cd024ddaa6ac3e3 (diff) | |
| parent | c42714e5f4b643de27b106a084601e39b7e5d04c (diff) | |
Merge branch 'master' into image-errors
Conflicts:
test/mammoth.tests.js
43 files changed, 2896 insertions, 2703 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..00044b5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +If you're reporting a bug or requesting a feature, it's handy to have: +* a minimal example document +* the HTML output that you'd expect + +If you're reporting a bug, it's also useful to know what platform you're +running on, including: + +* the operating system and version +* if running in the browser: + * the browser and version + * whether you get the same issue in other browsers +* if running on node.js, the version you're using @@ -1,3 +1,11 @@ +# 1.2.0 + +* Throw more informative error when word/document.xml cannot be found. + +# 1.1.0 + +* Add support for comments. + # 1.0.4 * Add support for w:sdt elements. This allows the bodies of content controls, @@ -39,6 +39,8 @@ The following features are currently supported: * Text boxes. The contents of the text box are treated as a separate paragraph that appears after the paragraph containing the text box. +* Comments. + ## Web demo The easiest way to try out mammoth is to use the web demo: @@ -295,6 +297,27 @@ var options = { mammoth.convertToHtml({path: "path/to/document.docx"}, options); ``` +#### Comments + +By default, comments are ignored. +To include comments in the generated HTML, +add a style mapping for `comment-reference`. +For instance: + +```javascript +var mammoth = require("mammoth"); + +var options = { + styleMap: [ + "comment-reference => sup" + ] +}; +mammoth.convertToHtml({path: "path/to/document.docx"}, options); +``` + +Comments will be appended to the end of the document, +with links to the comments wrapped using the specified style mapping. + ### API #### `mammoth.convertToHtml(input, options)` diff --git a/lib/document-matchers.js b/lib/document-matchers.js index 60bc65f..bdc213c 100644 --- a/lib/document-matchers.js +++ b/lib/document-matchers.js @@ -4,7 +4,7 @@ exports.bold = new Matcher("bold"); exports.italic = new Matcher("italic"); exports.underline = new Matcher("underline"); exports.strikethrough = new Matcher("strikethrough"); -exports.commentReference = new Matcher("comment-reference"); +exports.commentReference = new Matcher("commentReference"); function paragraph(options) { diff --git a/lib/document-to-html.js b/lib/document-to-html.js index 6aa7a9c..e022946 100644 --- a/lib/document-to-html.js +++ b/lib/document-to-html.js @@ -13,18 +13,24 @@ exports.DocumentConverter = DocumentConverter; function DocumentConverter(options) { return { - convertToHtml: function() { - var conversion = new DocumentConversion(options); - return conversion.convertToHtml.apply(this, arguments); + convertToHtml: function(element) { + var comments = _.indexBy( + element.type === documents.types.document ? element.comments : [], + "commentId" + ); + var conversion = new DocumentConversion(options, comments); + return conversion.convertToHtml(element); } }; } -function DocumentConversion(options) { +function DocumentConversion(options, comments) { var noteNumber = 1; var noteReferences = []; + var referencedComments = []; + options = _.extend({ignoreEmptyParagraphs: true}, options); var idPrefix = options.idPrefix === undefined ? "" : options.idPrefix; @@ -89,16 +95,17 @@ function DocumentConversion(options) { } function convertParagraph(element, messages) { - var style = styleForParagraph(element, messages); - - var children = convertElements(element.children, messages); - if (!options.ignoreEmptyParagraphs) { - children = [Html.forceWrite].concat(children); - } - return Html.pathToNodes(style, children); + return htmlPathForParagraph(element, messages).wrap(function() { + var content = convertElements(element.children, messages); + if (options.ignoreEmptyParagraphs) { + return content; + } else { + return [Html.forceWrite].concat(content); + } + }); } - function styleForParagraph(element, messages) { + function htmlPathForParagraph(element, messages) { var style = findStyle(element); if (style) { @@ -112,58 +119,60 @@ function DocumentConversion(options) { } function convertRun(run, messages) { - var style; - if (run.styleId) { - style = findStyle(run); - if (!style) { - messages.push(unrecognisedStyleWarning("run", run)); - } - } - var result = convertElements(run.children, messages); + var nodes = function() { + return convertElements(run.children, messages); + }; + var paths = []; if (run.isStrikethrough) { - result = convertRunProperty(result, "strikethrough", "s"); + paths.push(findHtmlPathForRunProperty("strikethrough", "s")); } if (run.isUnderline) { - result = convertUnderline(result); + paths.push(findHtmlPathForRunProperty("underline")); } if (run.verticalAlignment === documents.verticalAlignment.subscript) { - result = wrapInNonFreshElement(result, "sub"); + paths.push(htmlPaths.element("sub", {}, {fresh: false})); } if (run.verticalAlignment === documents.verticalAlignment.superscript) { - result = wrapInNonFreshElement(result, "sup"); + paths.push(htmlPaths.element("sup", {}, {fresh: false})); } if (run.isItalic) { - result = convertRunProperty(result, "italic", "em"); + paths.push(findHtmlPathForRunProperty("italic", "em")); } if (run.isBold) { - result = convertRunProperty(result, "bold", "strong"); + paths.push(findHtmlPathForRunProperty("bold", "strong")); } - if (style) { - result = Html.pathToNodes(style.to, result); + var stylePath = htmlPaths.empty; + if (run.styleId) { + var style = findStyle(run); + if (style) { + stylePath = style.to; + } else { + messages.push(unrecognisedStyleWarning("run", run)); + } } - return result; - } - - function wrapInNonFreshElement(nodes, tagName) { - return [Html.nonFreshElement(tagName, {}, nodes)]; + paths.push(stylePath); + + paths.forEach(function(path) { + nodes = path.wrap.bind(path, nodes); + }); + + return nodes(); } - function convertUnderline(runHtml) { - var style = findStyle({type: "underline"}); - if (style) { - return Html.pathToNodes(style.to, runHtml); + function findHtmlPathForRunProperty(elementType, defaultTagName) { + var path = findHtmlPath({type: elementType}); + if (path) { + return path; + } else if (defaultTagName) { + return htmlPaths.element(defaultTagName, {}, {fresh: false}); } else { - return runHtml; + return htmlPaths.empty; } } - function convertRunProperty(result, elementType, defaultTagName) { - var style = findStyle({type: elementType}); - if (style) { - return Html.pathToNodes(style.to, result); - } else { - return wrapInNonFreshElement(result, defaultTagName); - } + function findHtmlPath(element, defaultPath) { + var style = findStyle(element); + return style ? style.to : defaultPath; } function findStyle(element) { @@ -192,11 +201,19 @@ function DocumentConversion(options) { } function noteHtmlId(note) { - return htmlId(note.noteType + "-" + note.noteId); + return referentHtmlId(note.noteType, note.noteId); } function noteRefHtmlId(note) { - return htmlId(note.noteType + "-ref-" + note.noteId); + return referenceHtmlId(note.noteType, note.noteId); + } + + function referentHtmlId(referenceType, referenceId) { + return htmlId(referenceType + "-" + referenceId); + } + + function referenceHtmlId(referenceType, referenceId) { + return htmlId(referenceType + "-ref-" + referenceId); } function htmlId(suffix) { @@ -226,6 +243,46 @@ function DocumentConversion(options) { ]; } + function convertCommentReference(reference, messages) { + return findHtmlPath(reference, htmlPaths.ignore).wrap(function() { + var comment = comments[reference.commentId]; + var count = referencedComments.length + 1; + var label = "[" + commentAuthorLabel(comment) + count + "]"; + referencedComments.push({label: label, comment: comment}); + // TODO: remove duplication with note references + return [ + Html.freshElement("a", { + href: "#" + referentHtmlId("comment", reference.commentId), + id: referenceHtmlId("comment", reference.commentId) + }, [Html.text(label)]) + ]; + }); + } + + function convertComment(referencedComment, messages) { + // TODO: remove duplication with note references + + var label = referencedComment.label; + var comment = referencedComment.comment; + var body = convertElements(comment.body).concat([ + Html.nonFreshElement("p", {}, [ + Html.text(" "), + Html.freshElement("a", {"href": "#" + referenceHtmlId("comment", comment.commentId)}, [ + Html.text("↑") + ]) + ]) + ]); + + return [ + Html.freshElement( + "dt", + {"id": referentHtmlId("comment", comment.commentId)}, + [Html.text("Comment " + label)] + ), + Html.freshElement("dd", {}, body) + ]; + } + function wrapChildrenInFreshElement(element, wrapElementName, messages) { var children = convertElements(element.children, messages); return [ @@ -241,7 +298,10 @@ function DocumentConversion(options) { }); var notesNodes = convertElements(notes, messages); return children.concat([ - Html.freshElement("ol", {}, notesNodes) + Html.freshElement("ol", {}, notesNodes), + Html.freshElement("dl", {}, flatMap(referencedComments, function(referencedComment) { + return convertComment(referencedComment, messages); + })) ]); }, "paragraph": convertParagraph, @@ -283,6 +343,8 @@ function DocumentConversion(options) { return Html.freshElement("li", {id: noteHtmlId(element)}, body); }, + "commentReference": convertCommentReference, + "comment": convertComment, "image": deferredConversion(recoveringConvertImage(options.convertImage || defaultConvertImage)), "table": convertTable, "tableRow": convertTableRow, @@ -331,3 +393,7 @@ function walkHtml(nodes, callback) { } }); } + +var commentAuthorLabel = exports.commentAuthorLabel = function commentAuthorLabel(comment) { + return comment.authorInitials || ""; +}; diff --git a/lib/documents.js b/lib/documents.js index d4a28af..428dc09 100644 --- a/lib/documents.js +++ b/lib/documents.js @@ -24,7 +24,8 @@ function Document(children, options) { return { type: types.document, children: children, - notes: options.notes || new Notes({}) + notes: options.notes || new Notes({}), + comments: options.comments || [] }; } @@ -181,9 +182,9 @@ function BookmarkStart(options) { }; } -exports.Document = Document; -exports.Paragraph = Paragraph; -exports.Run = Run; +exports.document = exports.Document = Document; +exports.paragraph = exports.Paragraph = Paragraph; +exports.run = exports.Run = Run; exports.Text = Text; exports.Tab = Tab; exports.Hyperlink = Hyperlink; diff --git a/lib/docx/body-reader.js b/lib/docx/body-reader.js index b514363..7b3120d 100644 --- a/lib/docx/body-reader.js +++ b/lib/docx/body-reader.js @@ -379,13 +379,13 @@ var ignoreElements = { "office-word:wrap": true, "v:shadow": true, "v:shapetype": true, + "w:annotationRef": true, "w:bookmarkEnd": true, "w:sectPr": true, "w:proofErr": true, "w:lastRenderedPageBreak": true, "w:commentRangeStart": true, "w:commentRangeEnd": true, - "w:commentReference": true, "w:del": true, "w:footnoteRef": true, "w:endnoteRef": true, diff --git a/lib/docx/document-xml-reader.js b/lib/docx/document-xml-reader.js index 12ca19e..bb912e4 100644 --- a/lib/docx/document-xml-reader.js +++ b/lib/docx/document-xml-reader.js @@ -12,7 +12,10 @@ function DocumentXmlReader(options) { var result = bodyReader.readXmlElements(body.children) .map(function(children) { - return new documents.Document(children, {notes: options.notes}); + return new documents.Document(children, { + notes: options.notes, + comments: options.comments + }); }); return new Result(result.value, result.messages); } diff --git a/lib/docx/docx-reader.js b/lib/docx/docx-reader.js index 6ad63ab..0c7d57f 100644 --- a/lib/docx/docx-reader.js +++ b/lib/docx/docx-reader.js @@ -14,6 +14,7 @@ var contentTypesReader = require("./content-types-reader"); var numberingXml = require("./numbering-xml"); var stylesReader = require("./styles-reader"); var notesReader = require("./notes-reader"); +var commentsReader = require("./comments-reader"); var Files = require("./files").Files; @@ -40,6 +41,13 @@ function read(docxFile, input) { } else { return new Result([]); } + }), + comments: readXmlFileWithBody("comments", result, function(bodyReader, xml) { + if (xml) { + return commentsReader.createCommentsReader(bodyReader)(xml); + } else { + return new Result([]); + } }) }; }).also(function(result) { @@ -52,13 +60,20 @@ function read(docxFile, input) { }; }).then(function(result) { return readXmlFileWithBody("document", result, function(bodyReader, xml) { - return result.notes.flatMap(function(notes) { - var reader = new DocumentXmlReader({ - bodyReader: bodyReader, - notes: notes + if (xml) { + return result.notes.flatMap(function(notes) { + return result.comments.flatMap(function(comments) { + var reader = new DocumentXmlReader({ + bodyReader: bodyReader, + notes: notes, + comments: comments + }); + return reader.convertXmlToDocument(xml); + }); }); - return reader.convertXmlToDocument(xml); - }); + } else { + throw new Error("Could not find word/document.xml in ZIP file. Are you sure this is a valid .docx file?"); + } }); }); } diff --git a/lib/html-paths.js b/lib/html-paths.js index 04bc655..5a00cd8 100644 --- a/lib/html-paths.js +++ b/lib/html-paths.js @@ -1,5 +1,7 @@ var _ = require("underscore"); +var html = require("./html"); + exports.topLevelElement = topLevelElement; exports.elements = elements; exports.element = element; @@ -9,15 +11,27 @@ function topLevelElement(tagName, attributes) { } function elements(elementStyles) { - return elementStyles.map(function(elementStyle) { + return new HtmlPath(elementStyles.map(function(elementStyle) { if (_.isString(elementStyle)) { return element(elementStyle); } else { return elementStyle; } - }); + })); +} + +function HtmlPath(elements) { + this._elements = elements; } +HtmlPath.prototype.wrap = function wrap(children) { + var result = children(); + for (var index = this._elements.length - 1; index >= 0; index--) { + result = this._elements[index].wrapNodes(result); + } + return result; +}; + function element(tagName, attributes, options) { options = options || {}; return new Element(tagName, attributes, options); @@ -44,4 +58,17 @@ Element.prototype.matchesElement = function(element) { return this.tagNames[element.tagName] && _.isEqual(this.attributes || {}, element.attributes || {}); }; +Element.prototype.wrap = function wrap(generateNodes) { + return this.wrapNodes(generateNodes()); +}; + +Element.prototype.wrapNodes = function wrapNodes(nodes) { + return [html.elementWithTag(this, nodes)]; +}; + exports.empty = elements([]); +exports.ignore = { + wrap: function() { + return []; + } +}; diff --git a/lib/html/index.js b/lib/html/index.js index edc8256..600d783 100644 --- a/lib/html/index.js +++ b/lib/html/index.js @@ -7,15 +7,6 @@ exports.selfClosingElement = ast.selfClosingElement; exports.text = ast.text; exports.forceWrite = ast.forceWrite; -function pathToNodes(path, children) { - var result = children; - for (var index = path.length - 1; index >= 0; index--) { - result = [ast.elementWithTag(path[index], result)]; - } - return result; -} - -exports.pathToNodes = pathToNodes; exports.simplify = require("./simplify"); function write(writer, nodes) { diff --git a/lib/options-reader.js b/lib/options-reader.js index 19934d8..3765fd3 100644 --- a/lib/options-reader.js +++ b/lib/options-reader.js @@ -26,6 +26,8 @@ var standardOptions = exports._standardOptions = { "r[style-name='footnote reference'] =>", "p[style-name='endnote text'] => p", "r[style-name='endnote reference'] =>", + "p[style-name='annotation text'] => p", + "r[style-name='annotation reference'] =>", // LibreOffice "p[style-name='Footnote'] => p", diff --git a/lib/style-reader.js b/lib/style-reader.js index ab9a171..fe60930 100644 --- a/lib/style-reader.js +++ b/lib/style-reader.js @@ -177,16 +177,21 @@ function htmlPathRule() { return htmlPaths.element(tagName, attributes, options); }); - return lop.rules.then( - lop.rules.zeroOrMoreWithSeparator( - styleElementRule, - lop.rules.sequence( - whitespaceRule, - lop.rules.tokenOfType("gt"), - whitespaceRule - ) - ), - htmlPaths.elements + return lop.rules.firstOf("html path", + lop.rules.then(lop.rules.tokenOfType("bang"), function() { + return htmlPaths.ignore; + }), + lop.rules.then( + lop.rules.zeroOrMoreWithSeparator( + styleElementRule, + lop.rules.sequence( + whitespaceRule, + lop.rules.tokenOfType("gt"), + whitespaceRule + ) + ), + htmlPaths.elements + ) ); } @@ -234,7 +239,8 @@ function tokenise(string) { {name: "close-square-bracket", regex: /\]/}, {name: "string", regex: /'([^']*)'/}, {name: "integer", regex: /([0-9]+)/}, - {name: "choice", regex: /\|/} + {name: "choice", regex: /\|/}, + {name: "bang", regex: /(!)/} ]); return tokeniser.tokenise(string); } diff --git a/package.json b/package.json index 7ca400e..74d3644 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mammoth", - "version": "1.0.4", + "version": "1.1.0", "author": "Michael Williamson <mike@zwobble.org>", "description": "Convert Word documents from docx to simple HTML and Markdown", "keywords": [ diff --git a/test/document-matchers.tests.js b/test/document-matchers.tests.js index 58dbf80..b989b2d 100644 --- a/test/document-matchers.tests.js +++ b/test/document-matchers.tests.js @@ -1,50 +1,48 @@ var assert = require("assert"); -var test = require("./testing").test; +var test = require("./test")(module); var documentMatchers = require("../lib/document-matchers"); var documents = require("../lib/documents"); var Paragraph = documents.Paragraph; -describe("document-matchers", function() { - test("paragraph with no options matches any paragraph", function() { - var matcher = documentMatchers.paragraph(); - assert.ok(matcher.matches(new Paragraph())); - assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); - }); - - test("paragraph style ID only matches paragraphs with that style ID", function() { - var matcher = documentMatchers.paragraph({styleId: "Heading1"}); - assert.ok(!matcher.matches(new Paragraph())); - assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); - assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2"))); - }); - - test("paragraph style name only matches paragraphs with that style name", function() { - var matcher = documentMatchers.paragraph({styleName: "Heading 1"}); - assert.ok(!matcher.matches(new Paragraph())); - assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); - assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2"))); - }); - - test("ordered-list(index) matches an ordered list with specified level index", function() { - var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 1}}); - assert.ok(!matcher.matches(new Paragraph())); - assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}}))); - assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 0, isOrdered: true}}))); - assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}}))); - }); - - test("unordered-list(index) matches an unordered list with specified level index", function() { - var matcher = documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 1}}); - assert.ok(!matcher.matches(new Paragraph())); - assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}}))); - assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}}))); - }); - - test("matchers for lists with index 0 do not match elements that are not lists", function() { - var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}}); - assert.ok(!matcher.matches(new Paragraph())); - }); +test("paragraph with no options matches any paragraph", function() { + var matcher = documentMatchers.paragraph(); + assert.ok(matcher.matches(new Paragraph())); + assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); +}); + +test("paragraph style ID only matches paragraphs with that style ID", function() { + var matcher = documentMatchers.paragraph({styleId: "Heading1"}); + assert.ok(!matcher.matches(new Paragraph())); + assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); + assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2"))); +}); + +test("paragraph style name only matches paragraphs with that style name", function() { + var matcher = documentMatchers.paragraph({styleName: "Heading 1"}); + assert.ok(!matcher.matches(new Paragraph())); + assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1"))); + assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2"))); +}); + +test("ordered-list(index) matches an ordered list with specified level index", function() { + var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 1}}); + assert.ok(!matcher.matches(new Paragraph())); + assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}}))); + assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 0, isOrdered: true}}))); + assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}}))); +}); + +test("unordered-list(index) matches an unordered list with specified level index", function() { + var matcher = documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 1}}); + assert.ok(!matcher.matches(new Paragraph())); + assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}}))); + assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}}))); +}); + +test("matchers for lists with index 0 do not match elements that are not lists", function() { + var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}}); + assert.ok(!matcher.matches(new Paragraph())); }); function paragraphWithStyle(styleId, styleName) { diff --git a/test/document-to-html.tests.js b/test/document-to-html.tests.js index 0b4f905..cc8aff5 100644 --- a/test/document-to-html.tests.js +++ b/test/document-to-html.tests.js @@ -2,8 +2,10 @@ var assert = require("assert"); var promises = require("../lib/promises"); var documents = require("../lib/documents"); -var DocumentConverter = require("../lib/document-to-html").DocumentConverter; -var test = require("./testing").test; +var documentToHtml = require("../lib/document-to-html"); +var DocumentConverter = documentToHtml.DocumentConverter; +var commentAuthorLabel = documentToHtml.commentAuthorLabel; +var test = require("./test")(module); var htmlPaths = require("../lib/html-paths"); var xml = require("../lib/xml"); var results = require("../lib/results"); @@ -11,543 +13,592 @@ var documentMatchers = require("../lib/document-matchers"); var Html = require("../lib/html"); -describe('DocumentConverter', function() { - test('should empty document to empty string', function() { - var document = new documents.Document([]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, ""); - }); +test('should empty document to empty string', function() { + var document = new documents.Document([]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, ""); }); - - test('should convert document containing one paragraph to single p element', function() { - var document = new documents.Document([ - paragraphOfText("Hello.") - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<p>Hello.</p>"); - }); +}); + +test('should convert document containing one paragraph to single p element', function() { + var document = new documents.Document([ + paragraphOfText("Hello.") + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<p>Hello.</p>"); }); - - test('ignores empty paragraphs', function() { - var document = new documents.Document([ - paragraphOfText("") - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, ""); - }); +}); + +test('ignores empty paragraphs', function() { + var document = new documents.Document([ + paragraphOfText("") + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, ""); }); - - test('text is HTML-escaped', function() { - var document = new documents.Document([ - paragraphOfText("1 < 2") - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<p>1 < 2</p>"); - }); +}); + +test('text is HTML-escaped', function() { + var document = new documents.Document([ + paragraphOfText("1 < 2") + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<p>1 < 2</p>"); }); - - test('should convert document containing multiple paragraphs to multiple p elements', function() { - var document = new documents.Document([ - paragraphOfText("Hello."), - paragraphOfText("Goodbye.") - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<p>Hello.</p><p>Goodbye.</p>"); - }); +}); + +test('should convert document containing multiple paragraphs to multiple p elements', function() { + var document = new documents.Document([ + paragraphOfText("Hello."), + paragraphOfText("Goodbye.") + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<p>Hello.</p><p>Goodbye.</p>"); }); - - test('uses style mappings to pick HTML element for docx paragraph', function() { - var document = new documents.Document([ - paragraphOfText("Hello.", "Heading1", "Heading 1") - ]); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.paragraph({styleName: "Heading 1"}), - to: htmlPaths.topLevelElement("h1") - } - ] - }); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<h1>Hello.</h1>"); - }); +}); + +test('uses style mappings to pick HTML element for docx paragraph', function() { + var document = new documents.Document([ + paragraphOfText("Hello.", "Heading1", "Heading 1") + ]); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.paragraph({styleName: "Heading 1"}), + to: htmlPaths.topLevelElement("h1") + } + ] }); - - test('mappings for style names are case insensitive', function() { - var document = new documents.Document([ - paragraphOfText("Hello.", "Heading1", "heading 1") - ]); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.paragraph({styleName: "Heading 1"}), - to: htmlPaths.topLevelElement("h1") - } - ] - }); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<h1>Hello.</h1>"); - }); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<h1>Hello.</h1>"); }); - - test('can use non-default HTML element for unstyled paragraphs', function() { - var document = new documents.Document([ - paragraphOfText("Hello.") - ]); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.paragraph(), - to: htmlPaths.topLevelElement("h1") - } - ] - }); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<h1>Hello.</h1>"); - }); +}); + +test('mappings for style names are case insensitive', function() { + var document = new documents.Document([ + paragraphOfText("Hello.", "Heading1", "heading 1") + ]); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.paragraph({styleName: "Heading 1"}), + to: htmlPaths.topLevelElement("h1") + } + ] }); - - test('warning is emitted if paragraph style is unrecognised', function() { - var document = new documents.Document([ - paragraphOfText("Hello.", "Heading1", "Heading 1") - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.deepEqual(result.messages, [results.warning("Unrecognised paragraph style: 'Heading 1' (Style ID: Heading1)")]); - }); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<h1>Hello.</h1>"); }); - - test('can use stacked styles to generate nested HTML elements', function() { - var document = new documents.Document([ - paragraphOfText("Hello.") - ]); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.paragraph(), - to: htmlPaths.elements(["h1", "span"]) - } - ] - }); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, "<h1><span>Hello.</span></h1>"); - }); +}); + +test('can use non-default HTML element for unstyled paragraphs', function() { + var document = new documents.Document([ + paragraphOfText("Hello.") + ]); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.paragraph(), + to: htmlPaths.topLevelElement("h1") + } + ] }); - - test('bold runs are wrapped in <strong> tags by default', function() { - var run = runOfText("Hello.", {isBold: true}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<strong>Hello.</strong>"); - }); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<h1>Hello.</h1>"); }); - - test('bold runs can be configured with style mapping', function() { - var run = runOfText("Hello.", {isBold: true}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.bold, - to: htmlPaths.elements([htmlPaths.element("em")]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<em>Hello.</em>"); - }); +}); + +test('warning is emitted if paragraph style is unrecognised', function() { + var document = new documents.Document([ + paragraphOfText("Hello.", "Heading1", "Heading 1") + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.deepEqual(result.messages, [results.warning("Unrecognised paragraph style: 'Heading 1' (Style ID: Heading1)")]); }); - - test('bold runs can exist inside other tags', function() { - var run = new documents.Paragraph([ - runOfText("Hello.", {isBold: true}) - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<p><strong>Hello.</strong></p>"); - }); +}); + +test('can use stacked styles to generate nested HTML elements', function() { + var document = new documents.Document([ + paragraphOfText("Hello.") + ]); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.paragraph(), + to: htmlPaths.elements(["h1", "span"]) + } + ] }); - - test('consecutive bold runs are wrapped in a single <strong> element', function() { - var paragraph = new documents.Paragraph([ - runOfText("Hello", {isBold: true}), - runOfText(".", {isBold: true}) - ]); - var converter = new DocumentConverter(); - return converter.convertToHtml(paragraph).then(function(result) { - assert.equal(result.value, "<p><strong>Hello.</strong></p>"); - }); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, "<h1><span>Hello.</span></h1>"); }); - - test('underline runs are ignored by default', function() { - var run = runOfText("Hello.", {isUnderline: true}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "Hello."); - }); +}); + +test('bold runs are wrapped in <strong> tags by default', function() { + var run = runOfText("Hello.", {isBold: true}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<strong>Hello.</strong>"); }); - - test('underline runs can be mapped using style mapping', function() { - var run = runOfText("Hello.", {isUnderline: true}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.underline, - to: htmlPaths.elements([htmlPaths.element("u")]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<u>Hello.</u>"); - }); +}); + +test('bold runs can be configured with style mapping', function() { + var run = runOfText("Hello.", {isBold: true}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.bold, + to: htmlPaths.elements([htmlPaths.element("em")]) + } + ] }); - - test('style mapping for underline runs does not close parent elements', function() { - var run = runOfText("Hello.", {isUnderline: true, isBold: true}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.underline, - to: htmlPaths.elements([htmlPaths.element("u")]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<strong><u>Hello.</u></strong>"); - }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<em>Hello.</em>"); }); - - test('strikethrough runs are wrapped in <s> tags by default', function() { - var run = runOfText("Hello.", {isStrikethrough: true}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<s>Hello.</s>"); - }); +}); + +test('bold runs can exist inside other tags', function() { + var run = new documents.Paragraph([ + runOfText("Hello.", {isBold: true}) + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<p><strong>Hello.</strong></p>"); }); - - test('strikethrough runs can be configured with style mapping', function() { - var run = runOfText("Hello.", {isStrikethrough: true}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.strikethrough, - to: htmlPaths.elements([htmlPaths.element("del")]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<del>Hello.</del>"); - }); +}); + +test('consecutive bold runs are wrapped in a single <strong> element', function() { + var paragraph = new documents.Paragraph([ + runOfText("Hello", {isBold: true}), + runOfText(".", {isBold: true}) + ]); + var converter = new DocumentConverter(); + return converter.convertToHtml(paragraph).then(function(result) { + assert.equal(result.value, "<p><strong>Hello.</strong></p>"); }); +}); - test('italic runs are wrapped in <em> tags', function() { - var run = runOfText("Hello.", {isItalic: true}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<em>Hello.</em>"); - }); +test('underline runs are ignored by default', function() { + var run = runOfText("Hello.", {isUnderline: true}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "Hello."); }); - - test('italic runs can be configured with style mapping', function() { - var run = runOfText("Hello.", {isItalic: true}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.italic, - to: htmlPaths.elements([htmlPaths.element("strong")]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<strong>Hello.</strong>"); - }); +}); + +test('underline runs can be mapped using style mapping', function() { + var run = runOfText("Hello.", {isUnderline: true}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.underline, + to: htmlPaths.elements([htmlPaths.element("u")]) + } + ] }); - - test('run can be both bold and italic', function() { - var run = runOfText("Hello.", {isBold: true, isItalic: true}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<strong><em>Hello.</em></strong>"); - }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<u>Hello.</u>"); }); - - test('superscript runs are wrapped in <sup> tags', function() { - var run = runOfText("Hello.", { - verticalAlignment: documents.verticalAlignment.superscript - }); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<sup>Hello.</sup>"); - }); +}); + +test('style mapping for underline runs does not close parent elements', function() { + var run = runOfText("Hello.", {isUnderline: true, isBold: true}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.underline, + to: htmlPaths.elements([htmlPaths.element("u")]) + } + ] }); - - test('subscript runs are wrapped in <sub> tags', function() { - var run = runOfText("Hello.", { - verticalAlignment: documents.verticalAlignment.subscript - }); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<sub>Hello.</sub>"); - }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<strong><u>Hello.</u></strong>"); }); - - test('run styles are converted to HTML if mapping exists', function() { - var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"}); - var converter = new DocumentConverter({ - styleMap: [ - { - from: documentMatchers.run({styleName: "Heading 1 Char"}), - to: htmlPaths.elements(["strong"]) - } - ] - }); - return converter.convertToHtml(run).then(function(result) { - assert.equal(result.value, "<strong>Hello.</strong>"); - }); +}); + +test('strikethrough runs are wrapped in <s> tags by default', function() { + var run = runOfText("Hello.", {isStrikethrough: true}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<s>Hello.</s>"); }); - - test('warning is emitted if run style is unrecognised', function() { - var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"}); - var converter = new DocumentConverter(); - return converter.convertToHtml(run).then(function(result) { - assert.deepEqual(result.messages, [results.warning("Unrecognised run style: 'Heading 1 Char' (Style ID: Heading1Char)")]); - }); +}); + +test('strikethrough runs can be configured with style mapping', function() { + var run = runOfText("Hello.", {isStrikethrough: true}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.strikethrough, + to: htmlPaths.elements([htmlPaths.element("del")]) + } + ] }); - - test('docx hyperlink is converted to <a>', function() { - var hyperlink = new documents.Hyperlink( - [runOfText("Hello.")], - {href: "http://www.example.com"} - ); - var converter = new DocumentConverter(); - return converter.convertToHtml(hyperlink).then(function(result) { - assert.equal(result.value, '<a href="http://www.example.com">Hello.</a>'); - }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<del>Hello.</del>"); }); +}); - test('docx hyperlink with anchor is converted to <a>', function() { - var hyperlink = new documents.Hyperlink( - [runOfText("Hello.")], - {anchor: "_Peter"} - ); - var converter = new DocumentConverter({ - idPrefix: "doc-42-" - }); - return converter.convertToHtml(hyperlink).then(function(result) { - assert.equal(result.value, '<a href="#doc-42-_Peter">Hello.</a>'); - }); +test('italic runs are wrapped in <em> tags', function() { + var run = runOfText("Hello.", {isItalic: true}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<em>Hello.</em>"); }); +}); - test('bookmarks are converted to anchors', function() { - var bookmarkStart = new documents.BookmarkStart({name: "_Peter"}); - var converter = new DocumentConverter({ - idPrefix: "doc-42-" - }); - var document = new documents.Document([bookmarkStart]); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value, '<a id="doc-42-_Peter"></a>'); - }); +test('italic runs can be configured with style mapping', function() { + var run = runOfText("Hello.", {isItalic: true}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.italic, + to: htmlPaths.elements([htmlPaths.element("strong")]) + } + ] + }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<strong>Hello.</strong>"); }); +}); - test('docx tab is converted to tab in HTML', function() { - var tab = new documents.Tab(); - var converter = new DocumentConverter(); - return converter.convertToHtml(tab).then(function(result) { - assert.equal(result.value, "\t"); - }); +test('run can be both bold and italic', function() { + var run = runOfText("Hello.", {isBold: true, isItalic: true}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<strong><em>Hello.</em></strong>"); }); - - test('docx table is converted to table in HTML', function() { - var table = new documents.Table([ - new documents.TableRow([ - new documents.TableCell([paragraphOfText("Top left")]), - new documents.TableCell([paragraphOfText("Top right")]) - ]), - new documents.TableRow([ - new documents.TableCell([paragraphOfText("Bottom left")]), - new documents.TableCell([paragraphOfText("Bottom right")]) - ]) - ]); - var converter = new DocumentConverter(); - - return converter.convertToHtml(table).then(function(result) { - var expectedHtml = "<table>" + - "<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" + - "<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" + - "</table>"; - assert.equal(result.value, expectedHtml); - }); +}); + +test('superscript runs are wrapped in <sup> tags', function() { + var run = runOfText("Hello.", { + verticalAlignment: documents.verticalAlignment.superscript }); - - test('empty cells are preserved in table', function() { - var table = new documents.Table([ - new documents.TableRow([ - new documents.TableCell([paragraphOfText("")]), - new documents.TableCell([paragraphOfText("Top right")]) - ]) - ]); - var converter = new DocumentConverter(); - - return converter.convertToHtml(table).then(function(result) { - var expectedHtml = "<table>" + - "<tr><td></td><td><p>Top right</p></td></tr>" + - "</table>"; - assert.equal(result.value, expectedHtml); - }); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<sup>Hello.</sup>"); }); - - test('table cells are written with colSpan if not equal to one', function() { - var table = new documents.Table([ - new documents.TableRow([ - new documents.TableCell([paragraphOfText("Top left")], {colSpan: 2}), - new documents.TableCell([paragraphOfText("Top right")]) - ]) - ]); - var converter = new DocumentConverter(); - - return converter.convertToHtml(table).then(function(result) { - var expectedHtml = "<table>" + - "<tr><td colspan=\"2\"><p>Top left</p></td><td><p>Top right</p></td></tr>" + - "</table>"; - assert.equal(result.value, expectedHtml); - }); +}); + +test('subscript runs are wrapped in <sub> tags', function() { + var run = runOfText("Hello.", { + verticalAlignment: documents.verticalAlignment.subscript }); - - test('table cells are written with rowSpan if not equal to one', function() { - var table = new documents.Table([ - new documents.TableRow([ - new documents.TableCell([], {rowSpan: 2}) - ]) - ]); - var converter = new DocumentConverter(); - - return converter.convertToHtml(table).then(function(result) { - var expectedHtml = "<table>" + - "<tr><td rowspan=\"2\"></td></tr>" + - "</table>"; - assert.equal(result.value, expectedHtml); - }); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<sub>Hello.</sub>"); + }); +}); + +test('run styles are converted to HTML if mapping exists', function() { + var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"}); + var converter = new DocumentConverter({ + styleMap: [ + { + from: documentMatchers.run({styleName: "Heading 1 Char"}), + to: htmlPaths.elements(["strong"]) + } + ] + }); + return converter.convertToHtml(run).then(function(result) { + assert.equal(result.value, "<strong>Hello.</strong>"); + }); +}); + +test('warning is emitted if run style is unrecognised', function() { + var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"}); + var converter = new DocumentConverter(); + return converter.convertToHtml(run).then(function(result) { + assert.deepEqual(result.messages, [results.warning("Unrecognised run style: 'Heading 1 Char' (Style ID: Heading1Char)")]); + }); +}); + +test('docx hyperlink is converted to <a>', function() { + var hyperlink = new documents.Hyperlink( + [runOfText("Hello.")], + {href: "http://www.example.com"} + ); + var converter = new DocumentConverter(); + return converter.convertToHtml(hyperlink).then(function(result) { + assert.equal(result.value, '<a href="http://www.example.com">Hello.</a>'); + }); +}); + +test('docx hyperlink with anchor is converted to <a>', function() { + var hyperlink = new documents.Hyperlink( + [runOfText("Hello.")], + {anchor: "_Peter"} + ); + var converter = new DocumentConverter({ + idPrefix: "doc-42-" + }); + return converter.convertToHtml(hyperlink).then(function(result) { + assert.equal(result.value, '<a href="#doc-42-_Peter">Hello.</a>'); + }); +}); + +test('bookmarks are converted to anchors', function() { + var bookmarkStart = new documents.BookmarkStart({name: "_Peter"}); + var converter = new DocumentConverter({ + idPrefix: "doc-42-" + }); + var document = new documents.Document([bookmarkStart]); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, '<a id="doc-42-_Peter"></a>'); + }); +}); + +test('docx tab is converted to tab in HTML', function() { + var tab = new documents.Tab(); + var converter = new DocumentConverter(); + return converter.convertToHtml(tab).then(function(result) { + assert.equal(result.value, "\t"); + }); +}); + +test('docx table is converted to table in HTML', function() { + var table = new documents.Table([ + new documents.TableRow([ + new documents.TableCell([paragraphOfText("Top left")]), + new documents.TableCell([paragraphOfText("Top right")]) + ]), + new documents.TableRow([ + new documents.TableCell([paragraphOfText("Bottom left")]), + new documents.TableCell([paragraphOfText("Bottom right")]) + ]) + ]); + var converter = new DocumentConverter(); + + return converter.convertToHtml(table).then(function(result) { + var expectedHtml = "<table>" + + "<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" + + "<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" + + "</table>"; + assert.equal(result.value, expectedHtml); + }); +}); + +test('empty cells are preserved in table', function() { + var table = new documents.Table([ + new documents.TableRow([ + new documents.TableCell([paragraphOfText("")]), + new documents.TableCell([paragraphOfText("Top right")]) + ]) + ]); + var converter = new DocumentConverter(); + + return converter.convertToHtml(table).then(function(result) { + var expectedHtml = "<table>" + + "<tr><td></td><td><p>Top right</p></td></tr>" + + "</table>"; + assert.equal(result.value, expectedHtml); + }); +}); + +test('table cells are written with colSpan if not equal to one', function() { + var table = new documents.Table([ + new documents.TableRow([ + new documents.TableCell([paragraphOfText("Top left")], {colSpan: 2}), + new documents.TableCell([paragraphOfText("Top right")]) + ]) + ]); + var converter = new DocumentConverter(); + + return converter.convertToHtml(table).then(function(result) { + var expectedHtml = "<table>" + + "<tr><td colspan=\"2\"><p>Top left</p></td><td><p>Top right</p></td></tr>" + + "</table>"; + assert.equal(result.value, expectedHtml); + }); +}); + +test('table cells are written with rowSpan if not equal to one', function() { + var table = new documents.Table([ + new documents.TableRow([ + new documents.TableCell([], {rowSpan: 2}) + ]) + ]); + var converter = new DocumentConverter(); + + return converter.convertToHtml(table).then(function(result) { + var expectedHtml = "<table>" + + "<tr><td rowspan=\"2\"></td></tr>" + + "</table>"; + assert.equal(result.value, expectedHtml); }); +}); + +test('line break is converted to <br>', function() { + var lineBreak = new documents.LineBreak(); + var converter = new DocumentConverter(); - test('line break is converted to <br>', function() { - var lineBreak = new documents.LineBreak(); - var converter = new DocumentConverter(); - - return converter.convertToHtml(lineBreak).then(function(result) { - assert.equal(result.value, "<br />"); - }); + return converter.convertToHtml(lineBreak).then(function(result) { + assert.equal(result.value, "<br />"); }); +}); + +test('footnote reference is converted to superscript intra-page link', function() { + var footnoteReference = new documents.NoteReference({ + noteType: "footnote", + noteId: "4" + }); + var converter = new DocumentConverter({ + idPrefix: "doc-42-" + }); + return converter.convertToHtml(footnoteReference).then(function(result) { + assert.equal(result.value, '<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup>'); + }); +}); + +test('footnotes are included after the main body', function() { + var footnoteReference = new documents.NoteReference({ + noteType: "footnote", + noteId: "4" + }); + var document = new documents.Document( + [new documents.Paragraph([ + runOfText("Knock knock"), + new documents.Run([footnoteReference]) + ])], + { + notes: new documents.Notes({ + 4: new documents.Note({ + noteType: "footnote", + noteId: "4", + body: [paragraphOfText("Who's there?")] + }) + }) + } + ); - test('footnote reference is converted to superscript intra-page link', function() { - var footnoteReference = new documents.NoteReference({ - noteType: "footnote", - noteId: "4" - }); - var converter = new DocumentConverter({ - idPrefix: "doc-42-" - }); - return converter.convertToHtml(footnoteReference).then(function(result) { - assert.equal(result.value, '<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup>'); - }); + var converter = new DocumentConverter({ + idPrefix: "doc-42-" + }); + return converter.convertToHtml(document).then(function(result) { + var expectedOutput = '<p>Knock knock<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup></p>' + + '<ol><li id="doc-42-footnote-4"><p>Who\'s there? <a href="#doc-42-footnote-ref-4">↑</a></p></li></ol>'; + assert.equal(result.value, expectedOutput); + }); +}); + +test('comments are ignored by default', function() { + var reference = documents.commentReference({commentId: "4"}); + var comment = documents.comment({ + commentId: "4", + body: [paragraphOfText("Who's there?")] + }); + var document = documents.document([ + documents.paragraph([ + runOfText("Knock knock"), + documents.run([reference]) + ]) + ], {comments: [comment]}); + + var converter = new DocumentConverter({}); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value, '<p>Knock knock</p>'); + assert.deepEqual(result.messages, []); }); +}); - test('footnotes are included after the main body', function() { - var footnoteReference = new documents.NoteReference({ - noteType: "footnote", - noteId: "4" - }); - var document = new documents.Document( - [new documents.Paragraph([ - runOfText("Knock knock"), - new documents.Run([footnoteReference]) - ])], - { - notes: new documents.Notes({ - 4: new documents.Note({ - noteType: "footnote", - noteId: "4", - body: [paragraphOfText("Who's there?")] - }) - }) - } +test('comment references are linked to comment after main body', function() { + var reference = documents.commentReference({commentId: "4"}); + var comment = documents.comment({ + commentId: "4", + body: [paragraphOfText("Who's there?")], + authorName: "The Piemaker", + authorInitials: "TP" + }); + var document = documents.document([ + documents.paragraph([ + runOfText("Knock knock"), + documents.run([reference]) + ]) + ], {comments: [comment]}); + + var converter = new DocumentConverter({ + idPrefix: "doc-42-", + styleMap: [ + {from: documentMatchers.commentReference, to: htmlPaths.element("sup")} + ] + }); + return converter.convertToHtml(document).then(function(result) { + var expectedHtml = ( + '<p>Knock knock<sup><a href="#doc-42-comment-4" id="doc-42-comment-ref-4">[TP1]</a></sup></p>' + + '<dl><dt id="doc-42-comment-4">Comment [TP1]</dt><dd><p>Who\'s there? <a href="#doc-42-comment-ref-4">↑</a></p></dd></dl>' ); - - var converter = new DocumentConverter({ - idPrefix: "doc-42-" - }); - return converter.convertToHtml(document).then(function(result) { - var expectedOutput = '<p>Knock knock<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup></p>' + - '<ol><li id="doc-42-footnote-4"><p>Who\'s there? <a href="#doc-42-footnote-ref-4">↑</a></p></li></ol>'; - assert.equal(result.value, expectedOutput); - }); + assert.equal(result.value, expectedHtml); + assert.deepEqual(result.messages, []); }); - - test('images are written with data URIs', function() { - var imageBuffer = new Buffer("Not an image at all!"); - var image = new documents.Image({ - readImage: function(encoding) { - return promises.when(imageBuffer.toString(encoding)); - }, - contentType: "image/png" - }); - var converter = new DocumentConverter(); - return converter.convertToHtml(image).then(function(result) { - assert.equal(result.value, '<img src="data:image/png;base64,' + imageBuffer.toString("base64") + '" />'); - }); +}); + +test('images are written with data URIs', function() { + var imageBuffer = new Buffer("Not an image at all!"); + var image = new documents.Image({ + readImage: function(encoding) { + return promises.when(imageBuffer.toString(encoding)); + }, + contentType: "image/png" + }); + var converter = new DocumentConverter(); + return converter.convertToHtml(image).then(function(result) { + assert.equal(result.value, '<img src="data:image/png;base64,' + imageBuffer.toString("base64") + '" />'); }); - - test('images have alt attribute if available', function() { - var imageBuffer = new Buffer("Not an image at all!"); - var image = new documents.Image({ - readImage: function() { - return promises.when(imageBuffer); - }, - altText: "It's a hat" +}); + +test('images have alt attribute if available', function() { + var imageBuffer = new Buffer("Not an image at all!"); + var image = new documents.Image({ + readImage: function() { + return promises.when(imageBuffer); + }, + altText: "It's a hat" + }); + var converter = new DocumentConverter(); + return converter.convertToHtml(image) + .then(function(result) { + return xml.readString(result.value); + }) + .then(function(htmlImageElement) { + assert.equal(htmlImageElement.attributes.alt, "It's a hat"); }); - var converter = new DocumentConverter(); - return converter.convertToHtml(image) - .then(function(result) { - return xml.readString(result.value); - }) - .then(function(htmlImageElement) { - assert.equal(htmlImageElement.attributes.alt, "It's a hat"); +}); + +test('can add custom handler for images', function() { + var imageBuffer = new Buffer("Not an image at all!"); + var image = new documents.Image({ + readImage: function(encoding) { + return promises.when(imageBuffer.toString(encoding)); + }, + contentType: "image/png" + }); + var converter = new DocumentConverter({ + convertImage: function(element, messages) { + return element.read("utf8").then(function(altText) { + return [Html.selfClosingElement("img", {alt: altText})]; }); + } }); - - test('can add custom handler for images', function() { - var imageBuffer = new Buffer("Not an image at all!"); - var image = new documents.Image({ - readImage: function(encoding) { - return promises.when(imageBuffer.toString(encoding)); - }, - contentType: "image/png" - }); - var converter = new DocumentConverter({ - convertImage: function(element, messages) { - return element.read("utf8").then(function(altText) { - return [Html.selfClosingElement("img", {alt: altText})]; - }); - } - }); - return converter.convertToHtml(image).then(function(result) { - assert.equal(result.value, '<img alt="Not an image at all!" />'); - }); + return converter.convertToHtml(image).then(function(result) { + assert.equal(result.value, '<img alt="Not an image at all!" />'); }); - - test('long documents do not cause stack overflow', function() { - var paragraphs = []; - for (var i = 0; i < 1000; i++) { - paragraphs.push(paragraphOfText("Hello.")); - } - var document = new documents.Document(paragraphs); - var converter = new DocumentConverter(); - return converter.convertToHtml(document).then(function(result) { - assert.equal(result.value.indexOf("<p>Hello.</p>"), 0); - }); +}); + +test('long documents do not cause stack overflow', function() { + var paragraphs = []; + for (var i = 0; i < 1000; i++) { + paragraphs.push(paragraphOfText("Hello.")); + } + var document = new documents.Document(paragraphs); + var converter = new DocumentConverter(); + return converter.convertToHtml(document).then(function(result) { + assert.equal(result.value.indexOf("<p>Hello.</p>"), 0); }); }); @@ -563,3 +614,13 @@ function runOfText(text, properties) { var textElement = new documents.Text(text); return new documents.Run([textElement], properties); } + +test('when initials are not blank then comment author label is initials', function() { + assert.equal(commentAuthorLabel({authorInitials: "TP"}), "TP") +}); + +test('when initials are blank then comment author label is blank', function() { + assert.equal(commentAuthorLabel({authorInitials: ""}), "") + assert.equal(commentAuthorLabel({authorInitials: undefined}), "") + assert.equal(commentAuthorLabel({authorInitials: null}), "") +}); diff --git a/test/docx/body-reader.tests.js b/test/docx/body-reader.tests.js index 24c2d8d..34463f7 100644 --- a/test/docx/body-reader.tests.js +++ b/test/docx/body-reader.tests.js @@ -10,7 +10,7 @@ var Styles = require("../../lib/docx/styles-reader").Styles; var warning = require("../../lib/results").warning; var testing = require("../testing"); -var test = testing.test; +var test = require("../test")(module); var createFakeDocxFile = testing.createFakeDocxFile; function readXmlElement(element, options) { @@ -35,664 +35,662 @@ var fakeContentTypes = { } }; -describe("readXmlElement: ", function() { - test("paragraph has no style if it has no properties", function() { - var paragraphXml = new XmlElement("w:p", {}, []); - var paragraph = readXmlElementValue(paragraphXml); - assert.deepEqual(paragraph.styleId, null); - }); +test("paragraph has no style if it has no properties", function() { + var paragraphXml = new XmlElement("w:p", {}, []); + var paragraph = readXmlElementValue(paragraphXml); + assert.deepEqual(paragraph.styleId, null); +}); + +test("paragraph has style ID and name read from paragraph properties if present", function() { + var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []); + var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]); + var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - test("paragraph has style ID and name read from paragraph properties if present", function() { - var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []); - var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]); - var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - - var styles = new Styles({"Heading1": {name: "Heading 1"}}, {}); - - var paragraph = readXmlElementValue(paragraphXml, {styles: styles}); - assert.deepEqual(paragraph.styleId, "Heading1"); - assert.deepEqual(paragraph.styleName, "Heading 1"); - }); + var styles = new Styles({"Heading1": {name: "Heading 1"}}, {}); - test("warning is emitted when paragraph style cannot be found", function() { - var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []); - var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]); - var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - - var styles = new Styles({}, {}); - - var result = readXmlElement(paragraphXml, {styles: styles}); - var paragraph = result.value; - assert.deepEqual(paragraph.styleId, "Heading1"); - assert.deepEqual(paragraph.styleName, null); - assert.deepEqual(result.messages, [warning("Paragraph style with ID Heading1 was referenced but not defined in the document")]); - }); + var paragraph = readXmlElementValue(paragraphXml, {styles: styles}); + assert.deepEqual(paragraph.styleId, "Heading1"); + assert.deepEqual(paragraph.styleName, "Heading 1"); +}); + +test("warning is emitted when paragraph style cannot be found", function() { + var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []); + var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]); + var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); + + var styles = new Styles({}, {}); + + var result = readXmlElement(paragraphXml, {styles: styles}); + var paragraph = result.value; + assert.deepEqual(paragraph.styleId, "Heading1"); + assert.deepEqual(paragraph.styleName, null); + assert.deepEqual(result.messages, [warning("Paragraph style with ID Heading1 was referenced but not defined in the document")]); +}); + +test("paragraph has justification read from paragraph properties if present", function() { + var justificationXml = new XmlElement("w:jc", {"w:val": "center"}, []); + var propertiesXml = new XmlElement("w:pPr", {}, [justificationXml]); + var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); + var paragraph = readXmlElementValue(paragraphXml); + assert.deepEqual(paragraph.alignment, "center"); +}); + +test("paragraph has numbering properties from paragraph properties if present", function() { + var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ + new XmlElement("w:ilvl", {"w:val": "1"}), + new XmlElement("w:numId", {"w:val": "42"}) + ]); + var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]); + var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - test("paragraph has justification read from paragraph properties if present", function() { - var justificationXml = new XmlElement("w:jc", {"w:val": "center"}, []); - var propertiesXml = new XmlElement("w:pPr", {}, [justificationXml]); - var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - var paragraph = readXmlElementValue(paragraphXml); - assert.deepEqual(paragraph.alignment, "center"); - }); + var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - test("paragraph has numbering properties from paragraph properties if present", function() { - var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ - new XmlElement("w:ilvl", {"w:val": "1"}), - new XmlElement("w:numId", {"w:val": "42"}) - ]); - var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]); - var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]); - - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - - var paragraph = readXmlElementValue(paragraphXml, {numbering: numbering}); - assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true}); - }); + var paragraph = readXmlElementValue(paragraphXml, {numbering: numbering}); + assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true}); +}); + +test("numbering properties are converted to numbering at specified level", function() { + var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ + new XmlElement("w:ilvl", {"w:val": "1"}), + new XmlElement("w:numId", {"w:val": "42"}) + ]); - test("numbering properties are converted to numbering at specified level", function() { - var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ - new XmlElement("w:ilvl", {"w:val": "1"}), - new XmlElement("w:numId", {"w:val": "42"}) - ]); - - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); - assert.deepEqual(numberingLevel, {level: "1", isOrdered: true}); - }); + var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - test("numbering properties are ignored if w:ilvl is missing", function() { - var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ - new XmlElement("w:numId", {"w:val": "42"}) - ]); - - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); - assert.equal(numberingLevel, null); + var reader = new BodyReader({ + numbering: numbering }); + var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + assert.deepEqual(numberingLevel, {level: "1", isOrdered: true}); +}); + +test("numbering properties are ignored if w:ilvl is missing", function() { + var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ + new XmlElement("w:numId", {"w:val": "42"}) + ]); - test("numbering properties are ignored if w:numId is missing", function() { - var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ - new XmlElement("w:ilvl", {"w:val": "1"}) - ]); - - var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - - var reader = new BodyReader({ - numbering: numbering - }); - var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); - assert.equal(numberingLevel, null); - }); + var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - test("run has no style if it has no properties", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.styleId, null); + var reader = new BodyReader({ + numbering: numbering }); + var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + assert.equal(numberingLevel, null); +}); + +test("numbering properties are ignored if w:numId is missing", function() { + var numberingPropertiesXml = new XmlElement("w:numPr", {}, [ + new XmlElement("w:ilvl", {"w:val": "1"}) + ]); - test("run has style ID and name read from run properties if present", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"}); - var runXml = runWithProperties([runStyleXml]); - - var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}}); - - var run = readXmlElementValue(runXml, {styles: styles}); - assert.deepEqual(run.styleId, "Heading1Char"); - assert.deepEqual(run.styleName, "Heading 1 Char"); - }); + var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}}); - test("warning is emitted when run style cannot be found", function() { - var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"}); - var runXml = runWithProperties([runStyleXml]); - - var styles = new Styles({}, {}); - - var result = readXmlElement(runXml, {styles: styles}); - var run = result.value; - assert.deepEqual(run.styleId, "Heading1Char"); - assert.deepEqual(run.styleName, null); - assert.deepEqual(result.messages, [warning("Run style with ID Heading1Char was referenced but not defined in the document")]); + var reader = new BodyReader({ + numbering: numbering }); + var numberingLevel = reader._readNumberingProperties(numberingPropertiesXml); + assert.equal(numberingLevel, null); +}); + +test("run has no style if it has no properties", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.styleId, null); +}); + +test("run has style ID and name read from run properties if present", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"}); + var runXml = runWithProperties([runStyleXml]); - test("isBold is false if bold element is not present", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.isBold, false); - }); + var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}}); - test("isBold is true if bold element is present", function() { - var boldXml = new XmlElement("w:b"); - var runXml = runWithProperties([boldXml]); - var run = readXmlElementValue(runXml); - assert.equal(run.isBold, true); - }); + var run = readXmlElementValue(runXml, {styles: styles}); + assert.deepEqual(run.styleId, "Heading1Char"); + assert.deepEqual(run.styleName, "Heading 1 Char"); +}); - test("isUnderline is false if underline element is not present", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.isUnderline, false); - }); +test("warning is emitted when run style cannot be found", function() { + var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"}); + var runXml = runWithProperties([runStyleXml]); + + var styles = new Styles({}, {}); + + var result = readXmlElement(runXml, {styles: styles}); + var run = result.value; + assert.deepEqual(run.styleId, "Heading1Char"); + assert.deepEqual(run.styleName, null); + assert.deepEqual(result.messages, [warning("Run style with ID Heading1Char was referenced but not defined in the document")]); +}); - test("isUnderline is true if underline element is present", function() { - var underlineXml = new XmlElement("w:u"); - var runXml = runWithProperties([underlineXml]); - var run = readXmlElementValue(runXml); - assert.equal(run.isUnderline, true); - }); +test("isBold is false if bold element is not present", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.isBold, false); +}); - test("isStrikethrough is false if strikethrough element is not present", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.isStrikethrough, false); - }); +test("isBold is true if bold element is present", function() { + var boldXml = new XmlElement("w:b"); + var runXml = runWithProperties([boldXml]); + var run = readXmlElementValue(runXml); + assert.equal(run.isBold, true); +}); - test("isStrikethrough is true if strikethrough element is present", function() { - var strikethroughXml = new XmlElement("w:strike"); - var runXml = runWithProperties([strikethroughXml]); - var run = readXmlElementValue(runXml); - assert.equal(run.isStrikethrough, true); - }); +test("isUnderline is false if underline element is not present", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.isUnderline, false); +}); - test("isItalic is false if bold element is not present", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.isItalic, false); - }); - - test("isItalic is true if bold element is present", function() { - var italicXml = new XmlElement("w:i"); - var runXml = runWithProperties([italicXml]); - var run = readXmlElementValue(runXml); - assert.equal(run.isItalic, true); - }); - - test("run has baseline vertical alignment by default", function() { - var runXml = runWithProperties([]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.baseline); - }); - - test("run has vertical alignment read from properties", function() { - var verticalAlignmentXml = new XmlElement("w:vertAlign", {"w:val": "superscript"}); - var runXml = runWithProperties([verticalAlignmentXml]); - - var run = readXmlElementValue(runXml); - assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.superscript); - }); - - test("run properties not included as child of run", function() { - var runStyleXml = new XmlElement("w:rStyle"); - var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); - var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); - var result = readXmlElement(runXml); - assert.deepEqual(result.value.children, []); - }); - - test("w:tab is read as document tab element", function() { - var tabXml = new XmlElement("w:tab"); - var result = readXmlElement(tabXml); - assert.deepEqual(result.value, new documents.Tab()); - }); +test("isUnderline is true if underline element is present", function() { + var underlineXml = new XmlElement("w:u"); + var runXml = runWithProperties([underlineXml]); + var run = readXmlElementValue(runXml); + assert.equal(run.isUnderline, true); +}); + +test("isStrikethrough is false if strikethrough element is not present", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.isStrikethrough, false); +}); + +test("isStrikethrough is true if strikethrough element is present", function() { + var strikethroughXml = new XmlElement("w:strike"); + var runXml = runWithProperties([strikethroughXml]); + var run = readXmlElementValue(runXml); + assert.equal(run.isStrikethrough, true); +}); + +test("isItalic is false if bold element is not present", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.isItalic, false); +}); + +test("isItalic is true if bold element is present", function() { + var italicXml = new XmlElement("w:i"); + var runXml = runWithProperties([italicXml]); + var run = readXmlElementValue(runXml); + assert.equal(run.isItalic, true); +}); + +test("run has baseline vertical alignment by default", function() { + var runXml = runWithProperties([]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.baseline); +}); + +test("run has vertical alignment read from properties", function() { + var verticalAlignmentXml = new XmlElement("w:vertAlign", {"w:val": "superscript"}); + var runXml = runWithProperties([verticalAlignmentXml]); - test("w:tbl is read as document table element", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - new XmlElement("w:tr", {}, [ - new XmlElement("w:tc", {}, [ - new XmlElement("w:p", {}, []) - ]) - ]) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - new documents.TableRow([ - new documents.TableCell([ - new documents.Paragraph([]) - ]) + var run = readXmlElementValue(runXml); + assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.superscript); +}); + +test("run properties not included as child of run", function() { + var runStyleXml = new XmlElement("w:rStyle"); + var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]); + var runXml = new XmlElement("w:r", {}, [runPropertiesXml]); + var result = readXmlElement(runXml); + assert.deepEqual(result.value.children, []); +}); + +test("w:tab is read as document tab element", function() { + var tabXml = new XmlElement("w:tab"); + var result = readXmlElement(tabXml); + assert.deepEqual(result.value, new documents.Tab()); +}); + +test("w:tbl is read as document table element", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + new XmlElement("w:tr", {}, [ + new XmlElement("w:tc", {}, [ + new XmlElement("w:p", {}, []) ]) - ])); - }); - - test("w:gridSpan is read as colSpan for table cell", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - new XmlElement("w:tr", {}, [ - new XmlElement("w:tc", {}, [ - new XmlElement("w:tcPr", {}, [ - new XmlElement("w:gridSpan", {"w:val": "2"}) - ]), - new XmlElement("w:p", {}, []) - ]) + ]) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + new documents.TableRow([ + new documents.TableCell([ + new documents.Paragraph([]) ]) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - new documents.TableRow([ - new documents.TableCell([ - new documents.Paragraph([]) - ], {colSpan: 2}) + ]) + ])); +}); + +test("w:gridSpan is read as colSpan for table cell", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + new XmlElement("w:tr", {}, [ + new XmlElement("w:tc", {}, [ + new XmlElement("w:tcPr", {}, [ + new XmlElement("w:gridSpan", {"w:val": "2"}) + ]), + new XmlElement("w:p", {}, []) ]) - ])); - }); - - test("w:vMerge is read as rowSpan for table cell", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - row(emptyCell()), - row(emptyCell(vMerge("restart"))), - row(emptyCell(vMerge("continue"))), - row(emptyCell(vMerge("continue"))), - row(emptyCell()) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - docRow([docEmptyCell()]), - docRow([docEmptyCell({rowSpan: 3})]), - docRow([]), - docRow([]), - docRow([docEmptyCell()]) - ])); - }); - - test("w:vMerge without val is treated as continue", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - row(emptyCell(vMerge("restart"))), - row(emptyCell(vMerge())) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - docRow([docEmptyCell({rowSpan: 2})]), - docRow([]) - ])); - }); - - test("w:vMerge accounts for cells spanning columns", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - row(emptyCell(), emptyCell(), emptyCell(vMerge("restart"))), - row(emptyCell(gridSpan("2")), emptyCell(vMerge("continue"))), - row(emptyCell(), emptyCell(), emptyCell(vMerge("continue"))), - row(emptyCell(), emptyCell(), emptyCell()) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - docRow([docEmptyCell(), docEmptyCell(), docEmptyCell({rowSpan: 3})]), - docRow([docEmptyCell({colSpan: 2})]), - docRow([docEmptyCell(), docEmptyCell()]), - docRow([docEmptyCell(), docEmptyCell(), docEmptyCell()]) - ])); - }); - - test("no vertical cell merging if merged cells do not line up", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - row(emptyCell(gridSpan("2"), vMerge("restart"))), - row(emptyCell(), emptyCell(vMerge("continue"))) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.value, new documents.Table([ - docRow([docEmptyCell({colSpan: 2})]), - docRow([docEmptyCell(), docEmptyCell()]) - ])); - }); - - test("warning if non-row in table", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - new XmlElement("w:p") - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.messages, [warning("unexpected non-row element in table, cell merging may be incorrect")]); - }); - - test("warning if non-cell in table row", function() { - var tableXml = new XmlElement("w:tbl", {}, [ - row(new XmlElement("w:p")) - ]); - var result = readXmlElement(tableXml); - assert.deepEqual(result.messages, [warning("unexpected non-cell element in table row, cell merging may be incorrect")]); - }); - - function row() { - return new XmlElement("w:tr", {}, Array.prototype.slice.call(arguments)); - } - - function emptyCell() { - return new XmlElement("w:tc", {}, [ - new XmlElement("w:tcPr", {}, Array.prototype.slice.call(arguments)) - ]); - } - - function vMerge(val) { - return new XmlElement("w:vMerge", {"w:val": val}, []); - } - - function gridSpan(val) { - return new XmlElement("w:gridSpan", {"w:val": val}); - } - - function docRow(children) { - return new documents.TableRow(children); - } - - function docEmptyCell(properties) { - return new documents.TableCell([], properties); - } + ]) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + new documents.TableRow([ + new documents.TableCell([ + new documents.Paragraph([]) + ], {colSpan: 2}) + ]) + ])); +}); - test("w:bookmarkStart is read as a bookmarkStart", function() { - var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_Peter", "w:id": "42"}); - var result = readXmlElement(bookmarkStart); - assert.deepEqual(result.value.name, "_Peter"); - assert.deepEqual(result.value.type, "bookmarkStart"); - }); - - test('_GoBack bookmark is ignored', function() { - var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_GoBack"}); - var result = readXmlElement(bookmarkStart); - assert.deepEqual(result.value, []); - }); - - test("can read imagedata elements with r:id attribute", function() { - var imagedataElement = new XmlElement("v:imagedata", {"r:id": "rId5", "o:title": "It's a hat"}); - - var imageBuffer = new Buffer("Not an image at all!"); - var reader = new BodyReader({ - relationships: { - "rId5": {target: "media/hat.png"} - }, - contentTypes: fakeContentTypes, - docxFile: createFakeDocxFile({ - "word/media/hat.png": imageBuffer - }) - }); - var result = reader.readXmlElement(imagedataElement); - assert.deepEqual(result.messages, []); - var element = result.value; - assert.equal("image", element.type); - assert.equal(element.altText, "It's a hat"); - assert.equal(element.contentType, "image/png"); - return element.read() - .then(function(readValue) { - assert.equal(readValue, imageBuffer); - }); - }); - - test("can read inline pictures", function() { - var drawing = createInlineImage({ - blip: createEmbeddedBlip("rId5"), - description: "It's a hat" +test("w:vMerge is read as rowSpan for table cell", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row(emptyCell()), + row(emptyCell(vMerge("restart"))), + row(emptyCell(vMerge("continue"))), + row(emptyCell(vMerge("continue"))), + row(emptyCell()) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + docRow([docEmptyCell()]), + docRow([docEmptyCell({rowSpan: 3})]), + docRow([]), + docRow([]), + docRow([docEmptyCell()]) + ])); +}); + +test("w:vMerge without val is treated as continue", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row(emptyCell(vMerge("restart"))), + row(emptyCell(vMerge())) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + docRow([docEmptyCell({rowSpan: 2})]), + docRow([]) + ])); +}); + +test("w:vMerge accounts for cells spanning columns", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row(emptyCell(), emptyCell(), emptyCell(vMerge("restart"))), + row(emptyCell(gridSpan("2")), emptyCell(vMerge("continue"))), + row(emptyCell(), emptyCell(), emptyCell(vMerge("continue"))), + row(emptyCell(), emptyCell(), emptyCell()) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + docRow([docEmptyCell(), docEmptyCell(), docEmptyCell({rowSpan: 3})]), + docRow([docEmptyCell({colSpan: 2})]), + docRow([docEmptyCell(), docEmptyCell()]), + docRow([docEmptyCell(), docEmptyCell(), docEmptyCell()]) + ])); +}); + +test("no vertical cell merging if merged cells do not line up", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row(emptyCell(gridSpan("2"), vMerge("restart"))), + row(emptyCell(), emptyCell(vMerge("continue"))) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.value, new documents.Table([ + docRow([docEmptyCell({colSpan: 2})]), + docRow([docEmptyCell(), docEmptyCell()]) + ])); +}); + +test("warning if non-row in table", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + new XmlElement("w:p") + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.messages, [warning("unexpected non-row element in table, cell merging may be incorrect")]); +}); + +test("warning if non-cell in table row", function() { + var tableXml = new XmlElement("w:tbl", {}, [ + row(new XmlElement("w:p")) + ]); + var result = readXmlElement(tableXml); + assert.deepEqual(result.messages, [warning("unexpected non-cell element in table row, cell merging may be incorrect")]); +}); + +function row() { + return new XmlElement("w:tr", {}, Array.prototype.slice.call(arguments)); +} + +function emptyCell() { + return new XmlElement("w:tc", {}, [ + new XmlElement("w:tcPr", {}, Array.prototype.slice.call(arguments)) + ]); +} + +function vMerge(val) { + return new XmlElement("w:vMerge", {"w:val": val}, []); +} + +function gridSpan(val) { + return new XmlElement("w:gridSpan", {"w:val": val}); +} + +function docRow(children) { + return new documents.TableRow(children); +} + +function docEmptyCell(properties) { + return new documents.TableCell([], properties); +} + +test("w:bookmarkStart is read as a bookmarkStart", function() { + var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_Peter", "w:id": "42"}); + var result = readXmlElement(bookmarkStart); + assert.deepEqual(result.value.name, "_Peter"); + assert.deepEqual(result.value.type, "bookmarkStart"); +}); + +test('_GoBack bookmark is ignored', function() { + var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_GoBack"}); + var result = readXmlElement(bookmarkStart); + assert.deepEqual(result.value, []); +}); + +test("can read imagedata elements with r:id attribute", function() { + var imagedataElement = new XmlElement("v:imagedata", {"r:id": "rId5", "o:title": "It's a hat"}); + + var imageBuffer = new Buffer("Not an image at all!"); + var reader = new BodyReader({ + relationships: { + "rId5": {target: "media/hat.png"} + }, + contentTypes: fakeContentTypes, + docxFile: createFakeDocxFile({ + "word/media/hat.png": imageBuffer + }) + }); + var result = reader.readXmlElement(imagedataElement); + assert.deepEqual(result.messages, []); + var element = result.value; + assert.equal("image", element.type); + assert.equal(element.altText, "It's a hat"); + assert.equal(element.contentType, "image/png"); + return element.read() + .then(function(readValue) { + assert.equal(readValue, imageBuffer); }); - - var imageBuffer = new Buffer("Not an image at all!"); - var reader = new BodyReader({ - relationships: { - "rId5": {target: "media/hat.png"} - }, - contentTypes: fakeContentTypes, - docxFile: createFakeDocxFile({ - "word/media/hat.png": imageBuffer - }) +}); + +test("can read inline pictures", function() { + var drawing = createInlineImage({ + blip: createEmbeddedBlip("rId5"), + description: "It's a hat" + }); + + var imageBuffer = new Buffer("Not an image at all!"); + var reader = new BodyReader({ + relationships: { + "rId5": {target: "media/hat.png"} + }, + contentTypes: fakeContentTypes, + docxFile: createFakeDocxFile({ + "word/media/hat.png": imageBuffer + }) + }); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + var element = single(result.value); + assert.equal("image", element.type); + assert.equal(element.altText, "It's a hat"); + assert.equal(element.contentType, "image/png"); + return element.read() + .then(function(readValue) { + assert.equal(readValue, imageBuffer); }); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - var element = single(result.value); - assert.equal("image", element.type); - assert.equal(element.altText, "It's a hat"); - assert.equal(element.contentType, "image/png"); - return element.read() - .then(function(readValue) { - assert.equal(readValue, imageBuffer); - }); - }); - - test("can read anchored pictures", function() { - var drawing = new XmlElement("w:drawing", {}, [ - new XmlElement("wp:anchor", {}, [ - new XmlElement("wp:docPr", {descr: "It's a hat"}), - new XmlElement("a:graphic", {}, [ - new XmlElement("a:graphicData", {}, [ - new XmlElement("pic:pic", {}, [ - new XmlElement("pic:blipFill", {}, [ - new XmlElement("a:blip", {"r:embed": "rId5"}) - ]) +}); + +test("can read anchored pictures", function() { + var drawing = new XmlElement("w:drawing", {}, [ + new XmlElement("wp:anchor", {}, [ + new XmlElement("wp:docPr", {descr: "It's a hat"}), + new XmlElement("a:graphic", {}, [ + new XmlElement("a:graphicData", {}, [ + new XmlElement("pic:pic", {}, [ + new XmlElement("pic:blipFill", {}, [ + new XmlElement("a:blip", {"r:embed": "rId5"}) ]) ]) ]) ]) - ]); - - var imageBuffer = new Buffer("Not an image at all!"); - var reader = new BodyReader({ - relationships: { - "rId5": {target: "media/hat.png"} - }, - contentTypes: fakeContentTypes, - docxFile: createFakeDocxFile({ - "word/media/hat.png": imageBuffer - }) - }); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - var element = single(result.value); - assert.equal("image", element.type); - assert.equal(element.altText, "It's a hat"); - return element.read() - .then(function(readValue) { - assert.equal(readValue, imageBuffer); - }); - }); - - test("can read linked pictures", function() { - var drawing = createInlineImage({ - blip: createLinkedBlip("rId5"), - description: "It's a hat" - }); - - var imageBuffer = new Buffer("Not an image at all!"); - var reader = new BodyReader({ - relationships: { - "rId5": {target: "file:///media/hat.png"} - }, - contentTypes: fakeContentTypes, - files: testing.createFakeFiles({ - "file:///media/hat.png": imageBuffer - }) - }); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - var element = single(result.value); - assert.equal("image", element.type); - assert.equal(element.altText, "It's a hat"); - assert.equal(element.contentType, "image/png"); - return element.read() - .then(function(readValue) { - assert.equal(readValue, imageBuffer); - }); - }); + ]) + ]); - test("warning if unsupported image type", function() { - var drawing = createInlineImage({ - blip: createEmbeddedBlip("rId5"), - description: "It's a hat" + var imageBuffer = new Buffer("Not an image at all!"); + var reader = new BodyReader({ + relationships: { + "rId5": {target: "media/hat.png"} + }, + contentTypes: fakeContentTypes, + docxFile: createFakeDocxFile({ + "word/media/hat.png": imageBuffer + }) + }); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + var element = single(result.value); + assert.equal("image", element.type); + assert.equal(element.altText, "It's a hat"); + return element.read() + .then(function(readValue) { + assert.equal(readValue, imageBuffer); }); - - var imageBuffer = new Buffer("Not an image at all!"); - var reader = new BodyReader({ - relationships: { - "rId5": {target: "media/hat.emf"} - }, - contentTypes: fakeContentTypes, - docxFile: createFakeDocxFile({ - "word/media/hat.emf": imageBuffer - }) +}); + +test("can read linked pictures", function() { + var drawing = createInlineImage({ + blip: createLinkedBlip("rId5"), + description: "It's a hat" + }); + + var imageBuffer = new Buffer("Not an image at all!"); + var reader = new BodyReader({ + relationships: { + "rId5": {target: "file:///media/hat.png"} + }, + contentTypes: fakeContentTypes, + files: testing.createFakeFiles({ + "file:///media/hat.png": imageBuffer + }) + }); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + var element = single(result.value); + assert.equal("image", element.type); + assert.equal(element.altText, "It's a hat"); + assert.equal(element.contentType, "image/png"); + return element.read() + .then(function(readValue) { + assert.equal(readValue, imageBuffer); }); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, [warning("Image of type image/x-emf is unlikely to display in web browsers")]); - var element = single(result.value); - assert.equal(element.contentType, "image/x-emf"); - }); - - test("no elements created if image cannot be found in w:drawing", function() { - var drawing = new XmlElement("w:drawing", {}, []); - - var reader = new BodyReader({}); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - assert.deepEqual(result.value, []); - }); - - test("no elements created if image cannot be found in wp:inline", function() { - var drawing = new XmlElement("wp:inline", {}, []); - - var reader = new BodyReader({}); - var result = reader.readXmlElement(drawing); - assert.deepEqual(result.messages, []); - assert.deepEqual(result.value, []); - }); - - test("children of w:ins are converted normally", function() { - var runXml = new XmlElement("w:r", {}, []); - var insXml = new XmlElement("w:ins", {}, [runXml]); - var result = readXmlElement(insXml); - assert.deepEqual(result.value[0].type, "run"); - }); - - test("children of w:smartTag are converted normally", function() { - var runXml = new XmlElement("w:r", {}, []); - var smartTagXml = new XmlElement("w:smartTag", {}, [runXml]); - var result = readXmlElement(smartTagXml); - assert.deepEqual(result.value[0].type, "run"); - }); - - test("w:hyperlink is read as document hyperlink if it has a relationship ID", function() { - var runXml = new XmlElement("w:r", {}, []); - var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42"}, [runXml]); - var relationships = { - "r42": {target: "http://example.com"} - }; - var result = readXmlElement(hyperlinkXml, {relationships: relationships}); - assert.deepEqual(result.value.href, "http://example.com"); - assert.deepEqual(result.value.children[0].type, "run"); - }); +}); - test("w:hyperlink is read as document hyperlink if it has an anchor", function() { - var runXml = new XmlElement("w:r", {}, []); - var hyperlinkXml = new XmlElement("w:hyperlink", {"w:anchor": "_Peter"}, [runXml]); - var result = readXmlElement(hyperlinkXml); - assert.deepEqual(result.value.anchor, "_Peter"); - assert.deepEqual(result.value.children[0].type, "run"); - }); +test("warning if unsupported image type", function() { + var drawing = createInlineImage({ + blip: createEmbeddedBlip("rId5"), + description: "It's a hat" + }); + + var imageBuffer = new Buffer("Not an image at all!"); + var reader = new BodyReader({ + relationships: { + "rId5": {target: "media/hat.emf"} + }, + contentTypes: fakeContentTypes, + docxFile: createFakeDocxFile({ + "word/media/hat.emf": imageBuffer + }) + }); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, [warning("Image of type image/x-emf is unlikely to display in web browsers")]); + var element = single(result.value); + assert.equal(element.contentType, "image/x-emf"); +}); - test("w:hyperlink is ignored if it does not have a relationship ID nor anchor", function() { - var runXml = new XmlElement("w:r", {}, []); - var hyperlinkXml = new XmlElement("w:hyperlink", {}, [runXml]); - var result = readXmlElement(hyperlinkXml); - assert.deepEqual(result.value[0].type, "run"); - }); - - test("w:br is read as line break", function() { - var breakXml = new XmlElement("w:br", {}, []); - var result = readXmlElement(breakXml); - assert.deepEqual(result.value.type, "lineBreak"); - assert.deepEqual(result.messages, []); - }); - - test("warning on breaks that aren't line breaks", function() { - var breakXml = new XmlElement("w:br", {"w:type": "page"}, []); - var result = readXmlElement(breakXml); - assert.deepEqual(result.value, []); - assert.deepEqual(result.messages, [warning("Unsupported break type: page")]); - }); +test("no elements created if image cannot be found in w:drawing", function() { + var drawing = new XmlElement("w:drawing", {}, []); - test("w:footnoteReference has ID read", function() { - var referenceXml = new XmlElement("w:footnoteReference", {"w:id": "4"}); - var result = readXmlElement(referenceXml); - assert.deepEqual( - result.value, - documents.noteReference({noteType: "footnote", noteId: "4"}) - ); - assert.deepEqual(result.messages, []); - }); - - test("w:commentReference has ID read", function() { - var referenceXml = new XmlElement("w:commentReference", {"w:id": "4"}); - var result = readXmlElement(referenceXml); - assert.deepEqual( - result.value, - documents.commentReference({commentId: "4"}) - ); - assert.deepEqual(result.messages, []); - }); - - test("emits warning on unrecognised element", function() { - var unrecognisedElement = new XmlElement("w:not-an-element"); - var result = readXmlElement(unrecognisedElement); - assert.deepEqual( - result.messages, - [{ - type: "warning", - message: "An unrecognised element was ignored: w:not-an-element" - }] - ); - assert.deepEqual(result.value, []); - }); + var reader = new BodyReader({}); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + assert.deepEqual(result.value, []); +}); + +test("no elements created if image cannot be found in wp:inline", function() { + var drawing = new XmlElement("wp:inline", {}, []); - test("w:bookmarkEnd is ignored without warning", function() { - var ignoredElement = new XmlElement("w:bookmarkEnd"); - var result = readXmlElement(ignoredElement); - assert.deepEqual(result.messages, []); - assert.deepEqual([], result.value); - }); + var reader = new BodyReader({}); + var result = reader.readXmlElement(drawing); + assert.deepEqual(result.messages, []); + assert.deepEqual(result.value, []); +}); - test("text boxes have content appended after containing paragraph", function() { - var textbox = new XmlElement("w:pict", {}, [ - new XmlElement("v:shape", {}, [ - new XmlElement("v:textbox", {}, [ - new XmlElement("w:txbxContent", {}, [ - paragraphWithStyleId("textbox-content") - ]) +test("children of w:ins are converted normally", function() { + var runXml = new XmlElement("w:r", {}, []); + var insXml = new XmlElement("w:ins", {}, [runXml]); + var result = readXmlElement(insXml); + assert.deepEqual(result.value[0].type, "run"); +}); + +test("children of w:smartTag are converted normally", function() { + var runXml = new XmlElement("w:r", {}, []); + var smartTagXml = new XmlElement("w:smartTag", {}, [runXml]); + var result = readXmlElement(smartTagXml); + assert.deepEqual(result.value[0].type, "run"); +}); + +test("w:hyperlink is read as document hyperlink if it has a relationship ID", function() { + var runXml = new XmlElement("w:r", {}, []); + var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42"}, [runXml]); + var relationships = { + "r42": {target: "http://example.com"} + }; + var result = readXmlElement(hyperlinkXml, {relationships: relationships}); + assert.deepEqual(result.value.href, "http://example.com"); + assert.deepEqual(result.value.children[0].type, "run"); +}); + +test("w:hyperlink is read as document hyperlink if it has an anchor", function() { + var runXml = new XmlElement("w:r", {}, []); + var hyperlinkXml = new XmlElement("w:hyperlink", {"w:anchor": "_Peter"}, [runXml]); + var result = readXmlElement(hyperlinkXml); + assert.deepEqual(result.value.anchor, "_Peter"); + assert.deepEqual(result.value.children[0].type, "run"); +}); + +test("w:hyperlink is ignored if it does not have a relationship ID nor anchor", function() { + var runXml = new XmlElement("w:r", {}, []); + var hyperlinkXml = new XmlElement("w:hyperlink", {}, [runXml]); + var result = readXmlElement(hyperlinkXml); + assert.deepEqual(result.value[0].type, "run"); +}); + +test("w:br is read as line break", function() { + var breakXml = new XmlElement("w:br", {}, []); + var result = readXmlElement(breakXml); + assert.deepEqual(result.value.type, "lineBreak"); + assert.deepEqual(result.messages, []); +}); + +test("warning on breaks that aren't line breaks", function() { + var breakXml = new XmlElement("w:br", {"w:type": "page"}, []); + var result = readXmlElement(breakXml); + assert.deepEqual(result.value, []); + assert.deepEqual(result.messages, [warning("Unsupported break type: page")]); +}); + +test("w:footnoteReference has ID read", function() { + var referenceXml = new XmlElement("w:footnoteReference", {"w:id": "4"}); + var result = readXmlElement(referenceXml); + assert.deepEqual( + result.value, + documents.noteReference({noteType: "footnote", noteId: "4"}) + ); + assert.deepEqual(result.messages, []); +}); + +test("w:commentReference has ID read", function() { + var referenceXml = new XmlElement("w:commentReference", {"w:id": "4"}); + var result = readXmlElement(referenceXml); + assert.deepEqual( + result.value, + documents.commentReference({commentId: "4"}) + ); + assert.deepEqual(result.messages, []); +}); + +test("emits warning on unrecognised element", function() { + var unrecognisedElement = new XmlElement("w:not-an-element"); + var result = readXmlElement(unrecognisedElement); + assert.deepEqual( + result.messages, + [{ + type: "warning", + message: "An unrecognised element was ignored: w:not-an-element" + }] + ); + assert.deepEqual(result.value, []); +}); + +test("w:bookmarkEnd is ignored without warning", function() { + var ignoredElement = new XmlElement("w:bookmarkEnd"); + var result = readXmlElement(ignoredElement); + assert.deepEqual(result.messages, []); + assert.deepEqual([], result.value); +}); + +test("text boxes have content appended after containing paragraph", function() { + var textbox = new XmlElement("w:pict", {}, [ + new XmlElement("v:shape", {}, [ + new XmlElement("v:textbox", {}, [ + new XmlElement("w:txbxContent", {}, [ + paragraphWithStyleId("textbox-content") ]) ]) - ]); - var paragraph = new XmlElement("w:p", {}, [ - new XmlElement("w:r", {}, [textbox]) - ]); - var result = readXmlElement(paragraph); - assert.deepEqual(result.value[1].styleId, "textbox-content"); - }); + ]) + ]); + var paragraph = new XmlElement("w:p", {}, [ + new XmlElement("w:r", {}, [textbox]) + ]); + var result = readXmlElement(paragraph); + assert.deepEqual(result.value[1].styleId, "textbox-content"); +}); - test("mc:Fallback is used when mc:AlternateContent is read", function() { - var styles = new Styles({"first": {name: "First"}, "second": {name: "Second"}}, {}); - var textbox = new XmlElement("mc:AlternateContent", {}, [ - new XmlElement("mc:Choice", {"Requires": "wps"}, [ - paragraphWithStyleId("first") - ]), - new XmlElement("mc:Fallback", {}, [ - paragraphWithStyleId("second") - ]) - ]); - var result = readXmlElement(textbox, {styles: styles}); - assert.deepEqual(result.value[0].styleId, "second"); - }); +test("mc:Fallback is used when mc:AlternateContent is read", function() { + var styles = new Styles({"first": {name: "First"}, "second": {name: "Second"}}, {}); + var textbox = new XmlElement("mc:AlternateContent", {}, [ + new XmlElement("mc:Choice", {"Requires": "wps"}, [ + paragraphWithStyleId("first") + ]), + new XmlElement("mc:Fallback", {}, [ + paragraphWithStyleId("second") + ]) + ]); + var result = readXmlElement(textbox, {styles: styles}); + assert.deepEqual(result.value[0].styleId, "second"); +}); - test("w:sdtContent is used when w:sdt is read", function() { - var element = xml.element("w:sdt", {}, [ - xml.element("w:sdtContent", {}, [ - xml.element("w:t", {}, [xml.text("Blackdown")]) - ]) - ]); - var result = readXmlElement(element); - assert.deepEqual(result.value, [new documents.Text("Blackdown")]); - }); - - test("text nodes are ignored when reading children", function() { - var runXml = new XmlElement("w:r", {}, [xml.text("[text]")]); - var run = readXmlElementValue(runXml); - assert.deepEqual(run, new documents.Run([])); - }); +test("w:sdtContent is used when w:sdt is read", function() { + var element = xml.element("w:sdt", {}, [ + xml.element("w:sdtContent", {}, [ + xml.element("w:t", {}, [xml.text("Blackdown")]) + ]) + ]); + var result = readXmlElement(element); + assert.deepEqual(result.value, [new documents.Text("Blackdown")]); +}); + +test("text nodes are ignored when reading children", function() { + var runXml = new XmlElement("w:r", {}, [xml.text("[text]")]); + var run = readXmlElementValue(runXml); + assert.deepEqual(run, new documents.Run([])); }); function paragraphWithStyleId(styleId) { diff --git a/test/docx/content-types-reader.tests.js b/test/docx/content-types-reader.tests.js index 5ea93ec..0c83dd2 100644 --- a/test/docx/content-types-reader.tests.js +++ b/test/docx/content-types-reader.tests.js @@ -2,46 +2,44 @@ var assert = require("assert"); var readContentTypesFromXml = require("../../lib/docx/content-types-reader").readContentTypesFromXml; var XmlElement = require("../../lib/xml").Element; -var test = require("../testing").test; +var test = require("../test")(module); -describe('readContentTypesFromXml', function() { - test('reads default-per-extension from XML', function() { - var contentTypes = readContentTypesFromXml( - new XmlElement("content-types:Types", {}, [ - new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}) - ]) - ); - assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); - }); - - test('reads overrides in preference to defaults', function() { - var contentTypes = readContentTypesFromXml( - new XmlElement("content-types:Types", {}, [ - new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}), - new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"}) - ]) - ); - assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat"); - }); - - test('fallback content types have common image types', function() { - var contentTypes = readContentTypesFromXml( - new XmlElement("content-types:Types", {}, []) - ); - assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); - assert.equal(contentTypes.findContentType("word/media/hat.gif"), "image/gif"); - assert.equal(contentTypes.findContentType("word/media/hat.jpg"), "image/jpeg"); - assert.equal(contentTypes.findContentType("word/media/hat.jpeg"), "image/jpeg"); - assert.equal(contentTypes.findContentType("word/media/hat.bmp"), "image/bmp"); - assert.equal(contentTypes.findContentType("word/media/hat.tif"), "image/tiff"); - assert.equal(contentTypes.findContentType("word/media/hat.tiff"), "image/tiff"); - }); - - test('fallback content types are case insensitive on extension', function() { - var contentTypes = readContentTypesFromXml( - new XmlElement("content-types:Types", {}, []) - ); - assert.equal(contentTypes.findContentType("word/media/hat.PnG"), "image/png"); - }); +test('reads default-per-extension from XML', function() { + var contentTypes = readContentTypesFromXml( + new XmlElement("content-types:Types", {}, [ + new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}) + ]) + ); + assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); +}); + +test('reads overrides in preference to defaults', function() { + var contentTypes = readContentTypesFromXml( + new XmlElement("content-types:Types", {}, [ + new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}), + new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"}) + ]) + ); + assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat"); +}); + +test('fallback content types have common image types', function() { + var contentTypes = readContentTypesFromXml( + new XmlElement("content-types:Types", {}, []) + ); + assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png"); + assert.equal(contentTypes.findContentType("word/media/hat.gif"), "image/gif"); + assert.equal(contentTypes.findContentType("word/media/hat.jpg"), "image/jpeg"); + assert.equal(contentTypes.findContentType("word/media/hat.jpeg"), "image/jpeg"); + assert.equal(contentTypes.findContentType("word/media/hat.bmp"), "image/bmp"); + assert.equal(contentTypes.findContentType("word/media/hat.tif"), "image/tiff"); + assert.equal(contentTypes.findContentType("word/media/hat.tiff"), "image/tiff"); +}); + +test('fallback content types are case insensitive on extension', function() { + var contentTypes = readContentTypesFromXml( + new XmlElement("content-types:Types", {}, []) + ); + assert.equal(contentTypes.findContentType("word/media/hat.PnG"), "image/png"); }); diff --git a/test/docx/docx-reader.tests.js b/test/docx/docx-reader.tests.js index 69ade7e..283d03a 100644 --- a/test/docx/docx-reader.tests.js +++ b/test/docx/docx-reader.tests.js @@ -4,39 +4,37 @@ var docxReader = require("../../lib/docx/docx-reader"); var documents = require("../../lib/documents"); var testing = require("../testing"); -var test = testing.test; +var test = require("../test")(module); var testData = testing.testData; var createFakeDocxFile = testing.createFakeDocxFile; -describe("docx-reader", function() { - test("can read document with single paragraph with single run of text", function() { - var expectedDocument = documents.Document([ - documents.Paragraph([ - documents.Run([ - documents.Text("Hello.") - ]) +test("can read document with single paragraph with single run of text", function() { + var expectedDocument = documents.Document([ + documents.Paragraph([ + documents.Run([ + documents.Text("Hello.") ]) - ]); - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - return docxReader.read(docxFile).then(function(result) { - assert.deepEqual(expectedDocument, result.value); - }); + ]) + ]); + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") }); - - test("hyperlink hrefs are read from relationships file", function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("hyperlinks/word/document.xml"), - "word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels") - }); - return docxReader.read(docxFile).then(function(result) { - var paragraph = result.value.children[0]; - assert.equal(1, paragraph.children.length); - var hyperlink = paragraph.children[0]; - assert.equal(hyperlink.href, "http://www.example.com"); - assert.equal(hyperlink.children.length, 1); - }); + return docxReader.read(docxFile).then(function(result) { + assert.deepEqual(expectedDocument, result.value); + }); +}); + +test("hyperlink hrefs are read from relationships file", function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("hyperlinks/word/document.xml"), + "word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels") + }); + return docxReader.read(docxFile).then(function(result) { + var paragraph = result.value.children[0]; + assert.equal(1, paragraph.children.length); + var hyperlink = paragraph.children[0]; + assert.equal(hyperlink.href, "http://www.example.com"); + assert.equal(hyperlink.children.length, 1); }); }); diff --git a/test/docx/files.tests.js b/test/docx/files.tests.js index 2a0919c..75999aa 100644 --- a/test/docx/files.tests.js +++ b/test/docx/files.tests.js @@ -7,13 +7,13 @@ var Files = require("../../lib/docx/files").Files; var uriToPath = require("../../lib/docx/files").uriToPath; var testing = require("../testing"); -var test = testing.test; +var test = require("../test")(module); var readFile = promises.promisify(fs.readFile.bind(fs)); -describe("Files", function() { - test("can open files with file URI", function() { +test("Files", { + "can open files with file URI": function() { var filePath = path.resolve(testing.testPath("tiny-picture.png")); var files = new Files(null); return files.read("file:///" + filePath.replace(/^\//, ""), "base64").then(function(contents) { @@ -21,9 +21,9 @@ describe("Files", function() { assert.deepEqual(contents, expectedContents); }); }); - }); + }, - test("can open files with relative URI", function() { + "can open files with relative URI": function() { var filePath = path.resolve(testing.testPath("tiny-picture.png")); var files = new Files(testing.testPath(".")); return files.read("tiny-picture.png", "base64").then(function(contents) { @@ -31,21 +31,21 @@ describe("Files", function() { assert.deepEqual(contents, expectedContents); }); }); - }); + }, - test("given base is not set when opening relative uri then error is raised", function() { + "given base is not set when opening relative uri then error is raised": function() { var files = new Files(null); return assertError(files.read("not-a-real-file.png", "base64"), function(err) { assert.equal(err.message, "could not find external image 'not-a-real-file.png', path of input document is unknown"); }); - }); + }, - test("error if relative uri cannot be opened", function() { + "error if relative uri cannot be opened": function() { var files = new Files("/tmp"); return assertError(files.read("not-a-real-file.png", "base64"), function(err) { assertRegex(err.message, /could not open external image: 'not-a-real-file.png' \(document directory: '\/tmp'\)\nENOENT.*\/tmp\/not-a-real-file.png.*/); }); - }); + } }); function assertError(promise, func) { @@ -59,36 +59,36 @@ function assertRegex(actual, expected) { } -describe("uriToPath", function() { - test("leading slash is retained on non-Windows file URIs", function() { +test("uriToPath", { + "leading slash is retained on non-Windows file URIs": function() { assert.equal(uriToPath("file:///a/b/c", "linux"), "/a/b/c"); assert.equal(uriToPath("file:///a/b/c", "win32"), "/a/b/c"); - }); + }, - test("URI is unquoted", function() { + "URI is unquoted": function() { assert.equal(uriToPath("file:///a%20b"), "/a b"); - }); + }, - test("when host is set to localhost then path can be found", function() { + "when host is set to localhost then path can be found": function() { assert.equal(uriToPath("file://localhost/a/b/c"), "/a/b/c"); - }); + }, - test("when host is set but not localhost then path cannot be found", function() { + "when host is set but not localhost then path cannot be found": function() { assert.throws(function() { uriToPath("file://example/a/b/c"); }, /Could not convert URI to path: file:\/\/example\/a\/b\/c/); - }); + }, - test("leading slash is not dropped on Windows file URIs when platform is not Windows", function() { + "leading slash is not dropped on Windows file URIs when platform is not Windows": function() { assert.equal(uriToPath("file:///c:/a", "linux"), "/c:/a"); - }); + }, - test("leading slash is dropped on Windows file URIs when platform is Windows", function() { + "leading slash is dropped on Windows file URIs when platform is Windows": function() { assert.equal(uriToPath("file:///c:/a", "win32"), "c:/a"); assert.equal(uriToPath("file:///C:/a", "win32"), "C:/a"); - }); + }, - test("relative URI is unquoted", function() { + "relative URI is unquoted": function() { assert.equal(uriToPath("a%20b/c"), "a b/c"); - }); + } }); diff --git a/test/docx/notes-reader.tests.js b/test/docx/notes-reader.tests.js index 85a49f5..1d4cf9c 100644 --- a/test/docx/notes-reader.tests.js +++ b/test/docx/notes-reader.tests.js @@ -4,36 +4,32 @@ var createFootnotesReader = require("../../lib/docx/notes-reader").createFootnot var BodyReader = require("../../lib/docx/body-reader").BodyReader; var documents = require("../../lib/documents"); var XmlElement = require("../../lib/xml").Element; -var test = require("../testing").test; +var test = require("../test")(module); -describe('readFootnotesXml', function() { - test('ID and body of footnote are read', function() { - var bodyReader = new BodyReader({}); - var footnoteBody = [new XmlElement("w:p", {}, [])]; - var footnotes = createFootnotesReader(bodyReader)( - new XmlElement("w:footnotes", {}, [ - new XmlElement("w:footnote", {"w:id": "1"}, footnoteBody) - ]) - ); - assert.equal(footnotes.value.length, 1); - assert.deepEqual(footnotes.value[0].body, [new documents.Paragraph([])]); - assert.deepEqual(footnotes.value[0].noteId, "1"); - }); +test('ID and body of footnote are read', function() { + var bodyReader = new BodyReader({}); + var footnoteBody = [new XmlElement("w:p", {}, [])]; + var footnotes = createFootnotesReader(bodyReader)( + new XmlElement("w:footnotes", {}, [ + new XmlElement("w:footnote", {"w:id": "1"}, footnoteBody) + ]) + ); + assert.equal(footnotes.value.length, 1); + assert.deepEqual(footnotes.value[0].body, [new documents.Paragraph([])]); + assert.deepEqual(footnotes.value[0].noteId, "1"); }); footnoteTypeIsIgnored('continuationSeparator'); footnoteTypeIsIgnored('separator'); function footnoteTypeIsIgnored(type) { - describe('footnotes of type ' + type + ' are ignored', function() { - test('ID and body of footnote are read', function() { - var footnotes = createFootnotesReader()( - new XmlElement("w:footnotes", {}, [ - new XmlElement("w:footnote", {"w:id": "1", "w:type": type}, []) - ]) - ); - assert.equal(footnotes.value.length, 0); - }); + test('footnotes of type ' + type + ' are ignored', function() { + var footnotes = createFootnotesReader()( + new XmlElement("w:footnotes", {}, [ + new XmlElement("w:footnote", {"w:id": "1", "w:type": type}, []) + ]) + ); + assert.equal(footnotes.value.length, 0); }); } diff --git a/test/docx/numbering-xml.tests.js b/test/docx/numbering-xml.tests.js index fb28cb8..7ecacc2 100644 --- a/test/docx/numbering-xml.tests.js +++ b/test/docx/numbering-xml.tests.js @@ -2,31 +2,29 @@ var duck = require("duck"); var readNumberingXml = require("../../lib/docx/numbering-xml").readNumberingXml; var XmlElement = require("../../lib/xml").Element; -var test = require("../testing").test; +var test = require("../test")(module); -describe('readNumberingXml', function() { - test('w:num element inherits levels from w:abstractNum', function() { - var numbering = readNumberingXml( - new XmlElement("w:numbering", {}, [ - new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [ - new XmlElement("w:lvl", {"w:ilvl": "0"}, [ - new XmlElement("w:numFmt", {"w:val": "bullet"}) - ]), - new XmlElement("w:lvl", {"w:ilvl": "1"}, [ - new XmlElement("w:numFmt", {"w:val": "decimal"}) - ]) +test('w:num element inherits levels from w:abstractNum', function() { + var numbering = readNumberingXml( + new XmlElement("w:numbering", {}, [ + new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [ + new XmlElement("w:lvl", {"w:ilvl": "0"}, [ + new XmlElement("w:numFmt", {"w:val": "bullet"}) ]), - new XmlElement("w:num", {"w:numId": "47"}, [ - new XmlElement("w:abstractNumId", {"w:val": "42"}) + new XmlElement("w:lvl", {"w:ilvl": "1"}, [ + new XmlElement("w:numFmt", {"w:val": "decimal"}) ]) + ]), + new XmlElement("w:num", {"w:numId": "47"}, [ + new XmlElement("w:abstractNumId", {"w:val": "42"}) ]) - ); - duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({ - isOrdered: false - })); - duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({ - isOrdered: true - })); - }); + ]) + ); + duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({ + isOrdered: false + })); + duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({ + isOrdered: true + })); }); diff --git a/test/docx/office-xml-reader.tests.js b/test/docx/office-xml-reader.tests.js index f28c4f3..6cd9d7d 100644 --- a/test/docx/office-xml-reader.tests.js +++ b/test/docx/office-xml-reader.tests.js @@ -2,25 +2,23 @@ var assert = require("assert"); var xml = require("../../lib/xml"); var officeXmlReader = require("../../lib/docx/office-xml-reader"); -var test = require("../testing").test; +var test = require("../test")(module); -describe("office-xml-reader", function() { - test("mc:AlternateContent is replaced by contents of mc:Fallback", function() { - var xmlString = - '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + - '<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' + - '<mc:AlternateContent>' + - '<mc:Choice Requires="w14">' + - '<choice/>' + - '</mc:Choice>' + - '<mc:Fallback>' + - '<fallback/>' + - '</mc:Fallback>' + - '</mc:AlternateContent>' + - '</numbering>'; - return officeXmlReader.read(xmlString).then(function(element) { - assert.deepEqual(element.children, [xml.element("fallback")]); - }); - }); +test("mc:AlternateContent is replaced by contents of mc:Fallback", function() { + var xmlString = + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + + '<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' + + '<mc:AlternateContent>' + + '<mc:Choice Requires="w14">' + + '<choice/>' + + '</mc:Choice>' + + '<mc:Fallback>' + + '<fallback/>' + + '</mc:Fallback>' + + '</mc:AlternateContent>' + + '</numbering>'; + return officeXmlReader.read(xmlString).then(function(element) { + assert.deepEqual(element.children, [xml.element("fallback")]); + }); }); diff --git a/test/docx/style-map.tests.js b/test/docx/style-map.tests.js index 55171f8..4ddb127 100644 --- a/test/docx/style-map.tests.js +++ b/test/docx/style-map.tests.js @@ -4,85 +4,82 @@ var JSZip = require("jszip"); var zipfile = require("../../lib/zipfile"); var styleMap = require("../../lib/docx/style-map"); -var test = require("../testing").test; +var test = require("../test")(module); -describe("style-map", function() { - test('reading embedded style map on document without embedded style map returns null', function() { - var zip = normalDocx(); - - return styleMap.readStyleMap(zip).then(function(contents) { - assert.equal(contents, null); - }); +test('reading embedded style map on document without embedded style map returns null', function() { + var zip = normalDocx(); + + return styleMap.readStyleMap(zip).then(function(contents) { + assert.equal(contents, null); }); +}); + +test('embedded style map can be read after being written', function() { + var zip = normalDocx(); - test('embedded style map can be read after being written', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return styleMap.readStyleMap(zip).then(function(contents) { - assert.equal(contents, "p => h1"); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return styleMap.readStyleMap(zip).then(function(contents) { + assert.equal(contents, "p => h1"); }); }); +}); + +test('embedded style map is written to separate file', function() { + var zip = normalDocx(); - test('embedded style map is written to separate file', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return zip.read("mammoth/style-map", "utf8").then(function(contents) { - assert.equal(contents, "p => h1"); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return zip.read("mammoth/style-map", "utf8").then(function(contents) { + assert.equal(contents, "p => h1"); }); }); +}); + +test('embedded style map is referenced in relationships', function() { + var zip = normalDocx(); - test('embedded style map is referenced in relationships', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) { - assert.equal(contents, expectedRelationshipsXml); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) { + assert.equal(contents, expectedRelationshipsXml); }); }); +}); + +test('re-embedding style map replaces original', function() { + var zip = normalDocx(); - test('re-embedding style map replaces original', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return styleMap.writeStyleMap(zip, "p => h2"); - }).then(function() { - return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) { - assert.equal(contents, expectedRelationshipsXml); - }); - }).then(function() { - return styleMap.readStyleMap(zip).then(function(contents) { - assert.equal(contents, "p => h2"); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return styleMap.writeStyleMap(zip, "p => h2"); + }).then(function() { + return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) { + assert.equal(contents, expectedRelationshipsXml); + }); + }).then(function() { + return styleMap.readStyleMap(zip).then(function(contents) { + assert.equal(contents, "p => h2"); }); }); +}); + +test('embedded style map has override content type in [Content_Types].xml', function() { + var zip = normalDocx(); - test('embedded style map has override content type in [Content_Types].xml', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return zip.read("[Content_Types].xml", "utf8").then(function(contents) { - assert.equal(contents, expectedContentTypesXml); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return zip.read("[Content_Types].xml", "utf8").then(function(contents) { + assert.equal(contents, expectedContentTypesXml); }); }); +}); + +test('replacing style map keeps content type', function() { + var zip = normalDocx(); - test('replacing style map keeps content type', function() { - var zip = normalDocx(); - - return styleMap.writeStyleMap(zip, "p => h1").then(function() { - return styleMap.writeStyleMap(zip, "p => h2"); - }).then(function() { - return zip.read("[Content_Types].xml", "utf8").then(function(contents) { - assert.equal(contents, expectedContentTypesXml); - }); + return styleMap.writeStyleMap(zip, "p => h1").then(function() { + return styleMap.writeStyleMap(zip, "p => h2"); + }).then(function() { + return zip.read("[Content_Types].xml", "utf8").then(function(contents) { + assert.equal(contents, expectedContentTypesXml); }); }); - }); var expectedRelationshipsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + diff --git a/test/docx/styles-reader.tests.js b/test/docx/styles-reader.tests.js index 8de53ef..4c5da7e 100644 --- a/test/docx/styles-reader.tests.js +++ b/test/docx/styles-reader.tests.js @@ -2,75 +2,72 @@ var assert = require("assert"); var readStylesXml = require("../../lib/docx/styles-reader").readStylesXml; var XmlElement = require("../../lib/xml").Element; -var test = require("../testing").test; +var test = require("../test")(module); -describe('readStylesXml', function() { - test('paragraph style is null if no style with that ID exists', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, []) - ); - assert.equal(styles.findParagraphStyleById("Heading1"), null); - }); - - test('paragraph style can be found by ID', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - paragraphStyleElement("Heading1", "Heading 1") - ]) - ); - assert.equal(styles.findParagraphStyleById("Heading1").styleId, "Heading1"); - }); - - test('character style can be found by ID', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - characterStyleElement("Heading1Char", "Heading 1 Char") - ]) - ); - assert.equal(styles.findCharacterStyleById("Heading1Char").styleId, "Heading1Char"); - }); - - test('paragraph and character styles are distinct', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - paragraphStyleElement("Heading1", "Heading 1"), - characterStyleElement("Heading1Char", "Heading 1 Char") - ]) - ); - assert.equal(styles.findCharacterStyleById("Heading1"), null); - assert.equal(styles.findParagraphStyleById("Heading1Char"), null); - }); - - test('character and table styles are distinct', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - styleElement("table", "Heading1", "Heading 1") - ]) - ); - assert.equal(styles.findCharacterStyleById("Heading1"), null); - }); - - test('styles include names', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - paragraphStyleElement("Heading1", "Heading 1") - ]) - ); - assert.equal(styles.findParagraphStyleById("Heading1").name, "Heading 1"); - }); +test('paragraph style is null if no style with that ID exists', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, []) + ); + assert.equal(styles.findParagraphStyleById("Heading1"), null); +}); + +test('paragraph style can be found by ID', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + paragraphStyleElement("Heading1", "Heading 1") + ]) + ); + assert.equal(styles.findParagraphStyleById("Heading1").styleId, "Heading1"); +}); + +test('character style can be found by ID', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + characterStyleElement("Heading1Char", "Heading 1 Char") + ]) + ); + assert.equal(styles.findCharacterStyleById("Heading1Char").styleId, "Heading1Char"); +}); - test('style name is null if w:name element does not exist', function() { - var styles = readStylesXml( - new XmlElement("w:styles", {}, [ - styleWithoutWNameElement("paragraph", "Heading1"), - styleWithoutWNameElement("character", "Heading1Char") - ]) - ); - assert.equal(styles.findParagraphStyleById("Heading1").name, null); - assert.equal(styles.findCharacterStyleById("Heading1Char").name, null); - }); +test('paragraph and character styles are distinct', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + paragraphStyleElement("Heading1", "Heading 1"), + characterStyleElement("Heading1Char", "Heading 1 Char") + ]) + ); + assert.equal(styles.findCharacterStyleById("Heading1"), null); + assert.equal(styles.findParagraphStyleById("Heading1Char"), null); +}); + +test('character and table styles are distinct', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + styleElement("table", "Heading1", "Heading 1") + ]) + ); + assert.equal(styles.findCharacterStyleById("Heading1"), null); +}); + +test('styles include names', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + paragraphStyleElement("Heading1", "Heading 1") + ]) + ); + assert.equal(styles.findParagraphStyleById("Heading1").name, "Heading 1"); +}); +test('style name is null if w:name element does not exist', function() { + var styles = readStylesXml( + new XmlElement("w:styles", {}, [ + styleWithoutWNameElement("paragraph", "Heading1"), + styleWithoutWNameElement("character", "Heading1Char") + ]) + ); + assert.equal(styles.findParagraphStyleById("Heading1").name, null); + assert.equal(styles.findCharacterStyleById("Heading1Char").name, null); }); function paragraphStyleElement(id, name) { diff --git a/test/html-paths.tests.js b/test/html-paths.tests.js index 76a77ef..8d75b7c 100644 --- a/test/html-paths.tests.js +++ b/test/html-paths.tests.js @@ -1,22 +1,20 @@ var assert = require("assert"); -var test = require("./testing").test; +var test = require("./test")(module); var htmlPaths = require("../lib/html-paths"); -describe("html-paths", function() { - test("element can match multiple tag names", function() { - var pathPart = htmlPaths.element(["ul", "ol"]); - assert.ok(pathPart.matchesElement({tagName: "ul"})); - assert.ok(pathPart.matchesElement({tagName: "ol"})); - assert.ok(!pathPart.matchesElement({tagName: "p"})); - }); - - test("element matches if attributes are the same", function() { - var pathPart = htmlPaths.element(["p"], {"class": "tip"}); - assert.ok(!pathPart.matchesElement({tagName: "p"})); - assert.ok(!pathPart.matchesElement({tagName: "p", attributes: {"class": "tip help"}})); - assert.ok(pathPart.matchesElement({tagName: "p", attributes: {"class": "tip"}})); - }); +test("element can match multiple tag names", function() { + var pathPart = htmlPaths.element(["ul", "ol"]); + assert.ok(pathPart.matchesElement({tagName: "ul"})); + assert.ok(pathPart.matchesElement({tagName: "ol"})); + assert.ok(!pathPart.matchesElement({tagName: "p"})); +}); + +test("element matches if attributes are the same", function() { + var pathPart = htmlPaths.element(["p"], {"class": "tip"}); + assert.ok(!pathPart.matchesElement({tagName: "p"})); + assert.ok(!pathPart.matchesElement({tagName: "p", attributes: {"class": "tip help"}})); + assert.ok(pathPart.matchesElement({tagName: "p", attributes: {"class": "tip"}})); }); diff --git a/test/html/simplify.tests.js b/test/html/simplify.tests.js index ccc5610..50593ee 100644 --- a/test/html/simplify.tests.js +++ b/test/html/simplify.tests.js @@ -2,108 +2,105 @@ var assert = require("assert"); var _ = require("underscore"); -var test = require("../testing").test; +var test = require("../test")(module); var html = require("../../lib/html"); var htmlPaths = require("../../lib/html-paths"); var nonFreshElement = html.nonFreshElement; var text = html.text; -var pathToNodes = html.pathToNodes; -describe("simplify", function() { - test("empty text nodes are removed", function() { - assert.deepEqual( - simplifyNode(text("")), - [] - ); - }); - - test("elements with no children are removed", function() { - assert.deepEqual( - simplifyNode(nonFreshElement("p", {}, [])), - [] - ); - }); - - test("elements only containing empty nodes are removed", function() { - assert.deepEqual( - simplifyNode(nonFreshElement("p", {}, [text("")])), - [] - ); - }); - - test("empty children of element are removed", function() { - assert.deepEqual( - simplifyNode(nonFreshElement("p", {}, [text("Hello"), text("")])), - [nonFreshElement("p", {}, [text("Hello")])] - ); - }); +test("empty text nodes are removed", function() { + assert.deepEqual( + simplifyNode(text("")), + [] + ); +}); + +test("elements with no children are removed", function() { + assert.deepEqual( + simplifyNode(nonFreshElement("p", {}, [])), + [] + ); +}); + +test("elements only containing empty nodes are removed", function() { + assert.deepEqual( + simplifyNode(nonFreshElement("p", {}, [text("")])), + [] + ); +}); + +test("empty children of element are removed", function() { + assert.deepEqual( + simplifyNode(nonFreshElement("p", {}, [text("Hello"), text("")])), + [nonFreshElement("p", {}, [text("Hello")])] + ); +}); + +test("successive fresh elements are not collapsed", function() { + var path = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: true}) + ]); + var original = concat( + pathToNodes(path, [text("Hello")]), + pathToNodes(path, [text(" there")]) + ); - test("successive fresh elements are not collapsed", function() { - var path = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: true}) - ]); - var original = concat( + assert.deepEqual( + html.simplify(original), + original); +}); + +test("successive plain non-fresh elements are collapsed if they have the same tag name", function() { + var path = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: false}) + ]); + assert.deepEqual( + html.simplify(concat( pathToNodes(path, [text("Hello")]), pathToNodes(path, [text(" there")]) - ); - - assert.deepEqual( - html.simplify(original), - original); - }); - - test("successive plain non-fresh elements are collapsed if they have the same tag name", function() { - var path = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: false}) - ]); - assert.deepEqual( - html.simplify(concat( - pathToNodes(path, [text("Hello")]), - pathToNodes(path, [text(" there")]) - )), - pathToNodes(path, [text("Hello"), text(" there")]) - ); - }); - - test("non-fresh can collapse into preceding fresh element", function() { - var freshPath = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: true})]); - var nonFreshPath = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: false})]); - assert.deepEqual( - html.simplify(concat( - pathToNodes(freshPath, [text("Hello")]), - pathToNodes(nonFreshPath, [text(" there")]) - )), - pathToNodes(freshPath, [text("Hello"), text(" there")]) - ); - }); - - test("children of collapsed element can collapse with children of another collapsed element", function() { - assert.deepEqual( - html.simplify([ - nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello")])]), - nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("there")])]) - ]), - [nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello"), text("there")])])] - ); - }); - - test("empty elements are removed before collapsing", function() { - var freshPath = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: true})]); - var nonFreshPath = htmlPaths.elements([ - htmlPaths.element("p", {}, {fresh: false})]); - assert.deepEqual( - html.simplify(concat( - pathToNodes(nonFreshPath, [text("Hello")]), - pathToNodes(freshPath, []), - pathToNodes(nonFreshPath, [text(" there")]) - )), - pathToNodes(nonFreshPath, [text("Hello"), text(" there")]) - ); - }); + )), + pathToNodes(path, [text("Hello"), text(" there")]) + ); +}); + +test("non-fresh can collapse into preceding fresh element", function() { + var freshPath = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: true})]); + var nonFreshPath = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: false})]); + assert.deepEqual( + html.simplify(concat( + pathToNodes(freshPath, [text("Hello")]), + pathToNodes(nonFreshPath, [text(" there")]) + )), + pathToNodes(freshPath, [text("Hello"), text(" there")]) + ); +}); + +test("children of collapsed element can collapse with children of another collapsed element", function() { + assert.deepEqual( + html.simplify([ + nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello")])]), + nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("there")])]) + ]), + [nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello"), text("there")])])] + ); +}); + +test("empty elements are removed before collapsing", function() { + var freshPath = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: true})]); + var nonFreshPath = htmlPaths.elements([ + htmlPaths.element("p", {}, {fresh: false})]); + assert.deepEqual( + html.simplify(concat( + pathToNodes(nonFreshPath, [text("Hello")]), + pathToNodes(freshPath, []), + pathToNodes(nonFreshPath, [text(" there")]) + )), + pathToNodes(nonFreshPath, [text("Hello"), text(" there")]) + ); }); function simplifyNode(node) { @@ -113,3 +110,9 @@ function simplifyNode(node) { function concat() { return _.flatten(arguments, true); } + +function pathToNodes(path, nodes) { + return path.wrap(function() { + return nodes; + }); +} diff --git a/test/html/write.tests.js b/test/html/write.tests.js index 531995f..9db8c79 100644 --- a/test/html/write.tests.js +++ b/test/html/write.tests.js @@ -1,40 +1,38 @@ var assert = require("assert"); -var test = require("../testing").test; +var test = require("../test")(module); var html = require("../../lib/html"); var writers = require("../../lib/writers"); -describe("HTML write", function() { - test("text is HTML escaped", function() { - assert.equal( - generateString(html.text("<>&")), - "<>&"); - }); - - test("double quotes outside of attributes are not escaped", function() { - assert.equal( - generateString(html.text('"')), - '"'); - }); - - test("element attributes are HTML escaped", function() { - assert.equal( - generateString(html.freshElement("p", {"x": "<"})), - '<p x="<"></p>'); - }); - - test("double quotes inside attributes are escaped", function() { - assert.equal( - generateString(html.freshElement("p", {"x": '"'})), - '<p x="""></p>'); - }); - - test("element children are written", function() { - assert.equal( - generateString(html.freshElement("p", {}, [html.text("Hello")])), - '<p>Hello</p>'); - }); +test("text is HTML escaped", function() { + assert.equal( + generateString(html.text("<>&")), + "<>&"); +}); + +test("double quotes outside of attributes are not escaped", function() { + assert.equal( + generateString(html.text('"')), + '"'); +}); + +test("element attributes are HTML escaped", function() { + assert.equal( + generateString(html.freshElement("p", {"x": "<"})), + '<p x="<"></p>'); +}); + +test("double quotes inside attributes are escaped", function() { + assert.equal( + generateString(html.freshElement("p", {"x": '"'})), + '<p x="""></p>'); +}); + +test("element children are written", function() { + assert.equal( + generateString(html.freshElement("p", {}, [html.text("Hello")])), + '<p>Hello</p>'); }); function generateString(node) { diff --git a/test/images.tests.js b/test/images.tests.js index 7aac5a2..7ba3ec9 100644 --- a/test/images.tests.js +++ b/test/images.tests.js @@ -2,12 +2,9 @@ var assert = require("assert"); var mammoth = require("../"); -var testing = require("./testing"); -var test = testing.test; +var test = require("./test")(module); -describe('images', function() { - test('mammoth.images.inline() should be an alias of mammoth.images.imgElement()', function() { - assert.ok(mammoth.images.inline === mammoth.images.imgElement); - }); +test('mammoth.images.inline() should be an alias of mammoth.images.imgElement()', function() { + assert.ok(mammoth.images.inline === mammoth.images.imgElement); }); diff --git a/test/main.tests.js b/test/main.tests.js index d603626..505c31f 100644 --- a/test/main.tests.js +++ b/test/main.tests.js @@ -6,69 +6,67 @@ var fs = require("fs"); var temp = require('temp').track(); var promises = require("../lib/promises"); -var test = require("./testing").test; +var test = require("./test")(module); var testPath = require("./testing").testPath; -describe("CLI", function() { - test("HTML is printed to stdout if output file is not set", function() { - return runMammoth(testPath("single-paragraph.docx")).then(function(result) { - assert.equal(result.stderrOutput, ""); - assert.equal(result.output, "<p>Walking on imported air</p>"); - }); - }); - - test("HTML is written to file if output file is set", function() { - return createTempDir().then(function(tempDir) { - var outputPath = path.join(tempDir, "output.html"); - return runMammoth(testPath("single-paragraph.docx"), outputPath).then(function(result) { - assert.equal(result.stderrOutput, ""); - assert.equal(result.output, ""); - assert.equal(fs.readFileSync(outputPath, "utf8"), "<p>Walking on imported air</p>"); - }); - }); +test("HTML is printed to stdout if output file is not set", function() { + return runMammoth(testPath("single-paragraph.docx")).then(function(result) { + assert.equal(result.stderrOutput, ""); + assert.equal(result.output, "<p>Walking on imported air</p>"); }); - - var imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII="; - - test("inline images are included in output if writing to single file", function() { - return runMammoth(testPath("tiny-picture.docx")).then(function(result) { +}); + +test("HTML is written to file if output file is set", function() { + return createTempDir().then(function(tempDir) { + var outputPath = path.join(tempDir, "output.html"); + return runMammoth(testPath("single-paragraph.docx"), outputPath).then(function(result) { assert.equal(result.stderrOutput, ""); - assert.equal(result.output, '<p><img src="data:image/png;base64,' + imageBase64 + '" /></p>'); + assert.equal(result.output, ""); + assert.equal(fs.readFileSync(outputPath, "utf8"), "<p>Walking on imported air</p>"); }); }); - - test("images are written to separate files if output dir is set", function() { - return createTempDir().then(function(tempDir) { - var outputPath = path.join(tempDir, "tiny-picture.html"); - var imagePath = path.join(tempDir, "1.png"); - return runMammoth(testPath("tiny-picture.docx"), "--output-dir", tempDir).then(function(result) { - assert.equal(result.stderrOutput, ""); - assert.equal(result.output, ""); - assert.equal(fs.readFileSync(outputPath, "utf8"), '<p><img src="1.png" /></p>'); - assert.equal(fs.readFileSync(imagePath, "base64"), imageBase64); - }); - }); +}); + +var imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII="; + +test("inline images are included in output if writing to single file", function() { + return runMammoth(testPath("tiny-picture.docx")).then(function(result) { + assert.equal(result.stderrOutput, ""); + assert.equal(result.output, '<p><img src="data:image/png;base64,' + imageBase64 + '" /></p>'); }); - - test("style map is used if set", function() { - return createTempDir().then(function(tempDir) { - var styleMapPath = path.join(tempDir, "style-map"); - fs.writeFileSync(styleMapPath, "p => span:fresh"); - return runMammoth(testPath("single-paragraph.docx"), "--style-map", styleMapPath).then(function(result) { - assert.equal(result.stderrOutput, ""); - assert.equal(result.output, "<span>Walking on imported air</span>"); - }); +}); + +test("images are written to separate files if output dir is set", function() { + return createTempDir().then(function(tempDir) { + var outputPath = path.join(tempDir, "tiny-picture.html"); + var imagePath = path.join(tempDir, "1.png"); + return runMammoth(testPath("tiny-picture.docx"), "--output-dir", tempDir).then(function(result) { + assert.equal(result.stderrOutput, ""); + assert.equal(result.output, ""); + assert.equal(fs.readFileSync(outputPath, "utf8"), '<p><img src="1.png" /></p>'); + assert.equal(fs.readFileSync(imagePath, "base64"), imageBase64); }); }); - - test("--output-format=markdown option generate markdown output", function() { - return runMammoth(testPath("single-paragraph.docx"), "--output-format=markdown").then(function(result) { +}); + +test("style map is used if set", function() { + return createTempDir().then(function(tempDir) { + var styleMapPath = path.join(tempDir, "style-map"); + fs.writeFileSync(styleMapPath, "p => span:fresh"); + return runMammoth(testPath("single-paragraph.docx"), "--style-map", styleMapPath).then(function(result) { assert.equal(result.stderrOutput, ""); - assert.equal(result.output, "Walking on imported air\n\n"); + assert.equal(result.output, "<span>Walking on imported air</span>"); }); }); }); +test("--output-format=markdown option generate markdown output", function() { + return runMammoth(testPath("single-paragraph.docx"), "--output-format=markdown").then(function(result) { + assert.equal(result.stderrOutput, ""); + assert.equal(result.output, "Walking on imported air\n\n"); + }); +}); + function runMammoth() { var args = Array.prototype.slice.call(arguments, 0); diff --git a/test/mammoth.tests.js b/test/mammoth.tests.js index b01b65d..73e6a0f 100644 --- a/test/mammoth.tests.js +++ b/test/mammoth.tests.js @@ -8,347 +8,374 @@ var promises = require("../lib/promises"); var results = require("../lib/results"); var testing = require("./testing"); -var test = testing.test; +var test = require("./test")(module); var testData = testing.testData; var createFakeDocxFile = testing.createFakeDocxFile; -describe('mammoth', function() { - test('should convert docx containing one paragraph to single p element', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { +test('should convert docx containing one paragraph to single p element', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, "<p>Walking on imported air</p>"); + assert.deepEqual(result.messages, []); + }); +}); + +test('should convert docx represented by a Buffer', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return promises.nfcall(fs.readFile, docxPath) + .then(function(buffer) { + return mammoth.convertToHtml({buffer: buffer}); + }) + .then(function(result) { assert.equal(result.value, "<p>Walking on imported air</p>"); assert.deepEqual(result.messages, []); }); +}); + +test('should read docx xml files with unicode byte order mark', function() { + var docxPath = path.join(__dirname, "test-data/utf8-bom.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, "<p>This XML has a byte order mark.</p>"); + assert.deepEqual(result.messages, []); }); - - test('should convert docx represented by a Buffer', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return promises.nfcall(fs.readFile, docxPath) - .then(function(buffer) { - return mammoth.convertToHtml({buffer: buffer}); - }) - .then(function(result) { - assert.equal(result.value, "<p>Walking on imported air</p>"); - assert.deepEqual(result.messages, []); - }); - }); - - test('should read docx xml files with unicode byte order mark', function() { - var docxPath = path.join(__dirname, "test-data/utf8-bom.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, "<p>This XML has a byte order mark.</p>"); - assert.deepEqual(result.messages, []); - }); +}); + +test('empty paragraphs are ignored by default', function() { + var docxPath = path.join(__dirname, "test-data/empty.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, ""); + assert.deepEqual(result.messages, []); }); - - test('empty paragraphs are ignored by default', function() { - var docxPath = path.join(__dirname, "test-data/empty.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, ""); - assert.deepEqual(result.messages, []); - }); +}); + +test('empty paragraphs are preserved if ignoreEmptyParagraphs is false', function() { + var docxPath = path.join(__dirname, "test-data/empty.docx"); + return mammoth.convertToHtml({path: docxPath}, {ignoreEmptyParagraphs: false}).then(function(result) { + assert.equal(result.value, "<p></p>"); + assert.deepEqual(result.messages, []); }); - - test('empty paragraphs are preserved if ignoreEmptyParagraphs is false', function() { - var docxPath = path.join(__dirname, "test-data/empty.docx"); - return mammoth.convertToHtml({path: docxPath}, {ignoreEmptyParagraphs: false}).then(function(result) { - assert.equal(result.value, "<p></p>"); - assert.deepEqual(result.messages, []); - }); +}); + +test('style map can be expressed as string', function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") }); - - test('style map can be expressed as string', function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - var options = { - styleMap: "p => h1" - }; - return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { - assert.equal("<h1>Hello.</h1>", result.value); - }); + var options = { + styleMap: "p => h1" + }; + return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { + assert.equal("<h1>Hello.</h1>", result.value); }); - - test('style map can be expressed as array of style mappings', function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - var options = { - styleMap: ["p => h1"] - }; - return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { - assert.equal("<h1>Hello.</h1>", result.value); - }); +}); + +test('style map can be expressed as array of style mappings', function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") }); - - test('embedded style map is used if present', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return promises.nfcall(fs.readFile, docxPath) - .then(function(buffer) { - return mammoth.embedStyleMap({buffer: buffer}, "p => h1"); - }) - .then(function(docx) { - return mammoth.convertToHtml({buffer: docx.toBuffer()}); - }) - .then(function(result) { - assert.equal(result.value, "<h1>Walking on imported air</h1>"); - assert.deepEqual(result.messages, []); - }); - }); - - test('embedded style map can be retrieved', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return promises.nfcall(fs.readFile, docxPath) - .then(function(buffer) { - return mammoth.embedStyleMap({buffer: buffer}, "p => h1"); - }) - .then(function(docx) { - return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()}); - }) - .then(function(styleMap) { - assert.equal(styleMap, "p => h1"); - }); - }); - - test('warning if style mapping is not understood', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - var options = { - styleMap: "!!!!\np => h1" - }; - return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { - assert.equal("<h1>Walking on imported air</h1>", result.value); - var warning = "Did not understand this style mapping, so ignored it: !!!!\n" + - 'Error was at character number 1: Expected element type but got unrecognisedCharacter "!"'; - assert.deepEqual(result.messages, [results.warning(warning)]); - }); + var options = { + styleMap: ["p => h1"] + }; + return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { + assert.equal("<h1>Hello.</h1>", result.value); }); - - test('options are passed to document converter when calling mammoth.convertToHtml', function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") +}); + +test('embedded style map is used if present', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return promises.nfcall(fs.readFile, docxPath) + .then(function(buffer) { + return mammoth.embedStyleMap({buffer: buffer}, "p => h1"); + }) + .then(function(docx) { + return mammoth.convertToHtml({buffer: docx.toBuffer()}); + }) + .then(function(result) { + assert.equal(result.value, "<h1>Walking on imported air</h1>"); + assert.deepEqual(result.messages, []); }); - var options = { - styleMap: "p => h1" - }; - return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { - assert.equal("<h1>Hello.</h1>", result.value); +}); + +test('embedded style map can be retrieved', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return promises.nfcall(fs.readFile, docxPath) + .then(function(buffer) { + return mammoth.embedStyleMap({buffer: buffer}, "p => h1"); + }) + .then(function(docx) { + return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()}); + }) + .then(function(styleMap) { + assert.equal(styleMap, "p => h1"); }); +}); + +test('warning if style mapping is not understood', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + var options = { + styleMap: "????\np => h1" + }; + return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { + assert.equal("<h1>Walking on imported air</h1>", result.value); + var warning = "Did not understand this style mapping, so ignored it: ????\n" + + 'Error was at character number 1: Expected element type but got unrecognisedCharacter "?"'; + assert.deepEqual(result.messages, [results.warning(warning)]); }); - - test('options.transformDocument is used to transform document if set', function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - var options = { - transformDocument: function(document) { - document.children[0].styleId = "Heading1"; - return document; - } - }; - return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { - assert.equal("<h1>Hello.</h1>", result.value); - }); +}); + +test('options are passed to document converter when calling mammoth.convertToHtml', function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") }); - - test('mammoth.transforms.paragraph only transforms paragraphs', function() { - var docxFile = createFakeDocxFile({ - "word/document.xml": testData("simple/word/document.xml") - }); - var options = { - transformDocument: mammoth.transforms.paragraph(function(paragraph) { - return _.extend(paragraph, {styleId: "Heading1"}); - }) - }; - return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { - assert.equal("<h1>Hello.</h1>", result.value); - }); + var options = { + styleMap: "p => h1" + }; + return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { + assert.equal("<h1>Hello.</h1>", result.value); }); - - test('inline images are included in output', function() { - var docxPath = path.join(__dirname, "test-data/tiny-picture.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>'); - }); +}); + +test('options.transformDocument is used to transform document if set', function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") + }); + var options = { + transformDocument: function(document) { + document.children[0].styleId = "Heading1"; + return document; + } + }; + return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { + assert.equal("<h1>Hello.</h1>", result.value); }); - - test('src of inline images can be changed', function() { - var docxPath = path.join(__dirname, "test-data/tiny-picture.docx"); - var convertImage = mammoth.images.imgElement(function(element) { - return element.read("base64").then(function(encodedImage) { - return {src: encodedImage.substring(0, 2) + "," + element.contentType}; - }); - }); - return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) { - assert.equal(result.value, '<p><img src="iV,image/png" /></p>'); - }); +}); + +test('mammoth.transforms.paragraph only transforms paragraphs', function() { + var docxFile = createFakeDocxFile({ + "word/document.xml": testData("simple/word/document.xml") + }); + var options = { + transformDocument: mammoth.transforms.paragraph(function(paragraph) { + return _.extend(paragraph, {styleId: "Heading1"}); + }) + }; + return mammoth.convertToHtml({file: docxFile}, options).then(function(result) { + assert.equal("<h1>Hello.</h1>", result.value); }); - - test('images stored outside of document are included in output', function() { - var docxPath = path.join(__dirname, "test-data/external-picture.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>'); - assert.deepEqual(result.messages, []); - }); +}); + +test('inline images are included in output', function() { + var docxPath = path.join(__dirname, "test-data/tiny-picture.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>'); }); - - test('error if images stored outside of document are specified when passing file without path', function() { - var docxPath = path.join(__dirname, "test-data/external-picture.docx"); - var buffer = fs.readFileSync(docxPath); - return mammoth.convertToHtml({buffer: buffer}).then(function(result) { - assert.equal(result.value, ''); - assert.equal(result.messages[0].message, "could not find external image 'tiny-picture.png', path of input document is unknown"); - assert.equal(result.messages[0].type, "error"); +}); + +test('src of inline images can be changed', function() { + var docxPath = path.join(__dirname, "test-data/tiny-picture.docx"); + var convertImage = mammoth.images.imgElement(function(element) { + return element.read("base64").then(function(encodedImage) { + return {src: encodedImage.substring(0, 2) + "," + element.contentType}; }); }); - - test('simple list is converted to list elements', function() { - var docxPath = path.join(__dirname, "test-data/simple-list.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, '<ul><li>Apple</li><li>Banana</li></ul>'); - }); + return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) { + assert.equal(result.value, '<p><img src="iV,image/png" /></p>'); }); - - test('word tables are converted to html tables', function() { - var docxPath = path.join(__dirname, "test-data/tables.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - var expectedHtml = "<p>Above</p>" + - "<table>" + - "<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" + - "<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" + - "</table>" + - "<p>Below</p>"; - assert.equal(result.value, expectedHtml); - assert.deepEqual(result.messages, []); - }); +}); + +test('images stored outside of document are included in output', function() { + var docxPath = path.join(__dirname, "test-data/external-picture.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>'); + assert.deepEqual(result.messages, []); }); - - test('footnotes are appended to text', function() { - // TODO: don't duplicate footnotes with multiple references - var docxPath = path.join(__dirname, "test-data/footnotes.docx"); - var options = { - idPrefix: "doc-42-" - }; - return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { - var expectedOutput = '<p>Ouch' + - '<sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup>.' + - '<sup><a href="#doc-42-footnote-2" id="doc-42-footnote-ref-2">[2]</a></sup></p>' + - '<ol><li id="doc-42-footnote-1"><p> A tachyon walks into a bar. <a href="#doc-42-footnote-ref-1">↑</a></p></li>' + - '<li id="doc-42-footnote-2"><p> Fin. <a href="#doc-42-footnote-ref-2">↑</a></p></li></ol>'; - assert.equal(result.value, expectedOutput); - assert.deepEqual(result.messages, []); - }); +}); + +test('error if images stored outside of document are specified when passing file without path', function() { + var docxPath = path.join(__dirname, "test-data/external-picture.docx"); + var buffer = fs.readFileSync(docxPath); + return mammoth.convertToHtml({buffer: buffer}).then(function(result) { + assert.equal(result.value, ''); + assert.equal(result.messages[0].message, "could not find external image 'tiny-picture.png', path of input document is unknown"); + assert.equal(result.messages[0].type, "error"); }); - - test('endnotes are appended to text', function() { - var docxPath = path.join(__dirname, "test-data/endnotes.docx"); - var options = { - idPrefix: "doc-42-" - }; - return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { - var expectedOutput = '<p>Ouch' + - '<sup><a href="#doc-42-endnote-2" id="doc-42-endnote-ref-2">[1]</a></sup>.' + - '<sup><a href="#doc-42-endnote-3" id="doc-42-endnote-ref-3">[2]</a></sup></p>' + - '<ol><li id="doc-42-endnote-2"><p> A tachyon walks into a bar. <a href="#doc-42-endnote-ref-2">↑</a></p></li>' + - '<li id="doc-42-endnote-3"><p> Fin. <a href="#doc-42-endnote-ref-3">↑</a></p></li></ol>'; - assert.equal(result.value, expectedOutput); - assert.deepEqual(result.messages, []); - }); +}); + +test('simple list is converted to list elements', function() { + var docxPath = path.join(__dirname, "test-data/simple-list.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, '<ul><li>Apple</li><li>Banana</li></ul>'); }); - - test('relationships are handled properly in footnotes', function() { - var docxPath = path.join(__dirname, "test-data/footnote-hyperlink.docx"); - var options = { - idPrefix: "doc-42-" - }; - return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { - var expectedOutput = - '<p><sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup></p>' + - '<ol><li id="doc-42-footnote-1"><p> <a href="http://www.example.com">Example</a> <a href="#doc-42-footnote-ref-1">↑</a></p></li></ol>'; - assert.equal(result.value, expectedOutput); - assert.deepEqual(result.messages, []); - }); +}); + +test('word tables are converted to html tables', function() { + var docxPath = path.join(__dirname, "test-data/tables.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + var expectedHtml = "<p>Above</p>" + + "<table>" + + "<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" + + "<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" + + "</table>" + + "<p>Below</p>"; + assert.equal(result.value, expectedHtml); + assert.deepEqual(result.messages, []); }); - - test('textboxes are read', function() { - var docxPath = path.join(__dirname, "test-data/text-box.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - var expectedOutput = '<p>Datum plane</p>'; - assert.equal(result.value, expectedOutput); - }); +}); + +test('footnotes are appended to text', function() { + // TODO: don't duplicate footnotes with multiple references + var docxPath = path.join(__dirname, "test-data/footnotes.docx"); + var options = { + idPrefix: "doc-42-" + }; + return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { + var expectedOutput = '<p>Ouch' + + '<sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup>.' + + '<sup><a href="#doc-42-footnote-2" id="doc-42-footnote-ref-2">[2]</a></sup></p>' + + '<ol><li id="doc-42-footnote-1"><p> A tachyon walks into a bar. <a href="#doc-42-footnote-ref-1">↑</a></p></li>' + + '<li id="doc-42-footnote-2"><p> Fin. <a href="#doc-42-footnote-ref-2">↑</a></p></li></ol>'; + assert.equal(result.value, expectedOutput); + assert.deepEqual(result.messages, []); }); - - test('underline is ignored by default', function() { - var docxPath = path.join(__dirname, "test-data/underline.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, '<p><strong>The Sunset Tree</strong></p>'); - }); +}); + +test('endnotes are appended to text', function() { + var docxPath = path.join(__dirname, "test-data/endnotes.docx"); + var options = { + idPrefix: "doc-42-" + }; + return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { + var expectedOutput = '<p>Ouch' + + '<sup><a href="#doc-42-endnote-2" id="doc-42-endnote-ref-2">[1]</a></sup>.' + + '<sup><a href="#doc-42-endnote-3" id="doc-42-endnote-ref-3">[2]</a></sup></p>' + + '<ol><li id="doc-42-endnote-2"><p> A tachyon walks into a bar. <a href="#doc-42-endnote-ref-2">↑</a></p></li>' + + '<li id="doc-42-endnote-3"><p> Fin. <a href="#doc-42-endnote-ref-3">↑</a></p></li></ol>'; + assert.equal(result.value, expectedOutput); + assert.deepEqual(result.messages, []); }); - - test('underline can be configured with style mapping', function() { - var docxPath = path.join(__dirname, "test-data/underline.docx"); - return mammoth.convertToHtml({path: docxPath}, {styleMap: "u => em"}).then(function(result) { - assert.equal(result.value, '<p><strong>The <em>Sunset</em> Tree</strong></p>'); - }); +}); + +test('relationships are handled properly in footnotes', function() { + var docxPath = path.join(__dirname, "test-data/footnote-hyperlink.docx"); + var options = { + idPrefix: "doc-42-" + }; + return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { + var expectedOutput = + '<p><sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup></p>' + + '<ol><li id="doc-42-footnote-1"><p> <a href="http://www.example.com">Example</a> <a href="#doc-42-footnote-ref-1">↑</a></p></li></ol>'; + assert.equal(result.value, expectedOutput); + assert.deepEqual(result.messages, []); }); - - test('strikethrough is converted to <s> by default', function() { - var docxPath = path.join(__dirname, "test-data/strikethrough.docx"); - return mammoth.convertToHtml({path: docxPath}).then(function(result) { - assert.equal(result.value, "<p><s>Today's Special: Salmon</s> Sold out</p>"); - }); +}); + +test('when style mapping is defined for comment references then comments are included', function() { + var docxPath = path.join(__dirname, "test-data/comments.docx"); + var options = { + idPrefix: "doc-42-", + styleMap: "comment-reference => sup" + }; + return mammoth.convertToHtml({path: docxPath}, options).then(function(result) { + var expectedOutput = ( + '<p>Ouch' + + '<sup><a href="#doc-42-comment-0" id="doc-42-comment-ref-0">[MW1]</a></sup>.' + + '<sup><a href="#doc-42-comment-2" id="doc-42-comment-ref-2">[MW2]</a></sup></p>' + + '<dl><dt id="doc-42-comment-0">Comment [MW1]</dt><dd><p>A tachyon walks into a bar. <a href="#doc-42-comment-ref-0">↑</a></p></dd>' + + '<dt id="doc-42-comment-2">Comment [MW2]</dt><dd><p>Fin. <a href="#doc-42-comment-ref-2">↑</a></p></dd></dl>' + ); + assert.equal(result.value, expectedOutput); + assert.deepEqual(result.messages, []); }); - - test('strikethrough conversion can be configured with style mappings', function() { - var docxPath = path.join(__dirname, "test-data/strikethrough.docx"); - return mammoth.convertToHtml({path: docxPath}, {styleMap: "strike => del"}).then(function(result) { - assert.equal(result.value, "<p><del>Today's Special: Salmon</del> Sold out</p>"); - }); +}); + +test('textboxes are read', function() { + var docxPath = path.join(__dirname, "test-data/text-box.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + var expectedOutput = '<p>Datum plane</p>'; + assert.equal(result.value, expectedOutput); }); - - test('indentation is used if prettyPrint is true', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return mammoth.convertToHtml({path: docxPath}, {prettyPrint: true}).then(function(result) { - assert.equal(result.value, "<p>\n Walking on imported air\n</p>"); - assert.deepEqual(result.messages, []); - }); +}); + +test('underline is ignored by default', function() { + var docxPath = path.join(__dirname, "test-data/underline.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, '<p><strong>The Sunset Tree</strong></p>'); }); - - test('using styleMapping throws error', function() { - try { - mammoth.styleMapping(); - } catch (error) { - assert.equal( - error.message, - 'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")' - ); - } +}); + +test('underline can be configured with style mapping', function() { + var docxPath = path.join(__dirname, "test-data/underline.docx"); + return mammoth.convertToHtml({path: docxPath}, {styleMap: "u => em"}).then(function(result) { + assert.equal(result.value, '<p><strong>The <em>Sunset</em> Tree</strong></p>'); + }); +}); + +test('strikethrough is converted to <s> by default', function() { + var docxPath = path.join(__dirname, "test-data/strikethrough.docx"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.equal(result.value, "<p><s>Today's Special: Salmon</s> Sold out</p>"); + }); +}); + +test('strikethrough conversion can be configured with style mappings', function() { + var docxPath = path.join(__dirname, "test-data/strikethrough.docx"); + return mammoth.convertToHtml({path: docxPath}, {styleMap: "strike => del"}).then(function(result) { + assert.equal(result.value, "<p><del>Today's Special: Salmon</del> Sold out</p>"); + }); +}); + +test('indentation is used if prettyPrint is true', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return mammoth.convertToHtml({path: docxPath}, {prettyPrint: true}).then(function(result) { + assert.equal(result.value, "<p>\n Walking on imported air\n</p>"); + assert.deepEqual(result.messages, []); + }); +}); + +test('using styleMapping throws error', function() { + try { + mammoth.styleMapping(); + } catch (error) { + assert.equal( + error.message, + 'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")' + ); + } +}); + +test('can convert single paragraph to markdown', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return mammoth.convertToMarkdown({path: docxPath}).then(function(result) { + assert.equal(result.value, "Walking on imported air\n\n"); + assert.deepEqual(result.messages, []); + }); +}); + +test('extractRawText only retains raw text', function() { + var docxPath = path.join(__dirname, "test-data/simple-list.docx"); + return mammoth.extractRawText({path: docxPath}).then(function(result) { + assert.equal(result.value, 'Apple\n\nBanana\n\n'); }); - - test('can convert single paragraph to markdown', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return mammoth.convertToMarkdown({path: docxPath}).then(function(result) { +}); + +test('extractRawText can use .docx files represented by a Buffer', function() { + var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); + return promises.nfcall(fs.readFile, docxPath) + .then(function(buffer) { + return mammoth.extractRawText({buffer: buffer}); + }) + .then(function(result) { assert.equal(result.value, "Walking on imported air\n\n"); assert.deepEqual(result.messages, []); }); - }); +}); - test('extractRawText only retains raw text', function() { - var docxPath = path.join(__dirname, "test-data/simple-list.docx"); - return mammoth.extractRawText({path: docxPath}).then(function(result) { - assert.equal(result.value, 'Apple\n\nBanana\n\n'); - }); - }); - - test('extractRawText can use .docx files represented by a Buffer', function() { - var docxPath = path.join(__dirname, "test-data/single-paragraph.docx"); - return promises.nfcall(fs.readFile, docxPath) - .then(function(buffer) { - return mammoth.extractRawText({buffer: buffer}); - }) - .then(function(result) { - assert.equal(result.value, "Walking on imported air\n\n"); - assert.deepEqual(result.messages, []); - }); + +test('should throw error if file is not a valid docx document', function() { + var docxPath = path.join(__dirname, "test-data/empty.zip"); + return mammoth.convertToHtml({path: docxPath}).then(function(result) { + assert.ok(false, "Expected error"); + }, function(error) { + assert.equal(error.message, "Could not find word/document.xml in ZIP file. Are you sure this is a valid .docx file?"); }); }); diff --git a/test/options-reader.tests.js b/test/options-reader.tests.js index 94c864c..06e0764 100644 --- a/test/options-reader.tests.js +++ b/test/options-reader.tests.js @@ -3,46 +3,45 @@ var assert = require("assert"); var optionsReader = require("../lib/options-reader"); var standardOptions = optionsReader._standardOptions; var readOptions = optionsReader.readOptions; +var test = require("./test")(module); -describe('readOptions', function() { - it('standard options are used if options is undefined', function() { - assert.deepEqual(standardOptions, readOptions(undefined)); - }); - - it('standard options are used if options is empty', function() { - assert.deepEqual(standardOptions, readOptions({})); - }); - - it('custom style map as string is prepended to standard style map', function() { - var options = readOptions({ - styleMap: "p.SectionTitle => h2" - }); - assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); - assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); +test('standard options are used if options is undefined', function() { + assert.deepEqual(standardOptions, readOptions(undefined)); +}); + +test('standard options are used if options is empty', function() { + assert.deepEqual(standardOptions, readOptions({})); +}); + +test('custom style map as string is prepended to standard style map', function() { + var options = readOptions({ + styleMap: "p.SectionTitle => h2" }); - - it('custom style map as array is prepended to standard style map', function() { - var options = readOptions({ - styleMap: ["p.SectionTitle => h2"] - }); - assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); - assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); + assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); + assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); +}); + +test('custom style map as array is prepended to standard style map', function() { + var options = readOptions({ + styleMap: ["p.SectionTitle => h2"] }); - - it('lines starting with # in custom style map are ignored', function() { - var options = readOptions({ - styleMap: "# p.SectionTitle => h3\np.SectionTitle => h2" - }); - assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); - assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); + assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); + assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); +}); + +test('lines starting with # in custom style map are ignored', function() { + var options = readOptions({ + styleMap: "# p.SectionTitle => h3\np.SectionTitle => h2" }); - - it('default style mappings are ignored if includeDefaultStyleMap is false', function() { - var options = readOptions({ - styleMap: "p.SectionTitle => h2", - includeDefaultStyleMap: false - }); - assert.deepEqual(["p.SectionTitle => h2"], options.styleMap); + assert.deepEqual("p.SectionTitle => h2", options.styleMap[0]); + assert.deepEqual(standardOptions.styleMap, options.styleMap.slice(1)); +}); + +test('default style mappings are ignored if includeDefaultStyleMap is false', function() { + var options = readOptions({ + styleMap: "p.SectionTitle => h2", + includeDefaultStyleMap: false }); + assert.deepEqual(["p.SectionTitle => h2"], options.styleMap); }); diff --git a/test/results.tests.js b/test/results.tests.js index 3c0df78..2ce470b 100644 --- a/test/results.tests.js +++ b/test/results.tests.js @@ -2,16 +2,14 @@ var assert = require("assert"); var results = require("../lib/results"); var Result = results.Result; -var test = require("./testing").test; +var test = require("./test")(module); -describe("Result.combine", function() { - test("removes any duplicate messages", function() { - var first = new Result(null, [results.warning("Warning...")]); - var second = new Result(null, [results.warning("Warning...")]); - - var combined = Result.combine([first, second]); - - assert.deepEqual(combined.messages, [results.warning("Warning...")]); - }); +test("Result.combine removes any duplicate messages", function() { + var first = new Result(null, [results.warning("Warning...")]); + var second = new Result(null, [results.warning("Warning...")]); + + var combined = Result.combine([first, second]); + + assert.deepEqual(combined.messages, [results.warning("Warning...")]); }); diff --git a/test/style-reader.tests.js b/test/style-reader.tests.js index 7265e8f..19f9c00 100644 --- a/test/style-reader.tests.js +++ b/test/style-reader.tests.js @@ -3,7 +3,7 @@ var htmlPaths = require("../lib/html-paths"); var documentMatchers = require("../lib/document-matchers"); var styleReader = require("../lib/style-reader"); var results = require("../lib/results"); -var test = require("./testing").test; +var test = require("./test")(module); var readHtmlPath = styleReader.readHtmlPath; @@ -11,136 +11,140 @@ var readDocumentMatcher = styleReader.readDocumentMatcher; var readStyle = styleReader.readStyle; -describe('styleReader.readHtmlPath', function() { - test('reads empty path', function() { +test('styleReader.readHtmlPath', { + 'reads empty path': function() { assertHtmlPath("", htmlPaths.empty); - }); + }, - test('reads single element', function() { + 'reads single element': function() { assertHtmlPath("p", htmlPaths.elements(["p"])); - }); + }, - test('reads choice of elements', function() { + 'reads choice of elements': function() { assertHtmlPath( "ul|ol", htmlPaths.elements([ htmlPaths.element(["ul", "ol"]) ]) ); - }); + }, - test('reads nested elements', function() { + 'reads nested elements': function() { assertHtmlPath("ul > li", htmlPaths.elements(["ul", "li"])); - }); + }, - test('reads class on element', function() { + 'reads class on element': function() { var expected = htmlPaths.elements([ htmlPaths.element("p", {"class": "tip"}) ]); assertHtmlPath("p.tip", expected); - }); + }, - test('reads multiple classes on element', function() { + 'reads multiple classes on element': function() { var expected = htmlPaths.elements([ htmlPaths.element("p", {"class": "tip help"}) ]); assertHtmlPath("p.tip.help", expected); - }); + }, - test('reads when element must be fresh', function() { + 'reads when element must be fresh': function() { var expected = htmlPaths.elements([ htmlPaths.element("p", {}, {"fresh": true}) ]); assertHtmlPath("p:fresh", expected); - }); + }, - function assertHtmlPath(input, expected) { - assert.deepEqual(readHtmlPath(input), results.success(expected)); + 'reads ignore element': function() { + assertHtmlPath("!", htmlPaths.ignore); } }); -describe("styleReader.readDocumentMatcher", function() { - test("reads plain paragraph", function() { +function assertHtmlPath(input, expected) { + assert.deepEqual(readHtmlPath(input), results.success(expected)); +} + +test("styleReader.readDocumentMatcher", { + "reads plain paragraph": function() { assertDocumentMatcher("p", documentMatchers.paragraph()); - }); + }, - test("reads paragraph with style ID", function() { + "reads paragraph with style ID": function() { assertDocumentMatcher( "p.Heading1", documentMatchers.paragraph({styleId: "Heading1"}) ); - }); + }, - test("reads paragraph with style name", function() { + "reads paragraph with style name": function() { assertDocumentMatcher( "p[style-name='Heading 1']", documentMatchers.paragraph({styleName: "Heading 1"}) ); - }); + }, - test("reads p:ordered-list(1) as ordered list with index of 0", function() { + "reads p:ordered-list(1) as ordered list with index of 0": function() { assertDocumentMatcher( "p:ordered-list(1)", documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}}) ); - }); + }, - test("reads p:unordered-list(1) as unordered list with index of 0", function() { + "reads p:unordered-list(1) as unordered list with index of 0": function() { assertDocumentMatcher( "p:unordered-list(1)", documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 0}}) ); - }); + }, - test("reads plain run", function() { + "reads plain run": function() { assertDocumentMatcher( "r", documentMatchers.run() ); - }); + }, - test("reads bold", function() { + "reads bold": function() { assertDocumentMatcher( "b", documentMatchers.bold ); - }); + }, - test("reads italic", function() { + "reads italic": function() { assertDocumentMatcher( "i", documentMatchers.italic ); - }); + }, - test("reads underline", function() { + "reads underline": function() { assertDocumentMatcher( "u", documentMatchers.underline ); - }); + }, - test("reads strikethrough", function() { + "reads strikethrough": function() { assertDocumentMatcher( "strike", documentMatchers.strikethrough ); - }); + }, - test("reads comment-reference", function() { + "reads comment-reference": function() { assertDocumentMatcher( "comment-reference", documentMatchers.commentReference ); - }); - - function assertDocumentMatcher(input, expected) { - assert.deepEqual(readDocumentMatcher(input), results.success(expected)); } }); + +function assertDocumentMatcher(input, expected) { + assert.deepEqual(readDocumentMatcher(input), results.success(expected)); +} -describe("styleReader.read", function() { - test("document matcher is mapped to HTML path using arrow", function() { +test("styleReader.read", { + "document matcher is mapped to HTML path using arrow": function() { assertStyleMapping( "p => h1", { @@ -148,9 +152,9 @@ describe("styleReader.read", function() { to: htmlPaths.elements(["h1"]) } ); - }); + }, - test("reads style mapping with no HTML path", function() { + "reads style mapping with no HTML path": function() { assertStyleMapping( "r =>", { @@ -158,9 +162,9 @@ describe("styleReader.read", function() { to: htmlPaths.empty } ); - }); - - function assertStyleMapping(input, expected) { - assert.deepEqual(readStyle(input), results.success(expected)); } }); + +function assertStyleMapping(input, expected) { + assert.deepEqual(readStyle(input), results.success(expected)); +} diff --git a/test/test-data/comments.docx b/test/test-data/comments.docx Binary files differnew file mode 100755 index 0000000..32cdae1 --- /dev/null +++ b/test/test-data/comments.docx diff --git a/test/test-data/empty.zip b/test/test-data/empty.zip Binary files differnew file mode 100644 index 0000000..15cb0ec --- /dev/null +++ b/test/test-data/empty.zip diff --git a/test/testing.js b/test/testing.js index d70f86e..cb37777 100644 --- a/test/testing.js +++ b/test/testing.js @@ -3,22 +3,12 @@ var fs = require("fs"); var promises = require("../lib/promises"); var _ = require("underscore"); -exports.test = test; exports.testPath = testPath; exports.testData = testData; exports.createFakeDocxFile = createFakeDocxFile; exports.createFakeFiles = createFakeFiles; -function test(name, func) { - it(name, function(done) { - var result = func(); - promises.when(result).then(function() { - done(); - }).done(); - }); -} - function testPath(filename) { return path.join(__dirname, "test-data", filename); } diff --git a/test/unzip.tests.js b/test/unzip.tests.js index 994fb68..4e1bd61 100644 --- a/test/unzip.tests.js +++ b/test/unzip.tests.js @@ -2,39 +2,37 @@ var fs = require("fs"); var assert = require("assert"); var path = require("path"); -var test = require("./testing").test; +var test = require("./test")(module); var unzip = require("../lib/unzip"); var promises = require("../lib/promises"); -describe("unzip", function() { - test("unzip fails if given empty object", function() { - return unzip.openZip({}).then(function() { - assert.ok(false, "Expected failure"); - }, function(error) { - assert.equal("Could not find file in options", error.message); - }); +test("unzip fails if given empty object", function() { + return unzip.openZip({}).then(function() { + assert.ok(false, "Expected failure"); + }, function(error) { + assert.equal("Could not find file in options", error.message); + }); +}); + +test("unzip can open local zip file", function() { + var zipPath = path.join(__dirname, "test-data/hello.zip"); + return unzip.openZip({path: zipPath}).then(function(zipFile) { + return zipFile.read("hello", "utf8"); + }).then(function(contents) { + assert.equal(contents, "Hello world\n"); }); - - test("unzip can open local zip file", function() { - var zipPath = path.join(__dirname, "test-data/hello.zip"); - return unzip.openZip({path: zipPath}).then(function(zipFile) { +}); + +test('unzip can open Buffer', function() { + var zipPath = path.join(__dirname, "test-data/hello.zip"); + return promises.nfcall(fs.readFile, zipPath) + .then(function(buffer) { + return unzip.openZip({buffer: buffer}); + }) + .then(function(zipFile) { return zipFile.read("hello", "utf8"); - }).then(function(contents) { + }) + .then(function(contents) { assert.equal(contents, "Hello world\n"); }); - }); - - test('unzip can open Buffer', function() { - var zipPath = path.join(__dirname, "test-data/hello.zip"); - return promises.nfcall(fs.readFile, zipPath) - .then(function(buffer) { - return unzip.openZip({buffer: buffer}); - }) - .then(function(zipFile) { - return zipFile.read("hello", "utf8"); - }) - .then(function(contents) { - assert.equal(contents, "Hello world\n"); - }); - }); }); diff --git a/test/writers/html-writer.tests.js b/test/writers/html-writer.tests.js index 41d8839..b5cdf4e 100644 --- a/test/writers/html-writer.tests.js +++ b/test/writers/html-writer.tests.js @@ -1,117 +1,115 @@ var assert = require("assert"); -var test = require("../testing").test; +var test = require("../test")(module); var htmlWriter = require("../../lib/writers/html-writer"); -describe('html-writer', function() { - test('can generate simple paragraph', function() { - var writer = htmlWriter.writer(); - writer.open("p"); - writer.text("Hello"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>Hello</p>"); - }); - - test('can nest elements', function() { - var writer = htmlWriter.writer(); - writer.open("ul"); - writer.open("li"); - writer.text("One"); - writer.close("li"); - writer.open("li"); - writer.text("Two"); - writer.close("li"); - writer.close("ul"); - return assert.equal(writer.asString(), "<ul><li>One</li><li>Two</li></ul>"); - }); - - test('indents text if prettyPrint is true', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.text("One"); - writer.close("p"); - writer.open("p"); - writer.text("Two"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n One\n</p>\n<p>\n Two\n</p>"); - }); - - test('indents element if prettyPrint is true', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("ul"); - writer.open("li"); - writer.text("One"); - writer.close("li"); - writer.open("li"); - writer.text("Two"); - writer.close("li"); - writer.close("ul"); - return assert.equal(writer.asString(), "<ul>\n <li>\n One\n </li>\n <li>\n Two\n </li>\n</ul>"); - }); - - test('when prettyPrint is true inner elements do not have new lines', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.text("One"); - writer.open("em"); - writer.text("Two"); - writer.close("em"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n One<em>Two</em>\n</p>"); - }); - - test('indents closing element correctly when nested inside another indented element', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("div"); - writer.open("div"); - writer.open("div"); - writer.text("Hello"); - writer.close("div"); - writer.close("div"); - writer.close("div"); - return assert.equal(writer.asString(), "<div>\n <div>\n <div>\n Hello\n </div>\n </div>\n</div>"); - }); - - test('newlines in text are indented', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.text("One\nTwo"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>"); - }); - - test('run of text has only one new line', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.text("One"); - writer.text("Two"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>"); - }); - - test('run of html has only one new line', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.append("One"); - writer.append("Two"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>"); - }); - - test('self closing elements are indented', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.selfClosing("br"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n <br />\n</p>"); - }); - - test('newlines in appended HTML are indented', function() { - var writer = htmlWriter.writer({prettyPrint: true}); - writer.open("p"); - writer.append("One\nTwo"); - writer.close("p"); - return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>"); - }); +test('can generate simple paragraph', function() { + var writer = htmlWriter.writer(); + writer.open("p"); + writer.text("Hello"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>Hello</p>"); +}); + +test('can nest elements', function() { + var writer = htmlWriter.writer(); + writer.open("ul"); + writer.open("li"); + writer.text("One"); + writer.close("li"); + writer.open("li"); + writer.text("Two"); + writer.close("li"); + writer.close("ul"); + return assert.equal(writer.asString(), "<ul><li>One</li><li>Two</li></ul>"); +}); + +test('indents text if prettyPrint is true', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.text("One"); + writer.close("p"); + writer.open("p"); + writer.text("Two"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n One\n</p>\n<p>\n Two\n</p>"); +}); + +test('indents element if prettyPrint is true', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("ul"); + writer.open("li"); + writer.text("One"); + writer.close("li"); + writer.open("li"); + writer.text("Two"); + writer.close("li"); + writer.close("ul"); + return assert.equal(writer.asString(), "<ul>\n <li>\n One\n </li>\n <li>\n Two\n </li>\n</ul>"); +}); + +test('when prettyPrint is true inner elements do not have new lines', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.text("One"); + writer.open("em"); + writer.text("Two"); + writer.close("em"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n One<em>Two</em>\n</p>"); +}); + +test('indents closing element correctly when nested inside another indented element', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("div"); + writer.open("div"); + writer.open("div"); + writer.text("Hello"); + writer.close("div"); + writer.close("div"); + writer.close("div"); + return assert.equal(writer.asString(), "<div>\n <div>\n <div>\n Hello\n </div>\n </div>\n</div>"); +}); + +test('newlines in text are indented', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.text("One\nTwo"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>"); +}); + +test('run of text has only one new line', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.text("One"); + writer.text("Two"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>"); +}); + +test('run of html has only one new line', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.append("One"); + writer.append("Two"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n OneTwo\n</p>"); +}); + +test('self closing elements are indented', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.selfClosing("br"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n <br />\n</p>"); +}); + +test('newlines in appended HTML are indented', function() { + var writer = htmlWriter.writer({prettyPrint: true}); + writer.open("p"); + writer.append("One\nTwo"); + writer.close("p"); + return assert.equal(writer.asString(), "<p>\n One\n Two\n</p>"); }); diff --git a/test/writers/markdown-writer.tests.js b/test/writers/markdown-writer.tests.js index 38d2afe..6d078c5 100644 --- a/test/writers/markdown-writer.tests.js +++ b/test/writers/markdown-writer.tests.js @@ -1,306 +1,304 @@ var assert = require("assert"); -var test = require("../testing").test; +var test = require("../test")(module); var mdWriter = require("../../lib/writers/markdown-writer"); -describe('markdown-writer', function() { - test('special markdown characters are escaped', function() { - var writer = mdWriter.writer(); - writer.text("\\*"); - return assert.equal(writer.asString(), "\\\\\\*"); - }); - - test('unrecognised elements are treated as normal text', function() { - var writer = mdWriter.writer(); - writer.open("blah"); - writer.text("Hello"); - writer.close("blah"); - return assert.equal(writer.asString(), "Hello"); - }); - - test('paragraphs are terminated with double new line', function() { - var writer = mdWriter.writer(); - writer.open("p"); - writer.text("Hello"); - writer.close("p"); - return assert.equal(writer.asString(), "Hello\n\n"); - }); - - test('h1 elements are converted to heading with leading hash', function() { - var writer = mdWriter.writer(); - writer.open("h1"); - writer.text("Hello"); - writer.close("h1"); - return assert.equal(writer.asString(), "# Hello\n\n"); - }); - - test('h6 elements are converted to heading with six leading hashes', function() { - var writer = mdWriter.writer(); - writer.open("h6"); - writer.text("Hello"); - writer.close("h6"); - return assert.equal(writer.asString(), "###### Hello\n\n"); - }); - - test('br is written as two spaces followed by new line', function() { - var writer = mdWriter.writer(); - writer.text("Hello World"); - writer.selfClosing("br"); - return assert.equal(writer.asString(), "Hello World \n"); - }); - - test('strong text is surrounded by two underscores', function() { - var writer = mdWriter.writer(); - writer.text("Hello "); - writer.open("strong"); - writer.text("World"); - writer.close("strong"); - return assert.equal(writer.asString(), "Hello __World__"); - }); - - test('emphasised text is surrounded by one asterix', function() { - var writer = mdWriter.writer(); - writer.text("Hello "); - writer.open("em"); - writer.text("World"); - writer.close("em"); - return assert.equal(writer.asString(), "Hello *World*"); - }); - - test('anchor tags are written as hyperlinks', function() { - var writer = mdWriter.writer(); - writer.open("a", {"href": "http://example.com"}); - writer.text("Hello"); - writer.close("a"); - return assert.equal(writer.asString(), "[Hello](http://example.com)"); - }); - - test('anchor tags without href attribute are treated as ordinary text', function() { - var writer = mdWriter.writer(); - writer.open("a"); - writer.text("Hello"); - writer.close("a"); - return assert.equal(writer.asString(), "Hello"); - }); - - test('elements with IDs have anchor tags with IDs appended to start of markdown element', function() { - var writer = mdWriter.writer(); - writer.open("h1", {id: "start"}); - writer.text("Hello"); - writer.close("h1"); - return assert.equal(writer.asString(), '# <a id="start"></a>Hello\n\n'); - }); - - test('links have anchors before opening square bracket', function() { - var writer = mdWriter.writer(); - writer.open("a", {href: "http://example.com", id: "start"}); - writer.text("Hello"); - writer.close("a"); - return assert.equal(writer.asString(), '<a id="start"></a>[Hello](http://example.com)'); - }); - - test('can generate images', function() { - var writer = mdWriter.writer(); - writer.selfClosing("img", {"src": "http://example.com/image.jpg", "alt": "Alt Text"}); - return assert.equal(writer.asString(), ""); - }); - - test('can generate images with missing alt attribute', function() { - var writer = mdWriter.writer(); - writer.selfClosing("img", {"src": "http://example.com/image.jpg"}); - return assert.equal(writer.asString(), ""); - }); - - test('can generate images with missing src attribute', function() { - var writer = mdWriter.writer(); - writer.selfClosing("img", {"alt": "Alt Text"}); - return assert.equal(writer.asString(), "![Alt Text]()"); - }); - - test("doesn't display empty images", function() { - var writer = mdWriter.writer(); - writer.selfClosing("img"); - return assert.equal(writer.asString(), ""); - }); - - test('list item outside of list is treated as unordered list', function() { - var writer = mdWriter.writer(); - writer.open("li"); - writer.text("Hello"); - writer.close("li"); - return assert.equal(writer.asString(), "- Hello\n"); - }); - - test('can generate an ordered list', function() { - var writer = mdWriter.writer(); - writer.open("ol"); - writer.open("li"); - writer.text("Hello"); - writer.close("li"); - writer.open("li"); - writer.text("World"); - writer.close("li"); - writer.close("ol"); - return assert.equal(writer.asString(), "1. Hello\n2. World\n\n"); - }); - - test('can generate an unordered list', function() { - var writer = mdWriter.writer(); - writer.open("ul"); - writer.open("li"); - writer.text("Hello"); - writer.close("li"); - writer.open("li"); - writer.text("World"); - writer.close("li"); - writer.close("ul"); - return assert.equal(writer.asString(), "- Hello\n- World\n\n"); - }); - - test('can generate a nested ordered list with correct numbering', function() { - var writer = mdWriter.writer(); - writer.open("ol"); - writer.open("li"); - writer.text("Outer One"); - - writer.open("ol"); - writer.open("li"); - writer.text("Nested One"); - writer.close("li"); - writer.open("li"); - writer.text("Nested Two"); - writer.close("li"); - writer.close("ol"); - - writer.close("li"); - writer.open("li"); - writer.text("Outer Two"); - writer.close("li"); - writer.close("ol"); - return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t2. Nested Two\n2. Outer Two\n\n"); - }); - - test('can generate a multi-level nested ordered list', function() { - var writer = mdWriter.writer(); - writer.open("ol"); - writer.open("li"); - writer.text("Outer One"); - - writer.open("ol"); - writer.open("li"); - writer.text("Nested One"); - - writer.open("ol"); - writer.open("li"); - writer.text("Inner One"); - writer.close("li"); - writer.close("ol"); - - writer.close("li"); - writer.close("ol"); - - writer.close("li"); - writer.close("ol"); - return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t\t1. Inner One\n\n"); - }); - - test('new ordered list resets numbering', function() { - var writer = mdWriter.writer(); - writer.open("ol"); - writer.open("li"); - writer.text("First"); - writer.close("li"); - writer.close("ol"); - - writer.open("p"); - writer.text("Hello"); - writer.close("p"); - - writer.open("ol"); - writer.open("li"); - writer.text("Second"); - writer.close("li"); - writer.close("ol"); - - return assert.equal(writer.asString(), "1. First\n\nHello\n\n1. Second\n\n"); - }); - - test('can generate a nested unordered list', function() { - var writer = mdWriter.writer(); - writer.open("ul"); - writer.open("li"); - writer.text("Outer One"); - - writer.open("ul"); - writer.open("li"); - writer.text("Nested One"); - writer.close("li"); - writer.open("li"); - writer.text("Nested Two"); - writer.close("li"); - writer.close("ul"); - - writer.close("li"); - writer.open("li"); - writer.text("Outer Two"); - writer.close("li"); - writer.close("ul"); - return assert.equal(writer.asString(), "- Outer One\n\t- Nested One\n\t- Nested Two\n- Outer Two\n\n"); - }); - - test('can nest inline elements', function() { - var writer = mdWriter.writer(); - writer.open("p"); - writer.text("Lorem "); - writer.open("strong"); - writer.text("ipsum "); - writer.open("em"); - writer.text("dolor"); - writer.close("em"); - writer.text(" sit"); - writer.close("strong"); - writer.text(" amet"); - writer.close("p"); - return assert.equal(writer.asString(), "Lorem __ipsum *dolor* sit__ amet\n\n"); - }); - - test('can emphasise list text', function() { - var writer = mdWriter.writer(); - writer.open("ol"); - writer.open("li"); - writer.text("Hello "); - writer.open("strong"); - writer.text("Strong"); - writer.close("strong"); - writer.text(" World"); - writer.close("li"); - writer.open("li"); - writer.text("Hello "); - writer.open("em"); - writer.text("Emphasis"); - writer.close("em"); - writer.text(" World"); - writer.close("li"); - writer.close("ol"); - return assert.equal(writer.asString(), "1. Hello __Strong__ World\n2. Hello *Emphasis* World\n\n"); - }); - - test('generates correct spacing between paragraphs and lists', function() { - var writer = mdWriter.writer(); - writer.open("p"); - writer.text("Hello World"); - writer.close("p"); - writer.open("ul"); - writer.open("li"); - writer.text("First Item"); - writer.close("li"); - writer.open("li"); - writer.text("Second Item"); - writer.close("li"); - writer.close("ul"); - writer.open("p"); - writer.text("Hello World"); - writer.close("p"); - return assert.equal(writer.asString(), "Hello World\n\n- First Item\n- Second Item\n\nHello World\n\n"); - }); +test('special markdown characters are escaped', function() { + var writer = mdWriter.writer(); + writer.text("\\*"); + return assert.equal(writer.asString(), "\\\\\\*"); +}); + +test('unrecognised elements are treated as normal text', function() { + var writer = mdWriter.writer(); + writer.open("blah"); + writer.text("Hello"); + writer.close("blah"); + return assert.equal(writer.asString(), "Hello"); +}); + +test('paragraphs are terminated with double new line', function() { + var writer = mdWriter.writer(); + writer.open("p"); + writer.text("Hello"); + writer.close("p"); + return assert.equal(writer.asString(), "Hello\n\n"); +}); + +test('h1 elements are converted to heading with leading hash', function() { + var writer = mdWriter.writer(); + writer.open("h1"); + writer.text("Hello"); + writer.close("h1"); + return assert.equal(writer.asString(), "# Hello\n\n"); +}); + +test('h6 elements are converted to heading with six leading hashes', function() { + var writer = mdWriter.writer(); + writer.open("h6"); + writer.text("Hello"); + writer.close("h6"); + return assert.equal(writer.asString(), "###### Hello\n\n"); +}); + +test('br is written as two spaces followed by new line', function() { + var writer = mdWriter.writer(); + writer.text("Hello World"); + writer.selfClosing("br"); + return assert.equal(writer.asString(), "Hello World \n"); +}); + +test('strong text is surrounded by two underscores', function() { + var writer = mdWriter.writer(); + writer.text("Hello "); + writer.open("strong"); + writer.text("World"); + writer.close("strong"); + return assert.equal(writer.asString(), "Hello __World__"); +}); + +test('emphasised text is surrounded by one asterix', function() { + var writer = mdWriter.writer(); + writer.text("Hello "); + writer.open("em"); + writer.text("World"); + writer.close("em"); + return assert.equal(writer.asString(), "Hello *World*"); +}); + +test('anchor tags are written as hyperlinks', function() { + var writer = mdWriter.writer(); + writer.open("a", {"href": "http://example.com"}); + writer.text("Hello"); + writer.close("a"); + return assert.equal(writer.asString(), "[Hello](http://example.com)"); +}); + +test('anchor tags without href attribute are treated as ordinary text', function() { + var writer = mdWriter.writer(); + writer.open("a"); + writer.text("Hello"); + writer.close("a"); + return assert.equal(writer.asString(), "Hello"); +}); + +test('elements with IDs have anchor tags with IDs appended to start of markdown element', function() { + var writer = mdWriter.writer(); + writer.open("h1", {id: "start"}); + writer.text("Hello"); + writer.close("h1"); + return assert.equal(writer.asString(), '# <a id="start"></a>Hello\n\n'); +}); + +test('links have anchors before opening square bracket', function() { + var writer = mdWriter.writer(); + writer.open("a", {href: "http://example.com", id: "start"}); + writer.text("Hello"); + writer.close("a"); + return assert.equal(writer.asString(), '<a id="start"></a>[Hello](http://example.com)'); +}); + +test('can generate images', function() { + var writer = mdWriter.writer(); + writer.selfClosing("img", {"src": "http://example.com/image.jpg", "alt": "Alt Text"}); + return assert.equal(writer.asString(), ""); +}); + +test('can generate images with missing alt attribute', function() { + var writer = mdWriter.writer(); + writer.selfClosing("img", {"src": "http://example.com/image.jpg"}); + return assert.equal(writer.asString(), ""); +}); + +test('can generate images with missing src attribute', function() { + var writer = mdWriter.writer(); + writer.selfClosing("img", {"alt": "Alt Text"}); + return assert.equal(writer.asString(), "![Alt Text]()"); +}); + +test("doesn't display empty images", function() { + var writer = mdWriter.writer(); + writer.selfClosing("img"); + return assert.equal(writer.asString(), ""); +}); + +test('list item outside of list is treated as unordered list', function() { + var writer = mdWriter.writer(); + writer.open("li"); + writer.text("Hello"); + writer.close("li"); + return assert.equal(writer.asString(), "- Hello\n"); +}); + +test('can generate an ordered list', function() { + var writer = mdWriter.writer(); + writer.open("ol"); + writer.open("li"); + writer.text("Hello"); + writer.close("li"); + writer.open("li"); + writer.text("World"); + writer.close("li"); + writer.close("ol"); + return assert.equal(writer.asString(), "1. Hello\n2. World\n\n"); +}); + +test('can generate an unordered list', function() { + var writer = mdWriter.writer(); + writer.open("ul"); + writer.open("li"); + writer.text("Hello"); + writer.close("li"); + writer.open("li"); + writer.text("World"); + writer.close("li"); + writer.close("ul"); + return assert.equal(writer.asString(), "- Hello\n- World\n\n"); +}); + +test('can generate a nested ordered list with correct numbering', function() { + var writer = mdWriter.writer(); + writer.open("ol"); + writer.open("li"); + writer.text("Outer One"); + + writer.open("ol"); + writer.open("li"); + writer.text("Nested One"); + writer.close("li"); + writer.open("li"); + writer.text("Nested Two"); + writer.close("li"); + writer.close("ol"); + + writer.close("li"); + writer.open("li"); + writer.text("Outer Two"); + writer.close("li"); + writer.close("ol"); + return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t2. Nested Two\n2. Outer Two\n\n"); +}); + +test('can generate a multi-level nested ordered list', function() { + var writer = mdWriter.writer(); + writer.open("ol"); + writer.open("li"); + writer.text("Outer One"); + + writer.open("ol"); + writer.open("li"); + writer.text("Nested One"); + + writer.open("ol"); + writer.open("li"); + writer.text("Inner One"); + writer.close("li"); + writer.close("ol"); + + writer.close("li"); + writer.close("ol"); + + writer.close("li"); + writer.close("ol"); + return assert.equal(writer.asString(), "1. Outer One\n\t1. Nested One\n\t\t1. Inner One\n\n"); +}); + +test('new ordered list resets numbering', function() { + var writer = mdWriter.writer(); + writer.open("ol"); + writer.open("li"); + writer.text("First"); + writer.close("li"); + writer.close("ol"); + + writer.open("p"); + writer.text("Hello"); + writer.close("p"); + + writer.open("ol"); + writer.open("li"); + writer.text("Second"); + writer.close("li"); + writer.close("ol"); + + return assert.equal(writer.asString(), "1. First\n\nHello\n\n1. Second\n\n"); +}); + +test('can generate a nested unordered list', function() { + var writer = mdWriter.writer(); + writer.open("ul"); + writer.open("li"); + writer.text("Outer One"); + + writer.open("ul"); + writer.open("li"); + writer.text("Nested One"); + writer.close("li"); + writer.open("li"); + writer.text("Nested Two"); + writer.close("li"); + writer.close("ul"); + + writer.close("li"); + writer.open("li"); + writer.text("Outer Two"); + writer.close("li"); + writer.close("ul"); + return assert.equal(writer.asString(), "- Outer One\n\t- Nested One\n\t- Nested Two\n- Outer Two\n\n"); +}); + +test('can nest inline elements', function() { + var writer = mdWriter.writer(); + writer.open("p"); + writer.text("Lorem "); + writer.open("strong"); + writer.text("ipsum "); + writer.open("em"); + writer.text("dolor"); + writer.close("em"); + writer.text(" sit"); + writer.close("strong"); + writer.text(" amet"); + writer.close("p"); + return assert.equal(writer.asString(), "Lorem __ipsum *dolor* sit__ amet\n\n"); +}); + +test('can emphasise list text', function() { + var writer = mdWriter.writer(); + writer.open("ol"); + writer.open("li"); + writer.text("Hello "); + writer.open("strong"); + writer.text("Strong"); + writer.close("strong"); + writer.text(" World"); + writer.close("li"); + writer.open("li"); + writer.text("Hello "); + writer.open("em"); + writer.text("Emphasis"); + writer.close("em"); + writer.text(" World"); + writer.close("li"); + writer.close("ol"); + return assert.equal(writer.asString(), "1. Hello __Strong__ World\n2. Hello *Emphasis* World\n\n"); +}); + +test('generates correct spacing between paragraphs and lists', function() { + var writer = mdWriter.writer(); + writer.open("p"); + writer.text("Hello World"); + writer.close("p"); + writer.open("ul"); + writer.open("li"); + writer.text("First Item"); + writer.close("li"); + writer.open("li"); + writer.text("Second Item"); + writer.close("li"); + writer.close("ul"); + writer.open("p"); + writer.text("Hello World"); + writer.close("p"); + return assert.equal(writer.asString(), "Hello World\n\n- First Item\n- Second Item\n\nHello World\n\n"); }); diff --git a/test/xml/reader.tests.js b/test/xml/reader.tests.js index 005753c..6b7d3a0 100644 --- a/test/xml/reader.tests.js +++ b/test/xml/reader.tests.js @@ -1,87 +1,85 @@ var assert = require("assert"); var xmlreader = require("../../lib/xml/reader"); -var test = require("../testing").test; +var test = require("../test")(module); -describe('xmlreader.readString', function() { - test('should read self-closing element', function() { - return xmlreader.readString("<body/>").then(function(result) { - assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result); - }); +test('should read self-closing element', function() { + return xmlreader.readString("<body/>").then(function(result) { + assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result); }); - - test('should read empty element with separate closing tag', function() { - return xmlreader.readString("<body></body>").then(function(result) { - assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result); - }); +}); + +test('should read empty element with separate closing tag', function() { + return xmlreader.readString("<body></body>").then(function(result) { + assert.deepEqual({type: "element", name: "body", attributes: {}, children: []}, result); }); - - test('should read attributes of tags', function() { - return xmlreader.readString('<body name="bob"/>').then(function(result) { - assert.deepEqual({name: "bob"}, result.attributes); - }); +}); + +test('should read attributes of tags', function() { + return xmlreader.readString('<body name="bob"/>').then(function(result) { + assert.deepEqual({name: "bob"}, result.attributes); }); - - test('can read text element', function() { - return xmlreader.readString('<body>Hello!</body>').then(function(result) { - assert.deepEqual({type: "text", value: "Hello!"}, result.children[0]); - }); +}); + +test('can read text element', function() { + return xmlreader.readString('<body>Hello!</body>').then(function(result) { + assert.deepEqual({type: "text", value: "Hello!"}, result.children[0]); }); - - test('should read element with children', function() { - return xmlreader.readString("<body><a/><b/></body>").then(function(root) { - assert.equal(2, root.children.length); - assert.equal("a", root.children[0].name); - assert.equal("b", root.children[1].name); - }); +}); + +test('should read element with children', function() { + return xmlreader.readString("<body><a/><b/></body>").then(function(root) { + assert.equal(2, root.children.length); + assert.equal("a", root.children[0].name); + assert.equal("b", root.children[1].name); }); +}); - test('unmapped namespaces URIs are included in braces as prefix', function() { - return xmlreader.readString('<w:body xmlns:w="word"/>').then(function(result) { - assert.deepEqual(result.name, "{word}body"); - }); +test('unmapped namespaces URIs are included in braces as prefix', function() { + return xmlreader.readString('<w:body xmlns:w="word"/>').then(function(result) { + assert.deepEqual(result.name, "{word}body"); }); +}); - test('mapped namespaces URIs are translated using map', function() { - var namespaceMap = { - "word": "x" - }; - - return xmlreader.readString('<w:body xmlns:w="word"/>', namespaceMap).then(function(result) { - assert.deepEqual(result.name, "x:body"); - }); +test('mapped namespaces URIs are translated using map', function() { + var namespaceMap = { + "word": "x" + }; + + return xmlreader.readString('<w:body xmlns:w="word"/>', namespaceMap).then(function(result) { + assert.deepEqual(result.name, "x:body"); }); +}); - test('namespace of attributes is mapped to prefix', function() { - var namespaceMap = { - "word": "x" - }; - var xmlString = '<w:body xmlns:w="word" w:val="Hello!"/>'; - return xmlreader.readString(xmlString, namespaceMap).then(function(result) { - assert.deepEqual(result.attributes["x:val"], "Hello!"); - }); +test('namespace of attributes is mapped to prefix', function() { + var namespaceMap = { + "word": "x" + }; + var xmlString = '<w:body xmlns:w="word" w:val="Hello!"/>'; + return xmlreader.readString(xmlString, namespaceMap).then(function(result) { + assert.deepEqual(result.attributes["x:val"], "Hello!"); }); - - test('can find first element with name', function() { - return xmlreader.readString('<body><a/><b index="1"/><b index="2"/></body>').then(function(result) { - var first = result.first("b"); - assert.equal("1", first.attributes.index); - }); +}); + +test('can find first element with name', function() { + return xmlreader.readString('<body><a/><b index="1"/><b index="2"/></body>').then(function(result) { + var first = result.first("b"); + assert.equal("1", first.attributes.index); }); - - test('whitespace between xml declaration and root tag is ignored', function() { - return xmlreader.readString('<?xml version="1.0" ?>\n<body/>').then(function(result) { - assert.deepEqual("body", result.name); - }); +}); + +test('whitespace between xml declaration and root tag is ignored', function() { + return xmlreader.readString('<?xml version="1.0" ?>\n<body/>').then(function(result) { + assert.deepEqual("body", result.name); }); - - test('error if XML is badly formed', function() { - return xmlreader.readString("<bo").then(function(result) { - throw new Error("Expected failure"); - }, function(error) { - assert.ok(error); - return 1; - }); +}); + +test('error if XML is badly formed', function() { + return xmlreader.readString("<bo").then(function(result) { + throw new Error("Expected failure"); + }, function(error) { + assert.ok(error); + return 1; }); }); diff --git a/test/xml/writer.tests.js b/test/xml/writer.tests.js index d41ddc1..0d87cae 100644 --- a/test/xml/writer.tests.js +++ b/test/xml/writer.tests.js @@ -2,77 +2,75 @@ var assert = require("assert"); var xml = require("../../lib/xml"); var writer = require("../../lib/xml/writer"); -var test = require("../testing").test; +var test = require("../test")(module); -describe('xml.writer', function() { - test('writing empty root element writes out xml declaration and empty root element', function() { - assertXmlString(xml.element("root"), {}, '<root/>'); - }); - - test('can write empty child elements', function() { - assertXmlString(xml.element("root", {}, [xml.element("album"), xml.element("single")]), {}, - '<root><album/><single/></root>'); - }); - - test('can write empty descendant elements', function() { - var element = xml.element("root", {}, [ - xml.element("album", {}, [ - xml.element("year"), - xml.element("song") - ]) - ]); - assertXmlString(element, {}, - '<root><album><year/><song/></album></root>'); - }); - - test('can write element attributes', function() { - var element = xml.element("root", {}, [ - xml.element("album", {"title": "Everything in Transit"}) - ]); - assertXmlString(element, {}, - '<root><album title="Everything in Transit"/></root>'); - }); - - test('can write text nodes', function() { - var element = xml.element("root", {}, [ - xml.element("album", {}, [ - xml.text("Everything in Transit") - ]) - ]); - assertXmlString(element, {}, - '<root><album>Everything in Transit</album></root>'); - }); - - test('can write root element with long-form prefix when URI is namespace', function() { - var element = xml.element("{music}root"); - assertXmlString(element, {"m": "music"}, - '<m:root xmlns:m="music"/>'); - }); - - test('can write child elements with long-form prefix when URI is namespace', function() { - var element = xml.element("root", {}, [ - xml.element("{music}album") - ]); - assertXmlString(element, {"m": "music"}, - '<root xmlns:m="music"><m:album/></root>'); - }); - - test('can write child elements with short-form prefix when URI is namespace', function() { - var element = xml.element("root", {}, [ - xml.element("m:album") - ]); - assertXmlString(element, {"m": "music"}, - '<root xmlns:m="music"><m:album/></root>'); - }); - - test('default namespace has key of empty string', function() { - var element = xml.element("root", {}, [ - xml.element("{music}album") - ]); - assertXmlString(element, {"": "music"}, - '<root xmlns="music"><album/></root>'); - }); +test('writing empty root element writes out xml declaration and empty root element', function() { + assertXmlString(xml.element("root"), {}, '<root/>'); +}); + +test('can write empty child elements', function() { + assertXmlString(xml.element("root", {}, [xml.element("album"), xml.element("single")]), {}, + '<root><album/><single/></root>'); +}); + +test('can write empty descendant elements', function() { + var element = xml.element("root", {}, [ + xml.element("album", {}, [ + xml.element("year"), + xml.element("song") + ]) + ]); + assertXmlString(element, {}, + '<root><album><year/><song/></album></root>'); +}); + +test('can write element attributes', function() { + var element = xml.element("root", {}, [ + xml.element("album", {"title": "Everything in Transit"}) + ]); + assertXmlString(element, {}, + '<root><album title="Everything in Transit"/></root>'); +}); + +test('can write text nodes', function() { + var element = xml.element("root", {}, [ + xml.element("album", {}, [ + xml.text("Everything in Transit") + ]) + ]); + assertXmlString(element, {}, + '<root><album>Everything in Transit</album></root>'); +}); + +test('can write root element with long-form prefix when URI is namespace', function() { + var element = xml.element("{music}root"); + assertXmlString(element, {"m": "music"}, + '<m:root xmlns:m="music"/>'); +}); + +test('can write child elements with long-form prefix when URI is namespace', function() { + var element = xml.element("root", {}, [ + xml.element("{music}album") + ]); + assertXmlString(element, {"m": "music"}, + '<root xmlns:m="music"><m:album/></root>'); +}); + +test('can write child elements with short-form prefix when URI is namespace', function() { + var element = xml.element("root", {}, [ + xml.element("m:album") + ]); + assertXmlString(element, {"m": "music"}, + '<root xmlns:m="music"><m:album/></root>'); +}); + +test('default namespace has key of empty string', function() { + var element = xml.element("root", {}, [ + xml.element("{music}album") + ]); + assertXmlString(element, {"": "music"}, + '<root xmlns="music"><album/></root>'); }); diff --git a/test/zipfile.tests.js b/test/zipfile.tests.js index 1a0ab99..c838548 100644 --- a/test/zipfile.tests.js +++ b/test/zipfile.tests.js @@ -2,20 +2,18 @@ var assert = require("assert"); var JSZip = require("jszip"); -var test = require("./testing").test; +var test = require("./test")(module); var zipfile = require("../lib/zipfile"); -describe("zipfile", function() { - test('file in zip can be read after being written', function() { - var zip = emptyZipFile(); - assert(!zip.exists("song/title")); - - zip.write("song/title", "Dark Blue"); - - assert(zip.exists("song/title")); - return zip.read("song/title", "utf8").then(function(contents) { - assert.equal(contents, "Dark Blue"); - }); +test('file in zip can be read after being written', function() { + var zip = emptyZipFile(); + assert(!zip.exists("song/title")); + + zip.write("song/title", "Dark Blue"); + + assert(zip.exists("song/title")); + return zip.read("song/title", "utf8").then(function(contents) { + assert.equal(contents, "Dark Blue"); }); }); |
