node-webodf
Version:
WebODF - JavaScript Document Engine http://webodf.org/
368 lines (317 loc) • 13.2 kB
JavaScript
/**
* Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/WebODF/
*/
/*global runtime, core, gui, odf, ops, xmldom*/
/**
* @constructor
* @param {core.UnitTestRunner} runner
* @implements {core.UnitTest}
*/
gui.MetadataControllerTests = function MetadataControllerTests(runner) {
"use strict";
var r = runner,
t,
testarea,
internalMetadataTagnames = ["dc:creator", "dc:date", "meta:editing-cycles"],
officens = odf.Namespaces.officens,
inputMemberId = "Joe";
/**
* Trying to avoid having to load a complete document for these tests. Mocking ODF
* canvas allows some simplification in the testing setup
* @param {!Element} node
* @extends {odf.OdfCanvas} Well.... we don't really, but please shut your face closure compiler :)
* @constructor
*/
/*jslint emptyblock:true*/
function MockOdfCanvas(node) {
var odfContainer;
this.odfContainer = function() {return odfContainer; };
this.getContentElement = function () { return node.getElementsByTagNameNS(officens, 'text')[0]; };
this.getElement = function () { return node; };
this.rootElement = node;
this.refreshSize = function() { };
this.rerenderAnnotations = function() { };
// init
odfContainer = new odf.OdfContainer(odf.OdfContainer.DocumentType.TEXT);
odfContainer.setRootElement(node);
}
/*jslint emptyblock:false*/
/**
* @param {!ops.OdtDocument} odtDocument
* @extends {ops.Session} Don't mind me... I'm just lying to closure compiler again!
* @constructor
*/
function MockSession(odtDocument) {
var self = this,
/**@type{!ops.OperationFactory}*/
operationFactory = new ops.OperationFactory();
this.getOdtDocument = function() {
return odtDocument;
};
this.enqueue = function(operations) {
operations.forEach(function(op) {
var /**@type{?ops.Operation}*/
timedOp,
opspec = op.spec();
// need to set the timestamp, otherwise things fail in odtDocument
opspec.timestamp = Date.now();
timedOp = operationFactory.create(opspec);
if (timedOp.execute(odtDocument)) {
odtDocument.emit(ops.OdtDocument.signalOperationEnd, timedOp);
}
});
};
function init() {
var op = new ops.OpAddMember();
op.init({
memberid: inputMemberId,
setProperties: /**@type {!ops.MemberProperties}*/({
fullName: "Metha",
color: "black",
imageUrl: "avatar-joe.png"
})
});
self.enqueue([op]);
}
init();
}
/**
* @param {!gui.MetadataController} metadataController
* @constructor
*/
function MetadataChangeListener(metadataController) {
var changedMetadata = null;
function onMetadataChanged(changes) {
if (changedMetadata === null) {
changedMetadata = changes;
} else {
// merge signal data
Object.keys(changes.setProperties).forEach(function (key) {
changedMetadata.setProperties[key] = changes.setProperties[key];
});
changes.removedProperties.forEach(function (key) {
delete changedMetadata.setProperties[key];
if (changedMetadata.removedProperties.indexOf(key) !== -1) {
changedMetadata.removedProperties.push(key);
}
});
}
// remove automatic updated metadata
internalMetadataTagnames.forEach(function(internalTagName) {
if (changedMetadata.setProperties && changedMetadata.setProperties[internalTagName]) {
delete changedMetadata.setProperties[internalTagName];
}
});
}
this.getChangedMetadata = function() {
return changedMetadata;
};
this.reset = function() {
changedMetadata = null;
};
// init
metadataController.subscribe(gui.MetadataController.signalMetadataChanged, onMetadataChanged);
}
/**
* Create a new ODT document with the specified meta data
* @param {!string} xml
* @return {!Element} Root document node
*/
function createOdtDocument(xml) {
var domDocument = testarea.ownerDocument,
doc,
node;
doc = core.UnitTest.createOdtDocument("<office:meta>" + xml + "</office:meta><office:body><office:text></office:text></office:body>", odf.Namespaces.namespaceMap);
node = /**@type{!Element}*/(domDocument.importNode(doc.documentElement, true));
testarea.appendChild(node);
t.odtDocument = new ops.OdtDocument(new MockOdfCanvas(node));
t.session = new MockSession(t.odtDocument);
t.metadataController = new gui.MetadataController(t.session, inputMemberId);
t.metadataChangeListener = new MetadataChangeListener(t.metadataController);
return node;
}
/**
* Return a serialized string of the document metedata content, excluding the wrapping <office:text>
* tags and all non-odf elements. Also excluding the internal metadata like dc:date.
* If such an internal metadata should not be excluded because it is used for the tests,
* pass it as parameter unfilteredTagName.
* @param {?string|undefined=} unfilteredTagName name of the tag which will not get removed as internal
* @return {!string}
*/
function serializeMetadataContent(unfilteredTagName) {
var nsmap = odf.Namespaces.namespaceMap,
serializer = new xmldom.LSSerializer(),
filter = new odf.OdfNodeFilter(),
result;
serializer.filter = filter;
result = serializer.writeToString(t.odtDocument.getDocumentElement().meta, nsmap);
result = result.replace(/<[\/]{0,1}office:meta>/g, "");
// remove automatic updated metadata, unless filtered
internalMetadataTagnames.forEach(function(removedTagName) {
if (removedTagName !== unfilteredTagName) {
result = result.replace(new RegExp('<'+removedTagName+'>.*</'+removedTagName+'>'), "");
}
});
return result;
}
/**
* @param {!string} tagName
* @param {!string} content
* @return {!string}
*/
function tagged(tagName, content) {
return '<'+tagName+'>'+content+'</'+tagName+'>';
}
this.setUp = function () {
testarea = core.UnitTest.provideTestAreaDiv();
t = { doc: testarea.ownerDocument };
};
this.tearDown = function () {
core.UnitTest.cleanupTestAreaDiv();
t = {};
};
/**
* @param {!string} metadataName
* @param {?string} data
* @return {undefined}
*/
function getMetaData(metadataName, data) {
createOdtDocument(data ? tagged(metadataName, data) : "");
// check data via controller interface
t.actualData = t.metadataController.getMetadata(metadataName);
t.expectedData = data;
r.shouldBe(t, "t.actualData", "t.expectedData");
}
/**
* @param {!string} metadataName
* @param {?string} oldData
* @param {!string} newData
* @param {!boolean} shouldBeUpdated
* @return {undefined}
*/
function updateMetaData(metadataName, oldData, newData, shouldBeUpdated) {
var metaDataProperties = {};
createOdtDocument(oldData ? tagged(metadataName, oldData) : "");
t.metadataChangeListener.reset();
metaDataProperties[metadataName] = newData;
t.metadataController.setMetadata(metaDataProperties);
// check data via controller interface
t.actualData = t.metadataController.getMetadata(metadataName);
t.expectedData = shouldBeUpdated ? newData : oldData;
r.shouldBe(t, "t.actualData", "t.expectedData");
// check also raw data
t.actualDoc = serializeMetadataContent(metadataName);
t.expectedDoc = (shouldBeUpdated || oldData) ? tagged(metadataName, /**@type{!string}*/(t.expectedData)) : "";
r.shouldBe(t, "t.actualDoc", "t.expectedDoc");
// check event listener
t.signalledChangedMetadata = t.metadataChangeListener.getChangedMetadata();
if (shouldBeUpdated) {
t.expectedSignalledChangedMetadata = {
setProperties: {},
removedProperties: []
};
t.expectedSignalledChangedMetadata.setProperties[metadataName] = t.expectedData;
} else {
t.expectedSignalledChangedMetadata = null;
}
r.shouldBe(t, "t.signalledChangedMetadata", "t.expectedSignalledChangedMetadata");
}
/**
* @param {!string} metadataName
* @param {?string} oldData
* @param {!boolean} shouldBeRemoved
* @return {undefined}
*/
function removeMetaData(metadataName, oldData, shouldBeRemoved) {
createOdtDocument(oldData ? tagged(metadataName, oldData) : "");
t.metadataChangeListener.reset();
t.metadataController.setMetadata(null, [metadataName]);
// check data via controller interface
t.actualData = t.metadataController.getMetadata(metadataName);
t.expectedData = shouldBeRemoved ? null : oldData;
r.shouldBe(t, "t.actualData", "t.expectedData");
// check also raw data
t.actualDoc = serializeMetadataContent(metadataName);
t.expectedDoc = (shouldBeRemoved || !oldData) ? "" : tagged(metadataName, /**@type{!string}*/(oldData));
r.shouldBe(t, "t.actualDoc", "t.expectedDoc");
// check event listener
t.signalledChangedMetadata = t.metadataChangeListener.getChangedMetadata();
t.expectedSignalledChangedMetadata = shouldBeRemoved ? {
setProperties: {},
removedProperties: [metadataName]
} : null;
r.shouldBe(t, "t.signalledChangedMetadata", "t.expectedSignalledChangedMetadata");
}
function getNonexisting_dc_data() {
getMetaData("dc:date", null);
}
function getNonexisting_text_title() {
getMetaData("text:title", null);
}
function getExisting_dc_data() {
getMetaData("dc:date", "2013-08-01T18:44:55");
}
function getExisting_text_title() {
getMetaData("text:title", "Old title");
}
function setNonexisting_dc_data() {
updateMetaData("dc:date", null, "2010-01-01T00:00:00", false);
}
function setNonexisting_text_title() {
updateMetaData("text:title", null, "New title", true);
}
function overwriteExisting_dc_data() {
updateMetaData("dc:date", "2013-08-01T18:44:55", "2010-01-01T00:00:00", false);
}
function overwriteExisting_text_title() {
updateMetaData("text:title", "Old title", "New title", true);
}
function removeExisting_dc_data() {
removeMetaData("dc:date", "2013-08-01T18:44:55", false);
}
function removeExisting_text_title() {
removeMetaData("text:title", "Old title", true);
}
this.tests = function () {
return r.name([
getNonexisting_dc_data,
getNonexisting_text_title,
getExisting_dc_data,
getExisting_text_title,
setNonexisting_dc_data,
setNonexisting_text_title,
overwriteExisting_dc_data,
overwriteExisting_text_title,
removeExisting_dc_data,
removeExisting_text_title
]);
};
this.asyncTests = function () {
return [
];
};
};
gui.MetadataControllerTests.prototype.description = function () {
"use strict";
return "Test the MetadataController class.";
};