Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 797cd97

Browse files
authored
Merge pull request #1407 from ckeditor/t/403
Other: Renderer now uses partial text replacing when updating text nodes instead of replacing entire nodes. Closes #403.
2 parents e20e133 + 414ab10 commit 797cd97

File tree

2 files changed

+282
-1
lines changed

2 files changed

+282
-1
lines changed

src/view/renderer.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import remove from '@ckeditor/ckeditor5-utils/src/dom/remove';
1818
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
1919
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
2020
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
21+
import fastDiff from '@ckeditor/ckeditor5-utils/src/fastdiff';
2122

2223
/**
2324
* Renderer updates DOM structure and selection, to make them a reflection of the view structure and selection.
@@ -426,7 +427,15 @@ export default class Renderer {
426427
}
427428

428429
if ( actualText != expectedText ) {
429-
domText.data = expectedText;
430+
const actions = fastDiff( actualText, expectedText );
431+
432+
for ( const action of actions ) {
433+
if ( action.type === 'insert' ) {
434+
domText.insertData( action.index, action.values.join( '' ) );
435+
} else { // 'delete'
436+
domText.deleteData( action.index, action.howMany );
437+
}
438+
}
430439
}
431440
}
432441

tests/view/renderer.js

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,28 @@ describe( 'Renderer', () => {
217217
expect( renderer.markedTexts.size ).to.equal( 0 );
218218
} );
219219

220+
it( 'should not update same text', () => {
221+
const viewText = new ViewText( 'foo' );
222+
viewRoot._appendChild( viewText );
223+
224+
renderer.markToSync( 'children', viewRoot );
225+
renderer.render();
226+
227+
expect( domRoot.childNodes.length ).to.equal( 1 );
228+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
229+
230+
viewText._data = 'foo';
231+
232+
renderer.markToSync( 'text', viewText );
233+
234+
renderAndExpectNoChanges( renderer, domRoot );
235+
236+
expect( domRoot.childNodes.length ).to.equal( 1 );
237+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
238+
239+
expect( renderer.markedTexts.size ).to.equal( 0 );
240+
} );
241+
220242
it( 'should not update text parent child list changed', () => {
221243
const viewImg = new ViewElement( 'img' );
222244
const viewText = new ViewText( 'foo' );
@@ -2395,6 +2417,256 @@ describe( 'Renderer', () => {
23952417
expect( renderer.markedTexts.size ).to.equal( 0 );
23962418
} );
23972419
} );
2420+
2421+
describe( '_updateText', () => {
2422+
let viewRoot, domRoot;
2423+
2424+
beforeEach( () => {
2425+
viewRoot = new ViewElement( 'div' );
2426+
domRoot = document.createElement( 'div' );
2427+
document.body.appendChild( domRoot );
2428+
2429+
domConverter.bindElements( domRoot, viewRoot );
2430+
2431+
renderer.markedTexts.clear();
2432+
renderer.markedAttributes.clear();
2433+
renderer.markedChildren.clear();
2434+
2435+
renderer.isFocused = true;
2436+
} );
2437+
2438+
afterEach( () => {
2439+
domRoot.remove();
2440+
} );
2441+
2442+
it( 'should update text - change on end', () => {
2443+
const viewText = new ViewText( 'foo' );
2444+
viewRoot._appendChild( viewText );
2445+
2446+
renderer.markToSync( 'children', viewRoot );
2447+
renderer.render();
2448+
2449+
expect( domRoot.childNodes.length ).to.equal( 1 );
2450+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
2451+
2452+
viewText._data = 'fobar';
2453+
2454+
renderer._updateText( viewText, {} );
2455+
2456+
expect( domRoot.childNodes.length ).to.equal( 1 );
2457+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'fobar' );
2458+
} );
2459+
2460+
it( 'should update text - change on start', () => {
2461+
const viewText = new ViewText( 'foo' );
2462+
viewRoot._appendChild( viewText );
2463+
2464+
renderer.markToSync( 'children', viewRoot );
2465+
renderer.render();
2466+
2467+
expect( domRoot.childNodes.length ).to.equal( 1 );
2468+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
2469+
2470+
viewText._data = 'baro';
2471+
2472+
renderer._updateText( viewText, {} );
2473+
2474+
expect( domRoot.childNodes.length ).to.equal( 1 );
2475+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'baro' );
2476+
} );
2477+
2478+
it( 'should update text - change in the middle', () => {
2479+
const viewText = new ViewText( 'foobar' );
2480+
viewRoot._appendChild( viewText );
2481+
2482+
renderer.markToSync( 'children', viewRoot );
2483+
renderer.render();
2484+
2485+
expect( domRoot.childNodes.length ).to.equal( 1 );
2486+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foobar' );
2487+
2488+
viewText._data = 'fobazr';
2489+
2490+
renderer._updateText( viewText, {} );
2491+
2492+
expect( domRoot.childNodes.length ).to.equal( 1 );
2493+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'fobazr' );
2494+
} );
2495+
2496+
it( 'should update text - empty expected', () => {
2497+
const viewText = new ViewText( 'foo' );
2498+
viewRoot._appendChild( viewText );
2499+
2500+
renderer.markToSync( 'children', viewRoot );
2501+
renderer.render();
2502+
2503+
expect( domRoot.childNodes.length ).to.equal( 1 );
2504+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
2505+
2506+
viewText._data = '';
2507+
2508+
renderer._updateText( viewText, {} );
2509+
2510+
expect( domRoot.childNodes.length ).to.equal( 1 );
2511+
expect( domRoot.childNodes[ 0 ].data ).to.equal( '' );
2512+
} );
2513+
2514+
it( 'should update text - empty actual', () => {
2515+
const viewText = new ViewText( '' );
2516+
viewRoot._appendChild( viewText );
2517+
2518+
renderer.markToSync( 'children', viewRoot );
2519+
renderer.render();
2520+
2521+
expect( domRoot.childNodes.length ).to.equal( 1 );
2522+
expect( domRoot.childNodes[ 0 ].data ).to.equal( '' );
2523+
2524+
viewText._data = 'fobar';
2525+
2526+
renderer._updateText( viewText, {} );
2527+
2528+
expect( domRoot.childNodes.length ).to.equal( 1 );
2529+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'fobar' );
2530+
} );
2531+
2532+
it( 'should handle filler during text modifications', () => {
2533+
const viewText = new ViewText( 'foo' );
2534+
viewRoot._appendChild( viewText );
2535+
2536+
renderer.markToSync( 'children', viewRoot );
2537+
renderer.render();
2538+
2539+
expect( domRoot.childNodes.length ).to.equal( 1 );
2540+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'foo' );
2541+
2542+
// 1. Insert filler.
2543+
renderer._updateText( viewText, {
2544+
inlineFillerPosition: {
2545+
parent: viewText.parent,
2546+
offset: 0
2547+
}
2548+
} );
2549+
2550+
expect( domRoot.childNodes.length ).to.equal( 1 );
2551+
expect( domRoot.childNodes[ 0 ].data ).to.equal( INLINE_FILLER + 'foo' );
2552+
2553+
// 2. Edit text - filler should be preserved.
2554+
viewText._data = 'barfoo';
2555+
2556+
renderer._updateText( viewText, {
2557+
inlineFillerPosition: {
2558+
parent: viewText.parent,
2559+
offset: 0
2560+
}
2561+
} );
2562+
2563+
expect( domRoot.childNodes.length ).to.equal( 1 );
2564+
expect( domRoot.childNodes[ 0 ].data ).to.equal( INLINE_FILLER + 'barfoo' );
2565+
2566+
// 3. Remove filler.
2567+
renderer._updateText( viewText, {} );
2568+
2569+
expect( domRoot.childNodes.length ).to.equal( 1 );
2570+
expect( domRoot.childNodes[ 0 ].data ).to.equal( 'barfoo' );
2571+
} );
2572+
2573+
it( 'should handle filler during text modifications - empty text', () => {
2574+
const viewText = new ViewText( '' );
2575+
viewRoot._appendChild( viewText );
2576+
2577+
renderer.markToSync( 'children', viewRoot );
2578+
renderer.render();
2579+
2580+
expect( domRoot.childNodes.length ).to.equal( 1 );
2581+
expect( domRoot.childNodes[ 0 ].data ).to.equal( '' );
2582+
2583+
// 1. Insert filler.
2584+
renderer._updateText( viewText, {
2585+
inlineFillerPosition: {
2586+
parent: viewText.parent,
2587+
offset: 0
2588+
}
2589+
} );
2590+
2591+
expect( domRoot.childNodes.length ).to.equal( 1 );
2592+
expect( domRoot.childNodes[ 0 ].data ).to.equal( INLINE_FILLER );
2593+
2594+
// 2. Edit text - filler should be preserved.
2595+
viewText._data = 'foo';
2596+
2597+
renderer._updateText( viewText, {
2598+
inlineFillerPosition: {
2599+
parent: viewText.parent,
2600+
offset: 0
2601+
}
2602+
} );
2603+
2604+
expect( domRoot.childNodes.length ).to.equal( 1 );
2605+
expect( domRoot.childNodes[ 0 ].data ).to.equal( INLINE_FILLER + 'foo' );
2606+
2607+
// 3. Remove filler.
2608+
viewText._data = '';
2609+
2610+
renderer._updateText( viewText, {} );
2611+
2612+
expect( domRoot.childNodes.length ).to.equal( 1 );
2613+
expect( domRoot.childNodes[ 0 ].data ).to.equal( '' );
2614+
} );
2615+
2616+
it( 'should handle filler during text modifications inside inline element', () => {
2617+
const viewB = new ViewElement( 'b' );
2618+
const viewText = new ViewText( 'foo' );
2619+
2620+
viewB._appendChild( viewText );
2621+
viewRoot._appendChild( viewB );
2622+
2623+
renderer.markToSync( 'children', viewRoot );
2624+
renderer.render();
2625+
2626+
expect( domRoot.childNodes.length ).to.equal( 1 );
2627+
expect( domRoot.childNodes[ 0 ].tagName ).to.equal( 'B' );
2628+
expect( domRoot.childNodes[ 0 ].childNodes.length ).to.equal( 1 );
2629+
expect( domRoot.childNodes[ 0 ].childNodes[ 0 ].data ).to.equal( 'foo' );
2630+
2631+
// 1. Insert filler.
2632+
renderer._updateText( viewText, {
2633+
inlineFillerPosition: {
2634+
parent: viewText.parent,
2635+
offset: 0
2636+
}
2637+
} );
2638+
2639+
expect( domRoot.childNodes.length ).to.equal( 1 );
2640+
expect( domRoot.childNodes[ 0 ].tagName ).to.equal( 'B' );
2641+
expect( domRoot.childNodes[ 0 ].childNodes.length ).to.equal( 1 );
2642+
expect( domRoot.childNodes[ 0 ].childNodes[ 0 ].data ).to.equal( INLINE_FILLER + 'foo' );
2643+
2644+
// 2. Edit text - filler should be preserved.
2645+
viewText._data = 'bar';
2646+
2647+
renderer._updateText( viewText, {
2648+
inlineFillerPosition: {
2649+
parent: viewText.parent,
2650+
offset: 0
2651+
}
2652+
} );
2653+
2654+
expect( domRoot.childNodes.length ).to.equal( 1 );
2655+
expect( domRoot.childNodes[ 0 ].tagName ).to.equal( 'B' );
2656+
expect( domRoot.childNodes[ 0 ].childNodes.length ).to.equal( 1 );
2657+
expect( domRoot.childNodes[ 0 ].childNodes[ 0 ].data ).to.equal( INLINE_FILLER + 'bar' );
2658+
2659+
// 3. Remove filler.
2660+
viewText._data = 'bar';
2661+
2662+
renderer._updateText( viewText, {} );
2663+
2664+
expect( domRoot.childNodes.length ).to.equal( 1 );
2665+
expect( domRoot.childNodes[ 0 ].tagName ).to.equal( 'B' );
2666+
expect( domRoot.childNodes[ 0 ].childNodes.length ).to.equal( 1 );
2667+
expect( domRoot.childNodes[ 0 ].childNodes[ 0 ].data ).to.equal( 'bar' );
2668+
} );
2669+
} );
23982670
} );
23992671

24002672
function renderAndExpectNoChanges( renderer, domRoot ) {

0 commit comments

Comments
 (0)