jenkins-bot has submitted this change and it was merged.

Change subject: Highlight changes in the unified diff column
......................................................................


Highlight changes in the unified diff column

This patch introduces a new DiffFormater that builds similar to
the ArrayDiffFormater an array that aligns changes to the lines
from the original text. The formatted array will then be used
to build the highlighted and annotated single column diff view
for conflict solving.

Bug: T149720
Change-Id: Ib0c9911fe9f1e1e633df55183f1dd1c9765fd6c0
---
M extension.json
M i18n/en.json
M i18n/qqq.json
A includes/LineBasedUnifiedDiffFormatter.php
M includes/TwoColConflictPage.php
M modules/ext.TwoColConflict.css
A tests/phpunit/includes/LineBasedUnifiedDiffFormatterTest.php
7 files changed, 798 insertions(+), 39 deletions(-)

Approvals:
  Tobias Gritschacher: Looks good to me, but someone else must approve
  Addshore: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index 4d8d187..4af7921 100644
--- a/extension.json
+++ b/extension.json
@@ -11,7 +11,8 @@
        "manifest_version": 1,
        "AutoloadClasses": {
                "TwoColConflictHooks": "TwoColConflict.hooks.php",
-               "TwoColConflictPage": "includes/TwoColConflictPage.php"
+               "TwoColConflictPage": "includes/TwoColConflictPage.php",
+               "LineBasedUnifiedDiffFormatter": 
"includes/LineBasedUnifiedDiffFormatter.php"
        },
        "Hooks": {
                "AlternateEdit": [
diff --git a/i18n/en.json b/i18n/en.json
index 77dac1b..ac923b8 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -8,7 +8,9 @@
        "twoColConflict-desc": "Showing a side-by-side edit merge screen for 
edit conflict resolution",
        "twoColConflict-explainconflict": "Another user just edited and saved 
this page. There is a conflict between your version and the current version. 
You will have to merge your changes into the current text version. Only the 
text in the editor field will be saved when you click on \"$1\".",
        "twoColConflict-changes-col-title": "Conflicting changes",
-       "twoColConflict-changes-col-desc": "Differences between the currently 
published version (by $1 on $2) and your text ($3)",
+       "twoColConflict-changes-col-desc": "Differences between the currently 
published version (by $1 on $2) and <span 
class=\"mw-twocolconflict-user\">your</span> text ($3)",
        "twoColConflict-editor-col-title": "Editor with version to be 
published",
-       "twoColConflict-editor-col-desc": "The version in the editor will be 
published. Initially, its content is the currently published version (by $1 on 
$2)"
+       "twoColConflict-editor-col-desc": "The version in the editor will be 
published. Initially, its content is the currently published version (by $1 on 
$2)",
+       "twoColConflict-diffchange-own-title": "in your version",
+       "twoColConflict-diffchange-foreign-title": "in $1's version"
 }
\ No newline at end of file
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 131246e..e39b03f 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -7,8 +7,10 @@
        "twoColConflict": "TwoColConflict",
        "twoColConflict-desc": 
"{{desc|name=TwoColConflict|url=https://www.mediawiki.org/wiki/Extension:TwoColConflict}}";,
        "twoColConflict-explainconflict": "Appears at the top of a page when 
there is an edit conflict.\n\nParameters:\n$1 - Label of the save button. 
Either {{msg-mw|savechanges}} or {{msg-mw|publishchanges}}.\n\nSee also:\n* 
{{msg-mw|Savearticle}}",
-       "twoColConflict-changes-col-title": "Header for the conflicting changes 
column.",
-       "twoColConflict-changes-col-desc": "Description text for the 
conflicting changes column.\n\nParameters:\n$1 - Username of user that edited 
the current revision of the page.\n$2 - User-formatted time and date when the 
current revision was changed.\n$3 - User-formatted time and date of the 
conflicting change.",
+       "twoColConflict-changes-col-title": "Header for the unified diff column 
with the changes.",
+       "twoColConflict-changes-col-desc": "Description text for the unified 
diff column with the changes.\n\nParameters:\n$1 - Username of the user that 
edited the current revision of the page.\n$2 - User-formatted time and date 
when the current revision was changed.\n$3 - User-formatted time and date of 
the conflicting change.",
        "twoColConflict-editor-col-title": "Header for the editor column.",
-       "twoColConflict-editor-col-desc": "Description text for the editor 
column.\n\nParameters:\n$1 - Username of user that edited the last valid 
version.\n$2 - User-formatted time and date when the current revision was 
changed."
+       "twoColConflict-editor-col-desc": "Description text for the editor 
column.\n\nParameters:\n$1 - Username of user that edited the last valid 
version.\n$2 - User-formatted time and date when the current revision was 
changed.",
+       "twoColConflict-diffchange-own-title": "The title text in the unified 
diff view for changes by the user.",
+       "twoColConflict-diffchange-foreign-title": "The title text in the 
unified diff view for conflicting changes.\n\nParameters:\n$1 - Username of the 
user that edited the current revision of the page."
 }
\ No newline at end of file
diff --git a/includes/LineBasedUnifiedDiffFormatter.php 
b/includes/LineBasedUnifiedDiffFormatter.php
new file mode 100644
index 0000000..2eba4c7
--- /dev/null
+++ b/includes/LineBasedUnifiedDiffFormatter.php
@@ -0,0 +1,201 @@
+<?php
+use MediaWiki\Diff\WordAccumulator;
+
+/**
+ * @license GNU GPL v2+
+ * @author Christoph Jauera <[email protected]>
+ */
+class LineBasedUnifiedDiffFormatter extends DiffFormatter {
+       public $insClass = ' class="diffchange"';
+       public $delClass = ' class="diffchange"';
+
+       private $oldline = 1;
+       private $newline = 1;
+       private $retval = [];
+
+       /**
+        * @param Diff $diff A Diff object.
+        *
+        * @return array[] Associative array showing lists of changes in lines 
of the original text.
+        */
+       public function format( $diff ) {
+               $this->oldline = 1;
+               $this->newline = 1;
+               $this->retval = [];
+
+               foreach ( $diff->getEdits() as $edit ) {
+                       switch ( $edit->getType() ) {
+                               case 'add':
+                                       $this->addLines( $edit->getClosing() );
+                                       $this->newline += count( 
$edit->getClosing() );
+                                       break;
+                               case 'delete':
+                                       $this->deleteLines( $edit->getOrig() );
+                                       $this->oldline += count( 
$edit->getOrig() );
+                                       break;
+                               case 'change':
+                                       $wordLevelDiff = 
$this->getWordLevelDiff( $edit->getOrig(), $edit->getClosing() );
+                                       if ( $wordLevelDiff ) {
+                                               $this->retval[ $this->oldline 
][] = [
+                                                       'action' => 'delete',
+                                                       'old' => 
$this->getOriginalInlineDiff( $wordLevelDiff ),
+                                                       'oldline' => 
$this->oldline,
+                                               ];
+                                               $this->retval[ $this->oldline 
][] = [
+                                                       'action' => 'add',
+                                                       'new' => 
$this->getClosingInlineDiff( $wordLevelDiff ),
+                                                       'newline' => 
$this->newline
+                                               ];
+                                       } else {
+                                               $this->deleteLines( 
$edit->getOrig() );
+                                               $this->addLines( 
$edit->getClosing() );
+                                       }
+                                       $this->oldline += count( 
$edit->getOrig() );
+                                       $this->newline += count( 
$edit->getClosing() );
+                                       break;
+                               case 'copy':
+                                       $this->copyLines( $edit->getOrig() );
+                                       $this->oldline += count( 
$edit->getOrig() );
+                                       $this->newline += count( 
$edit->getOrig() );
+                                       break;
+                       }
+               }
+
+               return $this->retval;
+       }
+
+       /**
+        * @param string[] $lines Lines that should be marked deleted.
+        */
+       private function deleteLines( array $lines ) {
+               $this->retval[ $this->oldline ][] = [
+                       'action' => 'delete',
+                       'old' => "<del{$this->delClass}>" . 
$this->composeLines( $lines ) . '</del>',
+                       'oldline' => $this->oldline,
+               ];
+       }
+
+       /**
+        * @param string[] $lines Lines that should be marked as added.
+        */
+       private function addLines( array $lines ) {
+               $this->retval[ $this->oldline ][] = [
+                       'action' => 'add',
+                       'new' => "<ins{$this->insClass}>" . 
$this->composeLines( $lines ) . '</ins>',
+                       'newline' => $this->newline
+               ];
+       }
+
+       /**
+        * @param string[] $lines Lines that should be copied.
+        */
+       private function copyLines( array $lines ) {
+               $this->retval[ $this->oldline ][] = [
+                       'action' => 'copy',
+                       'copy' => $this->composeLines( $lines, false ),
+                       'oldline' => $this->oldline,
+                       'newline' => $this->newline
+               ];
+       }
+
+       /**
+        * Gets a diff on word level of two lines.
+        * Returns false if the number of coherent changes is over 5.
+        *
+        * @param string[] $orig Lines that should be marked deleted.
+        * @param string[] $closing Lines that should be marked deleted.
+        *
+        *  @return WordLevelDiff|boolean
+        */
+       private function getWordLevelDiff( array $orig, array $closing ) {
+               $diff = new WordLevelDiff( $orig, $closing );
+
+               // when comparing long multi-word lines getting an inline 
results might lead to
+               // a lot inline edits that confuse more then help
+               if ( count( $diff->getEdits() ) > 5 ) {
+                       return false;
+               }
+
+               return $diff;
+       }
+
+       /**
+        * Composes lines from a WordLevelDiff and marks removed words.
+        *
+        * @param WordLevelDiff $diff Diff on word level.
+        *
+        * @return string Composed string with marked lines.
+        */
+       private function getOriginalInlineDiff( WordLevelDiff $diff ) {
+               $wordAccumulator = $this->getWordAccumulator();
+
+               foreach ( $diff->getEdits() as $edit ) {
+                       if ( $edit->type == 'copy' ) {
+                               $wordAccumulator->addWords( $edit->orig );
+                       } elseif ( $edit->orig ) {
+                               $wordAccumulator->addWords( $edit->orig, 'del' 
);
+                       }
+               }
+               return implode( "\n", $wordAccumulator->getLines() );
+       }
+
+       /**
+        * Composes lines from a WordLevelDiff and marks added words.
+        *
+        * @param WordLevelDiff $diff Diff on word level.
+        *
+        * @return string Composed string with marked lines.
+        */
+       private function getClosingInlineDiff( WordLevelDiff $diff ) {
+               $wordAccumulator = $this->getWordAccumulator();
+
+               foreach ( $diff->getEdits() as $edit ) {
+                       if ( $edit->type == 'copy' ) {
+                               $wordAccumulator->addWords( $edit->closing );
+                       } elseif ( $edit->closing ) {
+                               $wordAccumulator->addWords( $edit->closing, 
'ins' );
+                       }
+               }
+               return implode( "\n", $wordAccumulator->getLines() );
+       }
+
+       /**
+        * @return WordAccumulator
+        */
+       private function getWordAccumulator() {
+               $wordAccumulator = new WordAccumulator;
+               $wordAccumulator->insClass = $this->insClass;
+               $wordAccumulator->delClass = $this->delClass;
+               return $wordAccumulator;
+       }
+
+       /**
+        * @param string[] $lines Lines that should be composed.
+        * @param boolean $replaceEmptyLine
+        *
+        * @return string
+        */
+       private function composeLines( array $lines, $replaceEmptyLine = true ) 
{
+               $result = [];
+                       foreach ( $lines as $line ) {
+                               $line = htmlspecialchars( $line );
+                               $result[] = $this->replaceEmptyLine( $line, 
$replaceEmptyLine );
+                       }
+               return implode( "\n", $result );
+       }
+
+       /**
+        * Replace empty lines with a NBSP
+        *
+        * @param string $line Lines that should be altered.
+        * @param boolean $replaceEmptyLine
+        *
+        * @return string
+        */
+       private function replaceEmptyLine( $line, $replaceEmptyLine = true ) {
+               if ( $line === '' && $replaceEmptyLine ) {
+                       $line = '&#160;';
+               }
+               return $line;
+       }
+}
diff --git a/includes/TwoColConflictPage.php b/includes/TwoColConflictPage.php
index 6f0d9f2..7acbfae 100644
--- a/includes/TwoColConflictPage.php
+++ b/includes/TwoColConflictPage.php
@@ -1,25 +1,50 @@
 <?php
 
+/**
+ * @license GNU GPL v2+
+ * @author Christoph Jauera <[email protected]>
+ */
 class TwoColConflictPage extends EditPage {
 
+       /**
+        * TwoColConflictPage constructor.
+        *
+        * @param Article $article
+        */
+       public function __construct( Article $article ) {
+               parent::__construct( $article );
+       }
+
+       /**
+        * Replace default header for explaining the conflict screen.
+        *
+        * @param OutputPage $out
+        */
        protected function addExplainConflictHeader( OutputPage $out ) {
                $labelAsPublish = 
$this->mArticle->getContext()->getConfig()->get(
                        'EditSubmitButtonLabelPublish'
                );
 
-               $buttonLabel = $this->context->msg(
+               $buttonLabel = $this->getContext()->msg(
                        $labelAsPublish ? 'publishchanges' : 'savechanges'
                )->text();
 
                $out->wrapWikiMsg(
                        "<div 
class='mw-twocolconflict-explainconflict'>\n$1\n</div>",
-                       $this->context->msg( 'twoColConflict-explainconflict', 
$buttonLabel )
+                       $this->getContext()->msg( 
'twoColConflict-explainconflict', $buttonLabel )
                );
        }
 
+       /**
+        * Set the HTML to encapsulate the default edit form.
+        *
+        * @param callable|null $formCallback
+        */
        public function showEditForm( $formCallback = null ) {
                if ( $this->isConflict ) {
-                       $this->addModules();
+                       $this->addCSS();
+                       $this->editFormTextTop = '<div 
class="mw-twocolconflict-form">';
+                       $this->editFormTextBottom = '</div>';
                        $this->editFormTextBeforeContent = 
$this->addEditFormBeforeContent();
                        $this->editFormTextAfterContent = 
$this->addEditFormAfterContent();
                }
@@ -27,39 +52,44 @@
                parent::showEditForm( $formCallback );
        }
 
-       // For the first version show at least the default diff view.
        protected function showConflict() {
-               global $wgOut;
-               // TODO What should happen if someone calls the hook in here?
-
-               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
-
-               $content1 = $this->toEditContent( $this->textbox1 );
-               $content2 = $this->toEditContent( $this->textbox2 );
-
-               $handler = ContentHandler::getForModelID( $this->contentModel );
-               $de = $handler->createDifferenceEngine( 
$this->mArticle->getContext() );
-               $de->setContent( $content2, $content1 );
-               $de->showDiff(
-                       $this->context->msg( 'yourtext' )->parse(),
-                       $this->context->msg( 'storedversion' )->text()
-               );
+               // don't show the original conflict view at the bottom
+               return false;
        }
 
+       /**
+        * Build HTML that will be added before the default edit form.
+        *
+        * @return string
+        */
        private function addEditFormBeforeContent() {
                return $this->buildConflictPageChangesCol() . 
$this->buildConflictPageEditorCol();
        }
 
+       /**
+        * Build HTML content that will be added after the default edit form.
+        *
+        * @return string
+        */
        private function addEditFormAfterContent() {
+               // this div is opened when encapsulating the editor in 
buildConflictPageEditorCol.
                return '</div>';
        }
 
+       /**
+        * Build HTML that will add the textbox with the unified diff.
+        *
+        * @return string
+        */
        private function buildConflictPageChangesCol() {
                global $wgUser;
 
-               $lastUser = $this->mArticle->getPage()->getUserText();
+               $lastUser =
+                       '<span class="mw-twocolconflict-lastuser">' .
+                       $this->mArticle->getPage()->getUserText() .
+                       '</span>';
                $lastChangeTime = 
$this->getContext()->getLanguage()->userTimeAndDate(
-                       $this->mArticle->getPage()->getTimestamp(),
+                       $this->getArticle()->getPage()->getTimestamp(),
                        $wgUser
                );
                $yourChangeTime = 
$this->getContext()->getLanguage()->userTimeAndDate(
@@ -78,11 +108,16 @@
                return $out;
        }
 
+       /**
+        * Build HTML for the textbox with the unified diff.
+        *
+        * @return string
+        */
        private function buildChangesTextbox() {
                global $wgUser;
 
                $name = 'mw-twocolconflict-changes-editor';
-               $wikitext = $this->safeUnicodeOutput( $this->textbox1 );
+               $wikitext = $this->safeUnicodeOutput( 
$this->getUnifiedDiffText() );
                $wikitext = $this->addNewLineAtEnd( $wikitext );
 
                $customAttribs = [];
@@ -95,11 +130,16 @@
                return Html::rawElement( 'div', $attribs, $wikitext );
        }
 
+       /**
+        * Build HTML to encapsulate editor with the conflicting text.
+        *
+        * @return string
+        */
        private function buildConflictPageEditorCol() {
                global $wgUser;
 
-               $lastUser = $this->mArticle->getPage()->getUserText();
-               $lastChangeTime = $this->mArticle->getPage()->getTimestamp();
+               $lastUser = $this->getArticle()->getPage()->getUserText();
+               $lastChangeTime = 
$this->getArticle()->getPage()->getTimestamp();
                $lastChangeTime = 
$this->getContext()->getLanguage()->userTimeAndDate( $lastChangeTime, $wgUser );
 
                $out = '<div class="mw-twocolconflict-editor-col">';
@@ -111,19 +151,81 @@
                return $out;
        }
 
-       private function addModules() {
-               $this->loadAndAddModule( 'ext.TwoColConflict.editor' );
+       /**
+        * Get array with line based diff changes.
+        *
+        * @param string[] $fromTextLines
+        * @param string[] $toTextLines
+        * @return array[]
+        */
+       private function getLineBasedUnifiedDiff( $fromTextLines, $toTextLines 
) {
+               $formatter = new LineBasedUnifiedDiffFormatter();
+               $formatter->insClass = ' class="mw-twocolconflict-diffchange"';
+               $formatter->delClass = ' class="mw-twocolconflict-diffchange"';
+               return $formatter->format(
+                       new Diff( $fromTextLines, $toTextLines )
+               );
        }
 
-       private function loadAndAddModule( $name ) {
-               global $wgOut;
+       /**
+        * Build HTML for the content of the unified diff box.
+        *
+        * @return string
+        */
+       private function getUnifiedDiffText() {
+               $lastUser = $this->getArticle()->getPage()->getUserText();
+               $currentText = $this->toEditText( $this->getCurrentContent() );
+               $yourText = $this->textbox1;
 
-               if ( $wgOut->getResourceLoader()->isModuleRegistered( $name ) ) 
{
-                       $wgOut->addModules( $name );
+               $currentLines = explode( "\n", $currentText );
+               $yourLines = explode( "\n", $yourText );
+
+               $combinedChanges = $this->getLineBasedUnifiedDiff( 
$currentLines, $yourLines );
+
+               $output = [];
+               foreach ( $currentLines as $key => $currentLine ) {
+                       ++$key;
+                       if ( isset( $combinedChanges[ $key ] ) ) {
+                               foreach ( $combinedChanges[ $key ] as 
$changeSet ) {
+                                       switch ( $changeSet[ 'action' ] ) {
+                                               case 'add':
+                                                       $output[] = '<div 
class="mw-twocolconflict-diffchange-own">' .
+                                                               '<div 
class="mw-twocolconflict-diffchange-title">' .
+                                                               
$this->getContext()->msg( 'twoColConflict-diffchange-own-title' ) .
+                                                               '</div>';
+                                                       $output[] = 
$changeSet['new'] . '</div>';
+                                                       break;
+                                               case 'delete':
+                                                       $output[] = '<div 
class="mw-twocolconflict-diffchange-foreign">' .
+                                                               '<div 
class="mw-twocolconflict-diffchange-title">' .
+                                                               
$this->getContext()->msg(
+                                                                       
'twoColConflict-diffchange-foreign-title',
+                                                                       
$lastUser
+                                                               ) .
+                                                               '</div>';
+                                                       $output[] = 
$changeSet['old'] . '</div>';
+                                                       break;
+                                               case 'copy':
+                                                       $output[] = '<div 
class="mw-twocolconflict-diffchange-same">' .
+                                                               
$changeSet['copy'] . '</div>';
+                                                       break;
+                                       }
+                               }
+                       }
                }
+
+               return implode( "\n", $output );
        }
 
        private function wikiEditorIsEnabled() {
                return class_exists( WikiEditorHooks::class ) && 
WikiEditorHooks::isEnabled( 'toolbar' );
        }
+
+       private function addCSS() {
+               global $wgOut;
+
+               if ( $wgOut->getResourceLoader()->isModuleRegistered( 
'ext.TwoColConflict.editor' ) ) {
+                       $wgOut->addModuleStyles( 'ext.TwoColConflict.editor' );
+               }
+       }
 }
diff --git a/modules/ext.TwoColConflict.css b/modules/ext.TwoColConflict.css
index f2bfa06..048926c 100644
--- a/modules/ext.TwoColConflict.css
+++ b/modules/ext.TwoColConflict.css
@@ -7,14 +7,21 @@
 .mw-twocolconflict-changes-col, .mw-twocolconflict-editor-col {
        display: table-cell;
        width: 50%;
-       padding: 0 0.5em;
+}
+
+.mw-twocolconflict-changes-col {
+       padding-right: 0.5em;
+}
+
+.mw-twocolconflict-editor-col {
+       padding-left: 0.5em;
 }
 
 .mw-twocolconflict-col-desc {
        font-size: 0.9em;
  }
 
-.editOptions {
+.mw-twocolconflict-form .editOptions {
        border-top: 1px solid #c0c0c0;
        margin-top: 1em;
 }
@@ -42,10 +49,14 @@
        text-shadow: none;
        text-align: start;
        overflow: auto;
-       padding: 0.1em;
+       padding: 2px;
        width: 100%;
        height: 400px;
        background-color: #f2f2f2;
+}
+
+#mw-twocolconflict-changes-editor div {
+       display: inline-block;
 }
 
 #mw-twocolconflict-changes-editor.mw-twocolconflict-wikieditor {
@@ -56,3 +67,41 @@
 .mw-twocolconflict-editor-col #wpTextbox1 {
        height: 400px;
 }
+
+.mw-twocolconflict-lastuser, del.mw-twocolconflict-diffchange {
+       background-color: #add8e6;
+}
+
+.mw-twocolconflict-user, ins.mw-twocolconflict-diffchange {
+       background-color: #ffffe0;
+}
+
+ins.mw-twocolconflict-diffchange,  del.mw-twocolconflict-diffchange {
+       text-decoration: none;
+}
+
+.mw-twocolconflict-diffchange-own,
+.mw-twocolconflict-diffchange-foreign,
+.mw-twocolconflict-diffchange-same {
+       margin: 5px 0 3px 0;
+}
+
+.mw-twocolconflict-diffchange-same {
+       background-color: #e2e2e2;
+}
+
+.mw-twocolconflict-diffchange-own .mw-twocolconflict-diffchange-title,
+.mw-twocolconflict-diffchange-foreign .mw-twocolconflict-diffchange-title {
+       color: white;
+       padding: 1px 2px;
+       font-weight: bold;
+       border-radius: 2px;
+}
+
+.mw-twocolconflict-diffchange-own .mw-twocolconflict-diffchange-title {
+       background-color: #ff8c00;
+}
+
+.mw-twocolconflict-diffchange-foreign .mw-twocolconflict-diffchange-title {
+       background-color: #3879ec;
+}
diff --git a/tests/phpunit/includes/LineBasedUnifiedDiffFormatterTest.php 
b/tests/phpunit/includes/LineBasedUnifiedDiffFormatterTest.php
new file mode 100644
index 0000000..04b8c73
--- /dev/null
+++ b/tests/phpunit/includes/LineBasedUnifiedDiffFormatterTest.php
@@ -0,0 +1,402 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @author Christoph Jauera <[email protected]>
+ */
+class LineBasedUnifiedDiffFormatterTest extends MediaWikiTestCase {
+
+       /**
+        * @param string $before
+        * @param string $after
+        * @param array $expectedOutput
+        * @dataProvider provider_testFormat
+        * @covers LineBasedUnifiedDiffFormatter::format
+        */
+       public function testFormat( $before, $after, $result ) {
+               $diff = new Diff( explode( "\n", $before ), explode( "\n", 
$after ) );
+               $instance = new LineBasedUnifiedDiffFormatter();
+               $output = $instance->format( $diff );
+               $this->assertEquals( $result, $output );
+       }
+
+       public function provider_testFormat() {
+               return [
+                       [
+                               'before' => 'Just text.',
+                               'after' => 'Just text.',
+                               'result' => [
+                                       1 => [
+                                               [
+                                                       'action' => 'copy',
+                                                       'copy' => 'Just text.',
+                                                       'oldline' => 1,
+                                                       'newline' => 1
+                                               ]
+                                       ]
+                               ],
+                       ],
+                       [
+                               'before' => 'Just text.',
+                               'after' => 'Just text. And more.',
+                               'result' =>
+                                       [
+                                               1 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' 
=> 'Just text.',
+                                                                       
'oldline' => 1
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' 
=> 'Just text<ins class="diffchange">. And more</ins>.',
+                                                                       
'newline' => 1
+                                                               ]
+                                                       ],
+                                       ],
+                       ],
+                       [
+                               'before' => 'Just less text.',
+                               'after' => 'Just less.',
+                               'result' =>
+                                       [
+                                               1 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' 
=> 'Just less <del class="diffchange">text</del>.',
+                                                                       
'oldline' => 1
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' 
=> 'Just less.',
+                                                                       
'newline' => 1
+                                                               ]
+                                                       ]
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just multi-line text.
+Line number 2.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just multi-line text.
+Line number 1.5.
+Line number 2.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => 'Just 
multi-line text.',
+                                                               'oldline' => 1,
+                                                               'newline' => 1
+                                                       ]
+                                               ],
+                                               2 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' 
=> '<ins class="diffchange">Line number 1.5.</ins>',
+                                                                       
'newline' => 2
+                                                               ],
+                                                               [
+                                                                       
'action' => 'copy',
+                                                                       'copy' 
=> 'Line number 2.',
+                                                                       
'oldline' => 2,
+                                                                       
'newline' => 3
+                                                               ]
+                                                       ]
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just multi-line text.
+Line number 1.5.
+Line number 2.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just multi-line text.
+Line number 1.5.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => "Just 
multi-line text.\nLine number 1.5.",
+                                                               'oldline' => 1,
+                                                               'newline' => 1
+                                                       ]
+                                               ],
+                                               3 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' 
=> '<del class="diffchange">Line number 2.</del>',
+                                                                       
'oldline' => 3
+                                                               ]
+                                                       ]
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just multi-line text.
+Line number 1.5.
+Line number 2.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just multi-line test.
+Line number 2.
+Line number 3.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' =>
+<<<TEXT
+Just multi-line <del class="diffchange">text.</del>
+<del class="diffchange">Line number 1.5</del>.
+TEXT
+                                                                       ,
+                                                                       
'oldline' => 1
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' 
=> 'Just multi-line <ins class="diffchange">test</ins>.',
+                                                                       
'newline' => 1
+                                                               ]
+                                                       ],
+                                               3 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => 'Line 
number 2.',
+                                                               'oldline' => 3,
+                                                               'newline' => 2
+                                                       ]
+                                               ],
+                                               4 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' 
=> '<ins class="diffchange">Line number 3.</ins>',
+                                                                       
'newline' => 3
+
+                                                               ]
+                                                       ],
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just multi-line text.
+To change number 2.
+To change number 3.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just multi-line test.
+Line number 2 changed.
+Line number 3 also changed.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' =>
+<<<TEXT
+<del class="diffchange">Just multi-line text.
+To change number 2.
+To change number 3.</del>
+TEXT
+                                                                       ,
+                                                                       
'oldline' => 1
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' =>
+<<<TEXT
+<ins class="diffchange">Just multi-line test.
+Line number 2 changed.
+Line number 3 also changed.</ins>
+TEXT
+                                                                       ,
+                                                                       
'newline' => 1
+                                                               ]
+                                                       ],
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just a multi-line text.
+Line number two. This line is quite long!
+And that's line number three - even longer than the line before.
+
+Just another line with an empty line above.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just a multi-line text.
+Add something new.
+Line number two. Now line number three and quite long!
+Add more new stuff.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => 'Just 
a multi-line text.',
+                                                               'oldline' => 1,
+                                                               'newline' => 1
+                                                       ]
+                                               ],
+                                               2 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' =>
+<<<TEXT
+<del class="diffchange">Line number two. This line is quite long!
+And that's line number three - even longer than the line before.
+&#160;
+Just another line with an empty line above.</del>
+TEXT
+                                                                       ,
+                                                                       
'oldline' => 2
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' =>
+<<<TEXT
+<ins class="diffchange">Add something new.
+Line number two. Now line number three and quite long!
+Add more new stuff.</ins>
+TEXT
+                                                                       ,
+                                                                       
'newline' => 2
+                                                               ]
+                                                       ],
+                                       ],
+                       ],
+                       [
+                               'before' =>
+<<<TEXT
+Just a multi-line text.
+Line number two. This line is quite long!
+Line number three.
+TEXT
+                               ,
+                               'after' =>
+<<<TEXT
+Just a multi-line text.
+Line number two. This line is now a bit longer!
+
+And it gets even longer.
+
+Line number three.
+TEXT
+                               ,
+                               'result' =>
+                                       [
+                                               1 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => 'Just 
a multi-line text.',
+                                                               'oldline' => 1,
+                                                               'newline' => 1
+                                                       ]
+                                               ],
+                                               2 =>
+                                                       [
+                                                               [
+                                                                       
'action' => 'delete',
+                                                                       'old' 
=> 'Line number two. This line is ' .
+                                                                               
'<del class="diffchange">quite long</del>!',
+                                                                       
'oldline' => 2
+                                                               ],
+                                                               [
+                                                                       
'action' => 'add',
+                                                                       'new' =>
+<<<TEXT
+Line number two. This line is <ins class="diffchange">now a bit longer</ins>!
+&#160;
+<ins class="diffchange">And it gets even longer.</ins>
+&#160;
+TEXT
+                                                                       ,
+                                                                       
'newline' => 2
+                                                               ],
+                                                       ],
+                                               3 => [
+                                                       [
+                                                               'action' => 
'copy',
+                                                               'copy' => 'Line 
number three.',
+                                                               'oldline' => 3,
+                                                               'newline' => 6
+                                                       ]
+                                               ],
+                                       ],
+                       ],
+               ];
+       }
+
+       /**
+        * @param string $before
+        * @param string $after
+        * @param array $expectedOutput
+        * @dataProvider provider_testMarkupFormat
+        * @covers LineBasedUnifiedDiffFormatter::format
+        */
+       public function testMarkupFormat( $before, $after, $result ) {
+               $diff = new Diff( explode( "\n", $before ), explode( "\n", 
$after ) );
+               $instance = new LineBasedUnifiedDiffFormatter();
+               $output = $instance->format( $diff );
+               $this->assertEquals( $result, $output );
+       }
+
+       public function provider_testMarkupFormat() {
+               return [
+                       [
+                               'before' => 'Text with [markup] <references 
/>.',
+                               'after' => 'Text with [markup] <references />.',
+                               'result' => [
+                                       1 => [
+                                               [
+                                                       'action' => 'copy',
+                                                       'copy' => 'Text with 
[markup] &lt;references /&gt;.',
+                                                       'oldline' => 1,
+                                                       'newline' => 1
+                                               ]
+                                       ]
+                               ],
+                       ]
+               ];
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/322128
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib0c9911fe9f1e1e633df55183f1dd1c9765fd6c0
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/extensions/TwoColConflict
Gerrit-Branch: master
Gerrit-Owner: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-jand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to