Last active
November 11, 2017 17:44
-
-
Save cburgmer/71ee75165ae3ee5ff854f65a0615e70a to your computer and use it in GitHub Desktop.
Mock Document for Google Apps Scripts, this implements a partial interface of https://developers.google.com/apps-script/reference/document/document
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var doc = fakeDocument( | |
aParagraph('some text'), | |
aBookmark('my-id') | |
); | |
doc.getBody().getNumChildren(); | |
// => 2 | |
doc.getBody().getChild(0).getText(); | |
// => 'some text' | |
doc.getBookmark('my-id').getId(); | |
// => 'my-id' | |
doc.getBookmark('my-id').getPosition().getElement().getParent().getType(); | |
// => DocumentApp.ElementType.BODY_SECTION |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var aBookmark = function (id) { | |
return {majorType: 'bookmark', id: id}; | |
}; | |
var aTable = function () { | |
var children = Array.prototype.slice.call(arguments); | |
return {majorType: 'element', type: DocumentApp.ElementType.TABLE, children: children}; | |
}; | |
var aTableRow = function () { | |
var children = Array.prototype.slice.call(arguments); | |
return {majorType: 'element', type: DocumentApp.ElementType.TABLE_ROW, children: children}; | |
}; | |
var aTableCell = function () { | |
var children = Array.prototype.slice.call(arguments); | |
return {majorType: 'element', type: DocumentApp.ElementType.TABLE_CELL, children: children}; | |
}; | |
var aParagraph = function () { | |
var text = arguments[0]; | |
var children = Array.prototype.slice.call(arguments, 1); | |
return {majorType: 'element', type: DocumentApp.ElementType.PARAGRAPH, text: text, children: children}; | |
}; | |
var anInlineImage = function () { | |
return {majorType: 'element', type: DocumentApp.ElementType.INLINE_IMAGE}; | |
}; | |
var fakeDocument = function () { | |
var childNodeDescriptions = Array.prototype.slice.call(arguments); | |
var findNodeDescription = function (nodeDescriptions, matcher) { | |
var i, candidate; | |
for(i = 0; i < nodeDescriptions.length; i++) { | |
candidate = nodeDescriptions[i]; | |
if (matcher(candidate)) { | |
return candidate; | |
} | |
// depth first | |
if (candidate.children) { | |
candidate = findNodeDescription(candidate.children, matcher); | |
if (candidate) { | |
return candidate; | |
} | |
} | |
} | |
}; | |
var matchBookmark = function (id) { | |
return function (candidateNodeDescription) { | |
return candidateNodeDescription.majorType === 'bookmark' && candidateNodeDescription.id === id; | |
}; | |
}; | |
var elementDescriptions = function (nodeDescriptions) { | |
return nodeDescriptions.filter(function (nodeDescription) { | |
return nodeDescription.majorType === 'element'; | |
}); | |
}; | |
var fakeElement = function (description) { | |
var element = {}; | |
element.getType = function () { return description.type; }; | |
element.getParent = function () { return fakeNode(description.parent); }; | |
element.getPreviousSibling = function () { | |
var siblings = elementDescriptions(description.parent.children); | |
var selfIdx = siblings.indexOf(description); | |
if (selfIdx === 0) { | |
return; | |
} | |
var previousSiblingDescription = siblings[selfIdx - 1]; | |
return fakeNode(previousSiblingDescription); | |
}; | |
element.removeFromParent = function () { | |
var siblings = description.parent.children; | |
var selfIdx = siblings.indexOf(description); | |
siblings.splice(selfIdx, 1); | |
}; | |
return element; | |
}; | |
var fakeContainerElement = function (description) { | |
var element = fakeElement(description); | |
element.getNumChildren = function () { return elementDescriptions(description.children).length; }; | |
element.getChild = function (idx) { return fakeNode(elementDescriptions(description.children)[idx]); }; | |
return element; | |
}; | |
var fakeTableElement = function (description) { | |
var element = fakeContainerElement(description); | |
element.getNumRows = function () { return description.children.length; }; | |
element.getRow = function (idx) { return fakeNode(description.children[idx]); }; | |
element.removeRow = function (idx) { description.children.splice(idx, 1) }; | |
return element; | |
}; | |
var fakeRowElement = function (description) { | |
var element = fakeContainerElement(description); | |
element.getCell = function (idx) { return fakeNode(description.children[idx]); }; | |
return element; | |
}; | |
var fakeCellElement = function (description) { | |
var element = fakeContainerElement(description); | |
element.getText = function () { | |
return elementDescriptions(description.children) | |
.map(function (descr) { return fakeNode(descr); }) | |
.map(function (node) { return node.getText(); }) | |
.join('\n'); | |
}; | |
return element; | |
}; | |
var fakeParagraphElement = function (description) { | |
var element = fakeContainerElement(description); | |
element.getText = function () { return description.text; }; | |
return element; | |
}; | |
var fakeBookmark = function (description) { | |
return { | |
getId: function () { return description.id; }, | |
getPosition: function () { | |
var siblings = description.parent.children; | |
var selfIdx = siblings.indexOf(description); | |
return { | |
getElement: function () { | |
var previousElementSiblings = elementDescriptions(siblings.slice(0, selfIdx)); | |
var nearestPreviousElementSibling = previousElementSiblings[previousElementSiblings.length - 1]; | |
return fakeNode(nearestPreviousElementSibling); | |
} | |
}; | |
}, | |
remove: function () { | |
var siblings = description.parent.children; | |
var selfIdx = siblings.indexOf(description); | |
siblings.splice(selfIdx, 1); | |
} | |
}; | |
}; | |
var fakeNode = function (description) { | |
if (description.majorType === 'bookmark') { | |
return fakeBookmark(description); | |
} else if (description.type === DocumentApp.ElementType.TABLE) { | |
return fakeTableElement(description); | |
} else if (description.type === DocumentApp.ElementType.TABLE_ROW) { | |
return fakeRowElement(description); | |
} else if (description.type === DocumentApp.ElementType.TABLE_CELL) { | |
return fakeCellElement(description); | |
} else if (description.type === DocumentApp.ElementType.PARAGRAPH) { | |
return fakeParagraphElement(description); | |
} else if (description.type === DocumentApp.ElementType.BODY_SECTION) { | |
return fakeContainerElement(description); | |
} else { | |
return fakeElement(description); | |
} | |
}; | |
var annotateDescriptionsWithParent = function (nodeDescriptions, parent) { | |
nodeDescriptions.forEach(function (nodeDescription) { | |
nodeDescription.parent = parent; | |
if (nodeDescription.children) { | |
annotateDescriptionsWithParent(nodeDescription.children, nodeDescription); | |
}; | |
}); | |
}; | |
var bodyNodeDescription = {type: DocumentApp.ElementType.BODY_SECTION, children: childNodeDescriptions}; | |
annotateDescriptionsWithParent(childNodeDescriptions, bodyNodeDescription); | |
return { | |
getBody: function () { | |
return fakeNode(bodyNodeDescription); | |
}, | |
getBookmark: function (id) { | |
var match = findNodeDescription(childNodeDescriptions, matchBookmark(id)); | |
if (match) { | |
return fakeNode(match); | |
} | |
} | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function testFakeDocument() { | |
var assert = function (value) { | |
if (!value) { | |
throw new Error('assert ' + value); | |
} | |
}; | |
try { | |
var emptyDoc = fakeDocument(); | |
assert(emptyDoc.getBody().getType() === DocumentApp.ElementType.BODY_SECTION); | |
assert(emptyDoc.getBody().getNumChildren() === 0); | |
var docWithParagraph = fakeDocument(aParagraph('there\'s text')); | |
assert(docWithParagraph.getBody().getNumChildren() === 1); | |
assert(docWithParagraph.getBody().getChild(0) !== null); | |
assert(docWithParagraph.getBody().getChild(0).getType() === DocumentApp.ElementType.PARAGRAPH); | |
assert(docWithParagraph.getBody().getChild(0).getNumChildren() === 0); | |
assert(docWithParagraph.getBody().getChild(0).getParent().getType() === DocumentApp.ElementType.BODY_SECTION); | |
assert(docWithParagraph.getBody().getChild(0).getText() === 'there\'s text'); | |
var twoChildrenDoc = fakeDocument(aParagraph('one'), aParagraph('two')); | |
assert(twoChildrenDoc.getBody().getNumChildren() === 2); | |
assert(twoChildrenDoc.getBody().getChild(0) !== twoChildrenDoc.getBody().getChild(1)); | |
assert(twoChildrenDoc.getBody().getChild(0).getText() === 'one'); | |
assert(twoChildrenDoc.getBody().getChild(0).getPreviousSibling() === undefined); | |
assert(twoChildrenDoc.getBody().getChild(1).getText() === 'two'); | |
assert(twoChildrenDoc.getBody().getChild(1).getPreviousSibling().getText() === 'one'); | |
var someDoc = fakeDocument(aParagraph('')); | |
someDoc.getBody().getChild(0).removeFromParent(); | |
assert(someDoc.getBody().getNumChildren() === 0); | |
var anotherDoc = fakeDocument(aParagraph('one'), aParagraph('two')); | |
var anotherDocsBody = anotherDoc.getBody(); | |
anotherDoc.getBody().getChild(0).removeFromParent(); | |
assert(anotherDoc.getBody().getNumChildren() === 1); | |
assert(anotherDocsBody.getNumChildren() === 1); | |
assert(anotherDoc.getBody().getChild(0).getText() === 'two'); | |
assert(anotherDocsBody.getChild(0).getText() === 'two'); | |
var docWithEmptyTable = fakeDocument(aTable()); | |
assert(docWithEmptyTable.getBody().getChild(0).getNumRows() === 0); | |
var docWithOneRowOneCellTable = fakeDocument(aTable(aTableRow(aTableCell(aParagraph('some text'))))); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getType() === DocumentApp.ElementType.TABLE); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getNumRows() === 1); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getType() === DocumentApp.ElementType.TABLE_ROW); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getCell(0).getType() === DocumentApp.ElementType.TABLE_CELL); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getCell(0).getText() === 'some text'); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getCell(0).getChild(0).getType() === DocumentApp.ElementType.PARAGRAPH); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getCell(0).getChild(0).getText() === 'some text'); | |
assert(docWithOneRowOneCellTable.getBody().getChild(0).getRow(0).getCell(0).getChild(0).getParent().getType() === DocumentApp.ElementType.TABLE_CELL); | |
var docWithTwoRowsTable = fakeDocument(aTable(aTableRow(aTableCell(aParagraph('one'))), aTableRow(aTableCell(aParagraph('two'))))); | |
assert(docWithTwoRowsTable.getBody().getChild(0).getNumRows() === 2); | |
assert(docWithTwoRowsTable.getBody().getChild(0).getRow(1).getCell(0).getText() === 'two'); | |
docWithTwoRowsTable.getBody().getChild(0).removeRow(1); | |
assert(docWithTwoRowsTable.getBody().getChild(0).getNumRows() === 1); | |
assert(docWithTwoRowsTable.getBody().getChild(0).getRow(0).getCell(0).getText() === 'one'); | |
var docWithTableCellAndTwoParagraphs = fakeDocument(aTable(aTableRow(aTableCell(aParagraph('some text'), aParagraph('next line'))))); | |
assert(docWithTableCellAndTwoParagraphs.getBody().getChild(0).getRow(0).getCell(0).getText() === 'some text\nnext line'); | |
var docWithTableWithBookmark = fakeDocument(aTable(aTableRow(aTableCell(aParagraph('some text'), aBookmark('cell bookmark'))))); | |
assert(docWithTableWithBookmark.getBookmark('cell bookmark').getPosition().getElement().getText() === 'some text'); | |
var docWithBookmark = fakeDocument(aParagraph(''), aBookmark('some bookmark')); | |
assert(docWithBookmark.getBody().getNumChildren() === 1); | |
assert(docWithBookmark.getBody().getChild(0).getNumChildren() === 0); | |
assert(docWithBookmark.getBookmark('some bookmark').getId() === 'some bookmark'); | |
assert(docWithBookmark.getBookmark('some bookmark').getPosition().getElement().getNumChildren() === 0); | |
var anotherDocWithBookmark = fakeDocument(aParagraph('one'), aBookmark('bookmark'), aParagraph('two')); | |
assert(anotherDocWithBookmark.getBody().getNumChildren() === 2); | |
assert(anotherDocWithBookmark.getBody().getChild(1).getPreviousSibling().getText() === 'one'); | |
var yetAnotherDocWithBookmark = fakeDocument(aParagraph(''), aBookmark('my bookmark')); | |
yetAnotherDocWithBookmark.getBookmark('my bookmark').remove(); | |
assert(yetAnotherDocWithBookmark.getBookmark('my bookmark') === undefined); | |
var docWithInlineImage = fakeDocument(anInlineImage()); | |
assert(docWithInlineImage.getBody().getChild(0).getType() === DocumentApp.ElementType.INLINE_IMAGE); | |
assert(docWithInlineImage.getBody().getChild(0).getParent().getType() === DocumentApp.ElementType.BODY_SECTION); | |
var anotherDocWithInlineImage = fakeDocument(aParagraph(''), anInlineImage()); | |
assert(anotherDocWithInlineImage.getBody().getChild(1).getPreviousSibling().getType() === DocumentApp.ElementType.PARAGRAPH); | |
anotherDocWithInlineImage.getBody().getChild(1).removeFromParent(); | |
assert(anotherDocWithInlineImage.getBody().getNumChildren() === 1); | |
} catch(e) { | |
// Work around bad UX on failing asserts | |
Logger.log(e.message); | |
Logger.log(e.stack); | |
throw e; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment