Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/extensions/VisualEditor/lib/ve/tests/dm/ve.dm.Surface.test.js
Ðазад
/*! * VisualEditor DataModel Surface tests. * * @copyright See AUTHORS.txt */ QUnit.module( 've.dm.Surface' ); ve.dm.SurfaceStub = function VeDmSurfaceStub( data, range ) { const doc = new ve.dm.Document( data || [ { type: 'paragraph' }, ...'hi', { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ); // Inheritance ve.dm.SurfaceStub.super.call( this, doc ); // Initialize selection to simulate the surface being focused this.setLinearSelection( range || new ve.Range( 1 ) ); }; OO.inheritClass( ve.dm.SurfaceStub, ve.dm.Surface ); // Tests QUnit.test( 'getDocument', ( assert ) => { const surface = new ve.dm.SurfaceStub(); assert.strictEqual( surface.getDocument(), surface.documentModel ); } ); QUnit.test( 'getSelection', ( assert ) => { const surface = new ve.dm.SurfaceStub(); assert.strictEqual( surface.getSelection(), surface.selection ); } ); QUnit.test( 'setSelection out of range', ( assert ) => { const surface = new ve.dm.SurfaceStub( [ { type: 'paragraph' }, ...'Foo', { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ); surface.setSelection( new ve.dm.LinearSelection( new ve.Range( 2, 100 ) ) ); let range = surface.getSelection().getRange(); assert.deepEqual( [ range.start, range.end ], [ 2, 5 ] ); surface.setSelection( new ve.dm.LinearSelection( new ve.Range( 99, 100 ) ) ); range = surface.getSelection().getRange(); assert.deepEqual( [ range.start, range.end ], [ 5, 5 ] ); } ); QUnit.test( 'contextChange events', ( assert ) => { const surface = new ve.dm.SurfaceStub( ve.dm.example.preprocessAnnotations( [ { type: 'paragraph' }, ...'Foo', // Bold "bar" [ 'b', [ ve.dm.example.bold ] ], [ 'a', [ ve.dm.example.bold ] ], [ 'r', [ ve.dm.example.bold ] ], // Italic "baz" [ 'b', [ ve.dm.example.italic ] ], [ 'a', [ ve.dm.example.italic ] ], [ 'z', [ ve.dm.example.italic ] ], ...'Foo', { type: '/paragraph' }, { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, ...'One', { type: '/paragraph' }, { type: '/listItem' }, { type: 'listItem' }, { type: 'paragraph' }, ...'Two', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' }, { type: 'internalList' }, { type: '/internalList' } ] ) ); let contextChanges = 0; surface.on( 'contextChange', () => { contextChanges++; } ); const cases = [ { title: 'setSelection, to equivalent selection', initialSelection: new ve.Range( 1 ), selection: new ve.Range( 1 ), expected: 0 }, { title: 'setSelection, to equivalent expanded selection', initialSelection: new ve.Range( 1, 2 ), selection: new ve.Range( 1, 2 ), expected: 0 }, { title: 'setSelection, move within plain text', initialSelection: new ve.Range( 1 ), selection: new ve.Range( 2 ), expected: 0 }, { // Going from collapsed to not is a context change title: 'setSelection, expands over plain text', initialSelection: new ve.Range( 1 ), selection: new ve.Range( 1, 2 ), expected: 1 }, { title: 'setSelection, collapses', initialSelection: new ve.Range( 1, 2 ), selection: new ve.Range( 1 ), expected: 1 }, { title: 'setSelection, non-collapsed selection expands over plain text', initialSelection: new ve.Range( 1, 2 ), selection: new ve.Range( 1, 3 ), expected: 0 }, { title: 'setSelection, non-collapsed selection expands into annotated text', initialSelection: new ve.Range( 1, 2 ), selection: new ve.Range( 1, 5 ), expected: 1 }, { title: 'setSelection, non-collapsed selection expands into annotated text, other direction', initialSelection: new ve.Range( 12, 11 ), selection: new ve.Range( 12, 8 ), expected: 2 // Move + insertion annotations change }, { title: 'setSelection, move collapsed selection to edge of annotated text', initialSelection: new ve.Range( 2 ), selection: new ve.Range( 4 ), expected: 1 // Move + no insertion annotations change }, { title: 'setSelection, move collapsed selection to within annotated text', initialSelection: new ve.Range( 2 ), selection: new ve.Range( 5 ), expected: 2 // Move + insertion annotations change }, { title: 'setSelection, selection collapsed in end node (but overally non-collapsed) becomes non-collapsed in end node', initialSelection: new ve.Range( 18, 24 ), selection: new ve.Range( 18, 26 ), expected: 1 // Move + insertion annotations change } ]; cases.forEach( ( caseItem ) => { surface.setLinearSelection( caseItem.initialSelection || new ve.Range( 1 ) ); contextChanges = 0; surface.setLinearSelection( caseItem.selection ); assert.strictEqual( contextChanges, caseItem.expected, caseItem.title ); } ); } ); QUnit.test( 'documentUpdate/select events', ( assert ) => { const surface = new ve.dm.SurfaceStub(), doc = surface.getDocument(), // docmentUpdate doesn't fire for no-op transactions, so make sure there's something there tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 3, [ 'i' ] ), events = { documentUpdate: 0, select: 0 }; surface.on( 'documentUpdate', () => { events.documentUpdate++; } ); surface.on( 'select', () => { events.select++; } ); surface.change( tx.clone() ); assert.deepEqual( events, { documentUpdate: 1, select: 0 }, 'change with transaction only' ); surface.setLinearSelection( new ve.Range( 2 ) ); assert.deepEqual( events, { documentUpdate: 1, select: 1 }, 'setSelection' ); surface.change( tx.clone(), new ve.dm.LinearSelection( new ve.Range( 3 ) ) ); assert.deepEqual( events, { documentUpdate: 2, select: 2 }, 'change with transaction and selection' ); } ); QUnit.test( 'breakpoint/undo/redo', ( assert ) => { const range = new ve.Range( 1, 3 ), surface = new ve.dm.SurfaceStub( null, range ), fragment = surface.getFragment(), doc = surface.getDocument(), selection = new ve.dm.LinearSelection( range ), tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 1, [ 'x' ] ); assert.strictEqual( surface.breakpoint(), false, 'Returns false if no transactions applied' ); surface.change( tx ); assert.deepEqual( surface.undoStack, [], 'Undo stack data matches before breakpoint' ); assert.deepEqual( surface.newTransactions, [ tx ], 'New transactions match before breakpoint' ); assert.strictEqual( surface.breakpoint(), true, 'Returns true after transaction applied' ); assert.strictEqual( surface.breakpoint(), false, 'Returns false if no transactions applied since last breakpoint' ); assert.deepEqual( surface.undoStack, [ { start: 0, transactions: [ tx ], selection: new ve.dm.LinearSelection( tx.translateRange( selection.getRange() ) ), selectionBefore: selection } ], 'Undo stack data matches after breakpoint' ); assert.deepEqual( surface.newTransactions, [], 'New transactions empty after breakpoint' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Range changed' ); // Dirty selection to make sure beforeSelection works surface.setLinearSelection( new ve.Range( 3 ) ); surface.undo(); assert.equalHash( surface.getSelection().getRange(), range, 'Range restored after undo' ); assert.strictEqual( fragment.getText(), 'hi', 'Text restored after undo' ); surface.setLinearSelection( new ve.Range( 3 ) ); surface.redo(); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Range changed after redo' ); assert.strictEqual( fragment.getText(), 'xhi', 'Text changed after redo' ); } ); QUnit.test( 'multi-user undo', ( assert ) => { const surfaces = []; // Create two surfaces owned by authors 1 & 2, consisting of interleaved // transactions by both users, each adding to their own paragraph for ( let i = 1; i <= 2; i++ ) { const range = new ve.Range( 1 ); const surface = new ve.dm.SurfaceStub( [ { type: 'paragraph' }, { type: '/paragraph' }, { type: 'paragraph' }, { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ], range ); surface.setAuthorId( i ); surface.setMultiUser( true ); const doc = surface.getDocument(); let tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 1, [ ...'foo' ] ); tx.authorId = 1; surface.change( tx ); surface.breakpoint(); tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 6, [ ...'123' ] ); tx.authorId = 2; surface.change( tx ); surface.breakpoint(); tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 4, [ ...'bar' ] ); tx.authorId = 1; surface.change( tx ); surface.breakpoint(); tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 12, [ ...'456' ] ); tx.authorId = 2; surface.change( tx ); surface.breakpoint(); surfaces.push( surface ); } // User on surface 1 presses undo twice, reverting only their changes surfaces[ 0 ].undo(); surfaces[ 0 ].undo(); assert.equalLinearData( surfaces[ 0 ].getDocument().getData(), [ { type: 'paragraph' }, { type: '/paragraph' }, { type: 'paragraph' }, ...'123456', { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ); assert.strictEqual( surfaces[ 0 ].canUndo(), false, 'No more steps for user on surface 1 to undo' ); // User on surface 2 presses undo twice, reverting only their changes surfaces[ 1 ].undo(); surfaces[ 1 ].undo(); assert.equalLinearData( surfaces[ 1 ].getDocument().getData(), [ { type: 'paragraph' }, ...'foobar', { type: '/paragraph' }, { type: 'paragraph' }, { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ); // TODO: We should disable undo as soon as the user runs out of transactions of their own assert.strictEqual( surfaces[ 1 ].canUndo(), true, 'User on surface 2 thinks they can still undo' ); surfaces[ 1 ].undo(); assert.strictEqual( surfaces[ 1 ].canUndo(), false, 'User on surface 2 realises they can\'t undo' ); // Count complete history: 4 transactions + 2 undos assert.strictEqual( surfaces[ 1 ].getDocument().getCompleteHistoryLength(), 6, 'Final undo was a no-op' ); } ); QUnit.test( 'change rollback', ( assert ) => { const range = new ve.Range( 1, 3 ), surface = new ve.dm.SurfaceStub( null, range ), doc = surface.getDocument(); const tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 1, [ { type: '/heading' } ] ); assert.throws( () => { surface.change( tx ); }, new Error( 'Expected closing for paragraph but got closing for heading' ), 'Transaction throws an exception' ); assert.strictEqual( surface.canUndo(), false, 'No history to undo after failed change' ); } ); QUnit.test( 'range translation', ( assert ) => { const surface = new ve.dm.SurfaceStub( null, new ve.Range( 3 ) ), doc = surface.getDocument(), tx = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 3, [ 'x' ] ); surface.change( tx ); const selection = surface.getSelection(); assert.true( selection instanceof ve.dm.LinearSelection, 'Selection is linear' ); assert.equalRange( selection.getRange(), new ve.Range( 3 ), 'Cursor is unmoved' ); } ); QUnit.test( 'staging', ( assert ) => { const surface = new ve.dm.SurfaceStub( null, new ve.Range( 1, 3 ) ), fragment = surface.getFragment(), doc = surface.getDocument(); assert.strictEqual( surface.isStaging(), false, 'isStaging false when not staging' ); assert.strictEqual( surface.getStagingTransactions(), undefined, 'getStagingTransactions undefined when not staging' ); assert.strictEqual( surface.doesStagingAllowUndo(), undefined, 'doesStagingAllowUndo undefined when not staging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface range matches fragment range' ); surface.change( ve.dm.TransactionBuilder.static.newFromInsertion( doc, 1, [ 'a' ] ) ); surface.pushStaging(); assert.strictEqual( surface.isStaging(), true, 'isStaging true after pushStaging' ); assert.deepEqual( surface.getStagingTransactions(), [], 'getStagingTransactions empty array after pushStaging' ); assert.strictEqual( surface.doesStagingAllowUndo(), false, 'doesStagingAllowUndo false when staging without undo' ); let tx1 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 2, [ 'b' ] ); surface.change( tx1 ); assert.strictEqual( fragment.getText(), 'abhi', 'document contents match after first transaction' ); assert.deepEqual( surface.getStagingTransactions(), [ tx1 ], 'getStagingTransactions contains first transaction after change' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.pushStaging( true ); assert.strictEqual( surface.isStaging(), true, 'isStaging true after nested pushStaging' ); assert.deepEqual( surface.getStagingTransactions(), [], 'getStagingTransactions empty array after nested pushStaging' ); assert.strictEqual( surface.doesStagingAllowUndo(), true, 'doesStagingAllowUndo true when staging with undo' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); let tx2 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 3, [ 'c' ] ); surface.change( tx2 ); assert.strictEqual( fragment.getText(), 'abchi', 'document contents match after second transaction' ); assert.deepEqual( surface.getStagingTransactions(), [ tx2 ], 'getStagingTransactions contains second transaction after change in nested staging' ); assert.deepEqual( surface.popStaging(), [ tx2 ], 'popStaging returns second transaction list' ); assert.strictEqual( surface.isStaging(), true, 'isStaging true after nested popStaging' ); assert.strictEqual( fragment.getText(), 'abhi', 'document contents match after nested popStaging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); assert.deepEqual( surface.popStaging(), [ tx1 ], 'popStaging returns first transaction list' ); assert.strictEqual( surface.isStaging(), false, 'isStaging false after outer popStaging' ); assert.strictEqual( fragment.getText(), 'ahi', 'document contents match after outer popStaging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.pushStaging(); tx1 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 2, [ 'b' ] ); surface.change( tx1 ); surface.pushStaging(); tx2 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 3, [ 'c' ] ); surface.change( tx2 ); assert.deepEqual( surface.popAllStaging(), [ tx1, tx2 ], 'popAllStaging returns full transaction list' ); assert.strictEqual( fragment.getText(), 'ahi', 'document contents match after outer clearStaging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.pushStaging(); tx1 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 2, [ 'b' ] ); surface.change( tx1 ); surface.pushStaging(); tx2 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 3, [ 'c' ] ); surface.change( tx2 ); surface.applyStaging(); assert.deepEqual( surface.getStagingTransactions(), [ tx1, tx2 ], 'applyStaging merges transactions' ); surface.applyStaging(); assert.strictEqual( surface.isStaging(), false, 'isStaging false after outer applyStaging' ); assert.strictEqual( fragment.getText(), 'abchi', 'document contents changed after applyStaging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.pushStaging(); tx1 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 4, [ 'd' ] ); surface.change( tx1 ); surface.pushStaging(); tx2 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 5, [ 'e' ] ); surface.change( tx2 ); surface.applyAllStaging(); assert.strictEqual( surface.isStaging(), false, 'isStaging false after outer applyAllStaging' ); assert.strictEqual( fragment.getText(), 'abcdehi', 'document contents changed after applyAllStaging' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.undo(); assert.strictEqual( fragment.getText(), 'abchi', 'document contents changed after undo' ); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); surface.pushStaging(); surface.pushStaging(); // Apply transaction at second level, first level is empty and has no selctionBefore tx1 = ve.dm.TransactionBuilder.static.newFromInsertion( doc, 4, [ 'd' ] ); surface.change( tx1 ); surface.applyAllStaging(); // Dirty selection surface.setLinearSelection( new ve.Range( 1 ) ); // Undo should restore the selection from the second level's selectionBefore surface.undo(); assert.equalHash( surface.getSelection(), fragment.getSelection(), 'Surface selection matches fragment range' ); } ); QUnit.test( 'getOffsetFromSourceOffset / getSourceOffsetFromOffset / getRangeFromSourceOffsets', ( assert ) => { const surface = new ve.dm.SurfaceStub( [ { type: 'paragraph' }, ...'Foo', { type: '/paragraph' }, { type: 'paragraph' }, ...'ba', { type: '/paragraph' }, { type: 'paragraph' }, ...'Quux', { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ), expectedOffsets = [ 1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14 ], expectedSourceOffsets = [ 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 11, 12 ]; for ( let i = 0; i < expectedOffsets.length; i++ ) { assert.strictEqual( surface.getOffsetFromSourceOffset( i ), expectedOffsets[ i ], 'Correct offset at ' + i ); } assert.throws( () => { surface.getOffsetFromSourceOffset( -1 ); }, Error, 'Offset -1 is out of bounds' ); assert.throws( () => { surface.getOffsetFromSourceOffset( expectedOffsets.length ); }, Error, 'Offset ' + expectedOffsets.length + ' is out of bounds' ); for ( let i = 0; i < expectedSourceOffsets.length; i++ ) { assert.strictEqual( surface.getSourceOffsetFromOffset( i ), expectedSourceOffsets[ i ], 'Correct source offset at ' + i ); } assert.throws( () => { surface.getSourceOffsetFromOffset( -1 ); }, Error, 'Offset -1 is out of bounds' ); assert.throws( () => { surface.getSourceOffsetFromOffset( expectedSourceOffsets.length ); }, Error, 'Offset ' + expectedSourceOffsets.length + ' is out of bounds' ); assert.equalRange( surface.getRangeFromSourceOffsets( 1, 5 ), new ve.Range( 2, 7 ), 'Simple forwards range' ); assert.equalRange( surface.getRangeFromSourceOffsets( 6, 2 ), new ve.Range( 8, 3 ), 'Simple backwards range' ); assert.equalRange( surface.getRangeFromSourceOffsets( 7, 7 ), new ve.Range( 10 ), 'Collapsed range (2 args)' ); assert.equalRange( surface.getRangeFromSourceOffsets( 8 ), new ve.Range( 11 ), 'Collapsed range (1 arg)' ); } ); QUnit.test( 'autosave', function ( assert ) { const storage = ve.init.platform.sessionStorage, done = assert.async(), state = { name: 'name', id: 1 }; let surface = new ve.dm.SurfaceStub(), fragment = surface.getLinearFragment( new ve.Range( 3 ) ), autosaveFailed = 0; assert.strictEqual( surface.restoreChanges(), false, 'restoreChanges returns false when nothing to restore' ); assert.strictEqual( surface.storeDocState( state, '<p>foo</p>' ), true, 'storeDocState returns true' ); assert.deepEqual( storage.getObject( 've-docstate' ), state, 'storeDocState writes doc state to session storage' ); assert.strictEqual( storage.get( 've-dochtml' ), '<p>foo</p>', 'storeDocState writes custom HTML to session storage' ); surface.storeDocState( state, '' ); assert.strictEqual( storage.get( 've-dochtml' ), '', 'storeDocState can set HTML to empty string' ); surface.storeDocState(); assert.strictEqual( storage.get( 've-dochtml' ), '<p>hi</p>', 'storeDocState writes current HTML to session storage' ); assert.strictEqual( storage.getObject( 've-docstate' ), null, 'docstate is empty if not provided' ); fragment.insertContent( ' bar' ); surface.breakpoint(); assert.deepEqual( storage.getObject( 've-changes' ), null, 'Changes aren\'t stored before startStoringChanges' ); surface = new ve.dm.SurfaceStub(); fragment = surface.getLinearFragment( new ve.Range( 3 ) ); surface.startStoringChanges(); surface.storeDocState( state ); fragment.insertContent( ' bar' ); surface.breakpoint(); assert.deepEqual( storage.getObject( 've-changes' ), [ { start: 0, transactions: [ [ 3, [ '', ' bar' ], 3 ] ] } ], 'First change stored' ); surface.storeChanges(); assert.strictEqual( storage.getObject( 've-changes' ).length, 1, 'No extra change stored if no changes since last store' ); fragment.convertNodes( 'heading', { level: 1 } ); surface.breakpoint(); assert.deepEqual( storage.getObject( 've-changes' )[ 1 ], { start: 1, transactions: [ [ [ [ { type: 'paragraph' } ], [ { type: 'heading', attributes: { level: 1 } } ] ], 6, [ [ { type: '/paragraph' } ], [ { type: '/heading' } ] ], 2 ] ] }, 'Second change stored' ); fragment.collapseToEnd().insertContent( ' baz' ); surface.setLinearSelection( new ve.Range( 5 ) ); surface.breakpoint(); assert.strictEqual( storage.getObject( 've-changes' ).length, 3, 'Fourth change stored' ); assert.deepEqual( storage.getObject( 've-selection' ), { type: 'linear', range: { type: 'range', from: 5, to: 5 } }, 'Selection state stored' ); surface.stopStoringChanges(); fragment.collapseToEnd().insertContent( ' quux' ); surface.breakpoint(); assert.strictEqual( storage.getObject( 've-changes' ).length, 3, 'Change not stored after stopStoringChanges' ); assert.throws( () => { surface.documentModel.completeHistory = new ve.dm.Change(); surface.restoreChanges(); }, /Failed to restore/, 'Calling restoreChanges on the wrong document state throws "Failed to restore…" error' ); surface = new ve.dm.SurfaceStub(); fragment = null; assert.strictEqual( surface.getHtml(), '<p>hi</p>', 'Document HTML before restoreChanges' ); assert.strictEqual( surface.restoreChanges(), true, 'restoreChanges returns true on success' ); assert.strictEqual( surface.getHtml(), '<h1>hi bar baz</h1>', 'Document HTML restored' ); assert.strictEqual( surface.getDocument().getCompleteHistoryLength(), 3, 'Document history restored' ); setTimeout( ( ( s ) => { assert.equalHash( s.getSelection(), { type: 'linear', range: { type: 'range', from: 5, to: 5 } }, 'Document selection restored (async)' ); s.undo(); assert.equalHash( s.getSelection(), { type: 'linear', range: { type: 'range', from: 7, to: 7 } }, 'Document selection guessed after undo' ); done(); } ).bind( this, surface ) ); ve.init.platform.storageDisabled = true; assert.strictEqual( surface.restoreChanges(), false, 'restoreChanges returns false if session storage disabled' ); ve.init.platform.storageDisabled = false; surface.removeDocStateAndChanges(); assert.strictEqual( storage.get( 've-html' ), null, 'HTML empty after removeDocStateAndChanges' ); assert.strictEqual( storage.getObject( 've-docstate' ), null, 'Doc state empty after removeDocStateAndChanges' ); assert.deepEqual( storage.getObject( 've-changes' ), null, 'Changes empty after removeDocStateAndChanges' ); surface = new ve.dm.SurfaceStub(); fragment = surface.getLinearFragment( new ve.Range( 3 ) ); surface.startStoringChanges(); // Pass magic string to only fail when writing HTML assert.strictEqual( surface.storeDocState( state, '__FAIL__' ), false, 'storeDocState returns false when HTML can\'t be stored' ); assert.strictEqual( storage.getObject( 've-docstate' ), null, 'docstate is wiped if HTML storage failed' ); ve.init.platform.storageDisabled = true; surface = new ve.dm.SurfaceStub(); fragment = surface.getLinearFragment( new ve.Range( 3 ) ); surface.startStoringChanges(); assert.strictEqual( surface.storeDocState( state ), false, 'storeDocState returns false when sessionStorage disabled' ); fragment.insertContent( ' bar' ); surface.breakpoint(); assert.strictEqual( storage.getObject( 've-changes' ), false, 'No changes recorded' ); ve.init.platform.storageDisabled = false; surface.on( 'autosaveFailed', () => { autosaveFailed++; } ); surface.startStoringChanges(); surface.storeDocState( state ); ve.init.platform.storageDisabled = true; assert.strictEqual( autosaveFailed, 0, 'Autosave hasn\'t failed before first change' ); fragment.insertContent( ' bar' ); surface.breakpoint(); assert.strictEqual( autosaveFailed, 1, 'Autosave fails after first change' ); fragment.insertContent( ' baz' ); surface.breakpoint(); assert.strictEqual( autosaveFailed, 1, 'Subsequent failures don\'t fire autosaveFailed again' ); assert.strictEqual( storage.getObject( 've-changes' ), false, 'No changes recorded' ); ve.init.platform.storageDisabled = false; surface.storeDocState( state, '<p>foo</p>' ); // This allows callers to call storeDocState after startStoringChanges, e.g. after the first transaction assert.strictEqual( surface.lastStoredChange, 1, 'storeDocState with custom HTML doesn\'t advance the lastStoredChange pointer' ); surface.storeDocState( state ); assert.strictEqual( surface.lastStoredChange, 5, 'storeDocState without custom HTML advances the lastStoredChange pointer' ); } ); // TODO: ve.dm.Surface#getHistory // TODO: ve.dm.Surface#canRedo // TODO: ve.dm.Surface#canUndo // TODO: ve.dm.Surface#hasBeenModified // TODO: ve.dm.Surface#truncateUndoStack
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка