%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/ |
| Current File : //www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/ve.test.utils.js |
/*!
* VisualEditor test utilities.
*
* @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
*/
( function () {
/*jshint browser:true */
// Create a standalone platform and target so ve.init.platform/target are available
/*jshint nonew:false */
new ve.init.sa.Platform();
new ve.init.sa.Target();
/*jshint nonew:true */
// Disable scroll animatinos
ve.scrollIntoView = function () {};
// Extend QUnit.module to provide a fixture element. This used to be in tests/index.html, but
// dynamic test runners like Karma build their own web page.
( function () {
var orgModule = QUnit.module;
QUnit.dump.maxDepth = 10;
QUnit.module = function ( name, localEnv ) {
localEnv = localEnv || {};
orgModule( name, {
setup: function () {
this.fixture = document.createElement( 'div' );
this.fixture.id = 'qunit-fixture';
document.body.appendChild( this.fixture );
if ( localEnv.setup ) {
localEnv.setup.call( this );
}
},
teardown: function () {
if ( localEnv.teardown ) {
localEnv.teardown.call( this );
}
this.fixture.parentNode.removeChild( this.fixture );
}
} );
};
}() );
/**
* @class
* @singleton
* @ignore
*/
ve.test = { utils: {} };
ve.test.utils.runIsolateTest = function ( assert, type, range, expected, label ) {
var data,
doc = ve.dm.example.createExampleDocument( 'isolationData' ),
surface = new ve.dm.Surface( doc ),
fragment = surface.getLinearFragment( range );
data = ve.copy( doc.getFullData() );
fragment.isolateAndUnwrap( type );
expected( data );
assert.deepEqual( doc.getFullData(), data, label );
};
ve.test.utils.runFormatConverterTest = function ( assert, range, type, attributes, expectedRange, expectedData, msg ) {
var surface = ve.test.utils.createModelOnlySurfaceFromHtml( ve.dm.example.isolationHtml ),
formatAction = new ve.ui.FormatAction( surface ),
data = ve.copy( surface.getModel().getDocument().getFullData() ),
originalData = ve.copy( data );
expectedData( data );
surface.getModel().setLinearSelection( range );
formatAction.convert( type, attributes );
assert.equalLinearData( surface.getModel().getDocument().getFullData(), data, msg + ': data models match' );
assert.equalRange( surface.getModel().getSelection().getRange(), expectedRange, msg + ': ranges match' );
surface.getModel().undo();
assert.equalLinearData( surface.getModel().getDocument().getFullData(), originalData, msg + ' (undo): data models match' );
assert.equalRange( surface.getModel().getSelection().getRange(), range, msg + ' (undo): ranges match' );
};
ve.test.utils.countActionTests = function ( cases ) {
var i, expected = 0;
for ( i = 0; i < cases.length; i++ ) {
expected += cases[ i ].undo ? 2 : 1;
if ( cases[ i ].expectedRangeOrSelection ) {
expected += cases[ i ].undo ? 2 : 1;
}
}
return expected;
};
ve.test.utils.runActionTest = function ( actionName, assert, html, createView, method, args, rangeOrSelection, msg, options ) {
var actualData, originalData, expectedOriginalRangeOrSelection,
surface = createView ?
ve.test.utils.createViewOnlySurfaceFromHtml( html || ve.dm.example.html ) :
ve.test.utils.createModelOnlySurfaceFromHtml( html || ve.dm.example.html ),
action = ve.ui.actionFactory.create( actionName, surface ),
data = ve.copy( surface.getModel().getDocument().getFullData() ),
documentModel = surface.getModel().getDocument(),
selection = ve.test.utils.selectionFromRangeOrSelection( documentModel, rangeOrSelection ),
expectedSelection = options.expectedRangeOrSelection && ve.test.utils.selectionFromRangeOrSelection( documentModel, options.expectedRangeOrSelection );
if ( options.undo ) {
originalData = ve.copy( data );
}
ve.dm.example.postprocessAnnotations( data, surface.getModel().getDocument().getStore() );
if ( options.expectedData ) {
options.expectedData( data, action );
}
surface.getModel().setSelection( selection );
action[ method ].apply( action, args || [] );
actualData = surface.getModel().getDocument().getFullData();
ve.dm.example.postprocessAnnotations( actualData, surface.getModel().getDocument().getStore() );
assert.equalLinearData( actualData, data, msg + ': data models match' );
if ( expectedSelection ) {
assert.equalHash( surface.getModel().getSelection(), expectedSelection, msg + ': selections match' );
}
if ( options.undo ) {
if ( options.expectedOriginalData ) {
options.expectedOriginalData( originalData, action );
}
surface.getModel().undo();
assert.equalLinearData( surface.getModel().getDocument().getFullData(), originalData, msg + ' (undo): data models match' );
if ( expectedSelection ) {
expectedOriginalRangeOrSelection = options.expectedOriginalRangeOrSelection &&
ve.test.utils.selectionFromRangeOrSelection( documentModel, options.expectedOriginalRangeOrSelection );
assert.equalHash( surface.getModel().getSelection(), expectedOriginalRangeOrSelection || selection, msg + ' (undo): selections match' );
}
}
};
ve.test.utils.countGetModelFromDomTests = function ( cases ) {
var msg, n = 0;
for ( msg in cases ) {
if ( cases[ msg ].head !== undefined || cases[ msg ].body !== undefined ) {
n += 3;
if ( cases[ msg ].storeItems ) {
n += Object.keys( cases[ msg ].storeItems ).length;
}
}
}
return n;
};
ve.test.utils.runGetModelFromDomTest = function ( assert, caseItem, msg ) {
var model, hash, html, htmlDoc, actualData, actualRtDoc, expectedRtDoc,
// Make sure we've always got a <base> tag
defaultHead = '<base href="' + ve.dm.example.baseUri + '">';
if ( caseItem.head !== undefined || caseItem.body !== undefined ) {
html = '<head>' + ( caseItem.head || defaultHead ) + '</head><body>' + caseItem.body + '</body>';
htmlDoc = ve.createDocumentFromHtml( html );
model = ve.dm.converter.getModelFromDom( htmlDoc, { fromClipboard: !!caseItem.fromClipboard } );
actualData = model.getFullData();
// Round-trip here, check round-trip later
if ( caseItem.modify ) {
actualData = ve.copy( actualData );
caseItem.modify( model );
}
actualRtDoc = ve.dm.converter.getDomFromModel( model );
// Normalize and verify data
ve.dm.example.postprocessAnnotations( actualData, model.getStore(), caseItem.preserveAnnotationDomElements );
assert.equalLinearData( actualData, caseItem.data, msg + ': data' );
assert.deepEqual( model.getInnerWhitespace(), caseItem.innerWhitespace || new Array( 2 ), msg + ': inner whitespace' );
// check storeItems have been added to store
if ( caseItem.storeItems ) {
for ( hash in caseItem.storeItems ) {
assert.deepEqualWithDomElements(
model.getStore().value( hash ) || {},
caseItem.storeItems[ hash ],
msg + ': store item ' + hash + ' found'
);
}
}
// Check round-trip
expectedRtDoc = caseItem.normalizedBody ?
ve.createDocumentFromHtml( caseItem.normalizedBody ) :
htmlDoc;
assert.equalDomElement( actualRtDoc.body, expectedRtDoc.body, msg + ': round-trip' );
}
};
ve.test.utils.getModelFromTestCase = function ( caseItem ) {
var hash, model,
store = new ve.dm.IndexValueStore();
// Load storeItems into store
if ( caseItem.storeItems ) {
for ( hash in caseItem.storeItems ) {
store.hashStore[ hash ] = ve.copy( caseItem.storeItems[ hash ] );
}
}
model = new ve.dm.Document( ve.dm.example.preprocessAnnotations( caseItem.data, store ) );
model.innerWhitespace = caseItem.innerWhitespace ? ve.copy( caseItem.innerWhitespace ) : new Array( 2 );
if ( caseItem.modify ) {
caseItem.modify( model );
}
return model;
};
ve.test.utils.runGetDomFromModelTest = function ( assert, caseItem, msg ) {
var originalData, model, html, fromDataBody, clipboardHtml;
model = ve.test.utils.getModelFromTestCase( caseItem );
originalData = ve.copy( model.getFullData() );
fromDataBody = caseItem.fromDataBody || caseItem.normalizedBody || caseItem.body;
html = '<body>' + fromDataBody + '</body>';
clipboardHtml = '<body>' + ( caseItem.clipboardBody || fromDataBody ) + '</body>';
assert.equalDomElement(
ve.dm.converter.getDomFromModel( model ),
ve.createDocumentFromHtml( html ),
msg
);
assert.equalDomElement(
ve.dm.converter.getDomFromModel( model, true ),
ve.createDocumentFromHtml( clipboardHtml ),
msg + ' (clipboard mode)'
);
assert.deepEqualWithDomElements( model.getFullData(), originalData, msg + ' (data hasn\'t changed)' );
};
/**
* Create a UI surface from some HTML
*
* This is incredibly slow (>100ms) so consider creating just a ce.Surface
* or dm.Surface, or a mock surface using create(View|Model)OnlySurface*.
*
* @param {string} html Document HTML
* @return {ve.ui.Surface} UI surface
*/
ve.test.utils.createSurfaceFromHtml = function ( html ) {
return this.createSurfaceFromDocument( ve.createDocumentFromHtml( html ) );
};
/**
* Create a UI surface from a document
*
* See warning in ve.test.utils.createSurfaceFromHtml.
*
* @param {ve.dm.Document} doc Document
* @return {ve.ui.Surface} UI surface
*/
ve.test.utils.createSurfaceFromDocument = function ( doc ) {
var target = new ve.init.sa.Target();
$( '#qunit-fixture' ).append( target.$element );
target.addSurface( doc );
return target.surface;
};
/**
* Create a CE surface from some HTML
*
* @param {string} html Document HTML
* @return {ve.ce.Surface} CE surface
*/
ve.test.utils.createSurfaceViewFromHtml = function ( html ) {
return this.createSurfaceViewFromDocument(
ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( html ) )
);
};
/**
* Create a CE surface from a document
*
* @param {ve.dm.Document} doc Document
* @return {ve.ce.Surface} CE surface
*/
ve.test.utils.createSurfaceViewFromDocument = function ( doc ) {
var model, view,
mockSurface = {
$blockers: $( '<div>' ),
$selections: $( '<div>' ),
$element: $( '<div>' ),
isMobile: function () {
return false;
},
getBoundingClientRect: function () {
return {};
},
getImportRules: function () {
return ve.init.sa.Target.static.importRules;
},
getModel: function () {
return model;
},
getView: function () {
return view;
},
commandRegistry: ve.ui.commandRegistry,
sequenceRegistry: ve.ui.sequenceRegistry,
dataTransferHandlerFactory: ve.ui.dataTransferHandlerFactory
};
model = new ve.dm.Surface( doc );
view = new ve.ce.Surface( model, mockSurface );
view.surface = mockSurface;
mockSurface.$element.append( view.$element );
$( '#qunit-fixture' ).append( mockSurface.$element );
view.initialize();
model.initialize();
return view;
};
/**
* Create a view-only UI surface from some HTML
*
* @param {string} html Document HTML
* @return {Object} Mock UI surface which only returns a real view (and its model)
*/
ve.test.utils.createViewOnlySurfaceFromHtml = function ( html ) {
var surfaceView = ve.test.utils.createSurfaceViewFromDocument(
ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( html ) )
);
return surfaceView.surface;
};
/**
* Create a model-only UI surface from some HTML
*
* @param {string} html Document HTML
* @return {Object} Mock UI surface which only returns a real model
*/
ve.test.utils.createModelOnlySurfaceFromHtml = function ( html ) {
var model = new ve.dm.Surface(
ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( html ) )
);
return {
getModel: function () {
return model;
},
getView: function () {
// Mock view
return {
focus: function () {}
};
}
};
};
/**
* Create a DM selection from a range or a JSON selection
*
* @param {ve.dm.Document} doc Document
* @param {ve.Range|Object|string} rangeOrSelection Range or JSON selection
* @return {ve.dm.Selection} Selection
*/
ve.test.utils.selectionFromRangeOrSelection = function ( doc, rangeOrSelection ) {
return rangeOrSelection instanceof ve.Range ?
new ve.dm.LinearSelection( doc, rangeOrSelection ) :
ve.dm.Selection.static.newFromJSON( doc, rangeOrSelection );
};
/**
* Build a DOM from a JSON structure.
*
* @param {Object} data JSON structure
* @param {string} data.type Tag name or '#text' or '#comment'
* @param {string} [data.text] Node text, only used if type is '#text' or '#comment'
* @param {Object[]} [data.children] Node's children; array of objects like data
* @return {Node} DOM node corresponding to data
*/
ve.test.utils.buildDom = function buildDom( data ) {
var i, node;
if ( data.type === '#text' ) {
return document.createTextNode( data.text );
}
if ( data.type === '#comment' ) {
return document.createComment( data.text );
}
node = document.createElement( data.type );
if ( data.children ) {
for ( i = 0; i < data.children.length; i++ ) {
node.appendChild( buildDom( data.children[ i ] ) );
}
}
return node;
};
/**
* Like a reduced outerHTML serialization, but with a position marker '|'.
*
* For clarity, also wraps each text node in a fake tag, and omits non-class attributes.
*
* @param {Node} rootNode The node to serialize
* @param {Object} position
* @param {Node} position.node The node at which the position marker lies
* @param {number} position.offset The offset at which the position marker lies
* @param {Object} [options]
* @param {Function|string} options.ignore Selector for nodes to omit from output
* @return {string} Serialization of the node and position
*/
ve.test.utils.serializePosition = function ( rootNode, position, options ) {
var html = [];
function add( node ) {
var i, len;
if ( options && options.ignore && $( node ).is( options.ignore ) ) {
return;
} else if ( node.nodeType === Node.TEXT_NODE ) {
html.push( '<#text>' );
if ( node === position.node ) {
html.push( ve.escapeHtml(
node.textContent.substring( 0, position.offset ) +
'|' +
node.textContent.substring( position.offset )
) );
} else {
html.push( ve.escapeHtml( node.textContent ) );
}
html.push( '</#text>' );
return;
} else if ( node.nodeType !== Node.ELEMENT_NODE ) {
html.push( '<#unknown type=\'' + node.nodeType + '\'/>' );
return;
}
// else node.nodeType === Node.ELEMENT_NODE
html.push( '<', ve.escapeHtml( node.nodeName.toLowerCase() ) );
if ( node.hasAttribute( 'class' ) ) {
// Single quotes are less annoying for JSON escaping
html.push(
' class=\'',
ve.escapeHtml( node.getAttribute( 'class' ) ),
'\''
);
}
html.push( '>' );
for ( i = 0, len = node.childNodes.length; i < len; i++ ) {
if ( node === position.node && i === position.offset ) {
html.push( '|' );
}
add( node.childNodes[ i ] );
}
if ( node === position.node && node.childNodes.length === position.offset ) {
html.push( '|' );
}
html.push( '</', ve.escapeHtml( node.nodeName.toLowerCase() ), '>' );
}
add( rootNode );
return html.join( '' );
};
}() );