├── .circleci └── config.yml ├── .codecov.yml ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── dependabot-auto-merge.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── angular-diff-match-patch.js ├── bower.json ├── index.html ├── index.js ├── jsconfig.json ├── package.json ├── test ├── diffmatchpatch-spec.js ├── karma-watch.conf.js └── karma.conf.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | codecov: codecov/codecov@5.2.1 4 | jobs: 5 | test: 6 | docker: 7 | - image: cimg/node:lts-browsers 8 | steps: 9 | - checkout 10 | - run: 11 | name: Enable corepack 12 | command: sudo corepack enable 13 | - run: 14 | name: Install packages 15 | command: yarn install 16 | - run: 17 | name: Run tests 18 | command: yarn test 19 | - codecov/upload 20 | - store_test_results: 21 | path: ./test/results/junit 22 | - store_artifacts: 23 | path: ./coverage 24 | - persist_to_workspace: 25 | root: ./ 26 | paths: 27 | - package.json 28 | - ./*.js 29 | - README.md 30 | resource_class: large 31 | 32 | deploy: 33 | docker: 34 | - image: cimg/node:lts-browsers 35 | steps: 36 | - attach_workspace: 37 | at: ./ 38 | - run: 39 | name: Set NPM token 40 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 41 | - run: 42 | name: NPM Publish 43 | command: | 44 | if [ ! -z "$CIRCLE_TAG" ]; 45 | then npm publish; 46 | else echo " No tag found, no attempt to publish to npm. "; 47 | fi 48 | 49 | workflows: 50 | test-and-deploy: 51 | jobs: 52 | - test: 53 | filters: # required since `deploy` has tag filters AND requires `build` 54 | tags: 55 | only: /.*/ 56 | - deploy: 57 | requires: 58 | - test 59 | filters: 60 | tags: 61 | only: /^v.*/ 62 | branches: 63 | ignore: /.*/ 64 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" 5 | 6 | comment: 7 | layout: "header, diff" 8 | behavior: default 9 | require_changes: true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2.4.0 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | if: ${{steps.metadata.outputs.dependency-names}} 20 | run: gh pr merge --auto --merge "$PR_URL" 21 | env: 22 | PR_URL: ${{github.event.pull_request.html_url}} 23 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/bower_components/ 2 | **/node_modules/ 3 | **/coverage/ 4 | npm-debug.log 5 | test/results 6 | .DS_Store 7 | yarn-error.log 8 | .pnp.* 9 | .yarn/* 10 | !.yarn/patches 11 | !.yarn/plugins 12 | !.yarn/releases 13 | !.yarn/sdks 14 | !.yarn/versions 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome against localhost", 8 | "url": "http://127.0.0.1:8080/index.html", 9 | "webRoot": "${workspaceRoot}" 10 | }, 11 | { 12 | "type": "chrome", 13 | "request": "attach", 14 | "name": "Attach to Chrome", 15 | "port": 9222, 16 | "webRoot": "${workspaceRoot}" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": false 3 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.8.13](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.12...v0.8.13) (2024-07-08) 6 | 7 | ### [0.8.12](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.11...v0.8.12) (2024-07-08) 8 | 9 | ### [0.8.11](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.10...v0.8.11) (2024-07-08) 10 | 11 | ### [0.8.10](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.8...v0.8.10) (2024-07-08) 12 | 13 | ### [0.8.9](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.8...v0.8.9) (2024-07-08) 14 | 15 | ### [0.8.8](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.7...v0.8.8) (2023-01-19) 16 | 17 | ### [0.8.7](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.6...v0.8.7) (2023-01-19) 18 | 19 | ### [0.8.6](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.5...v0.8.6) (2022-09-07) 20 | 21 | ### [0.8.5](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.4...v0.8.5) (2022-02-11) 22 | 23 | ### [0.8.4](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.3...v0.8.4) (2022-02-10) 24 | 25 | ### [0.8.3](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.2...v0.8.3) (2021-11-20) 26 | 27 | ### [0.8.2](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.1...v0.8.2) (2021-10-13) 28 | 29 | ### [0.8.1](https://github.com/amweiss/angular-diff-match-patch/compare/v0.8.0...v0.8.1) (2021-05-14) 30 | 31 | ## [0.8.0](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.83...v0.8.0) (2021-04-21) 32 | 33 | 34 | ### ⚠ BREAKING CHANGES 35 | 36 | * use ES2015 stuff 37 | * use ES2015 stuff 38 | 39 | * Merge pull request #231 from amweiss/dependabot/npm_and_yarn/xo-0.39.1 ([0615dcf](https://github.com/amweiss/angular-diff-match-patch/commit/0615dcf1bb8bff577efa17c64b0964568026d604)), closes [#231](https://github.com/amweiss/angular-diff-match-patch/issues/231) 40 | * Fix linting ([ff7a7ed](https://github.com/amweiss/angular-diff-match-patch/commit/ff7a7edb91a6a433bf95e75ad0d5be3357eede1c)) 41 | 42 | ### [0.7.83](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.82...v0.7.83) (2021-03-01) 43 | 44 | ### [0.7.82](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.81...v0.7.82) (2021-03-01) 45 | 46 | ### [0.7.81](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.80...v0.7.81) (2021-01-07) 47 | 48 | ### [0.7.80](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.79...v0.7.80) (2021-01-07) 49 | 50 | ### [0.7.79](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.78...v0.7.79) (2020-11-20) 51 | 52 | ### [0.7.78](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.77...v0.7.78) (2020-08-06) 53 | 54 | ### [0.7.77](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.76...v0.7.77) (2020-05-08) 55 | 56 | ### [0.7.76](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.75...v0.7.76) (2020-04-24) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * **bug:** Fix [#148](https://github.com/amweiss/angular-diff-match-patch/issues/148) ([f2d162f](https://github.com/amweiss/angular-diff-match-patch/commit/f2d162f910eb0ca8ae475644eecbe7b32ef93275)) 62 | 63 | ### [0.7.75](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.74...v0.7.75) (2020-04-24) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * **bug:** Fix [#148](https://github.com/amweiss/angular-diff-match-patch/issues/148) ([d6390c2](https://github.com/amweiss/angular-diff-match-patch/commit/d6390c2459f35740f5c925e5e8ea63c5e5e2798d)) 69 | 70 | ### [0.7.74](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.52...v0.7.74) (2020-04-23) 71 | 72 | ### [0.7.52](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.28...v0.7.52) (2019-10-23) 73 | 74 | ## [0.7.28](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.4...v0.7.28) (2019-03-15) 75 | 76 | 77 | 78 | 79 | ## [0.7.4](https://github.com/amweiss/angular-diff-match-patch/compare/v0.7.0...v0.7.4) (2018-09-13) 80 | 81 | 82 | 83 | 84 | ## [0.7.0](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.23...v0.7.0) (2018-08-27) 85 | 86 | 87 | 88 | 89 | ## [0.6.23](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.15...v0.6.23) (2018-08-24) 90 | 91 | 92 | 93 | 94 | ## [0.6.16](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.15...v0.6.16) (2018-07-16) 95 | 96 | 97 | 98 | 99 | ## [0.6.15](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.14...v0.6.15) (2018-05-29) 100 | 101 | 102 | 103 | 104 | ## [0.6.14](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.13...v0.6.14) (2018-04-10) 105 | 106 | 107 | ### Bug Fixes 108 | 109 | * **demo:** Fix the js files in the local demo ([341edb0](https://github.com/amweiss/angular-diff-match-patch/commit/341edb0)) 110 | 111 | 112 | 113 | 114 | ## [0.6.13](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.12...v0.6.13) (2018-03-23) 115 | 116 | 117 | 118 | 119 | ## [0.6.12](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.11...v0.6.12) (2018-03-23) 120 | 121 | 122 | 123 | 124 | ## [0.6.11](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.10...v0.6.11) (2018-03-23) 125 | 126 | 127 | 128 | 129 | ## [0.6.10](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.9...v0.6.10) (2017-06-13) 130 | 131 | 132 | ### Bug Fixes 133 | 134 | * **ci:** cci2 config for npm publish ([1c8ac2b](https://github.com/amweiss/angular-diff-match-patch/commit/1c8ac2b)) 135 | 136 | 137 | 138 | 139 | ## [0.6.9](https://github.com/amweiss/angular-diff-match-patch/compare/v0.6.8...v0.6.9) (2017-06-13) 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Adam Weiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-diff-match-patch 2 | ======================== 3 | 4 | [![npm](https://img.shields.io/npm/v/angular-diff-match-patch.svg)](https://www.npmjs.com/package/angular-diff-match-patch) 5 | [![CircleCI](https://img.shields.io/circleci/project/github/amweiss/angular-diff-match-patch/master.svg)](https://circleci.com/gh/amweiss/angular-diff-match-patch/tree/master) [![Codecov](https://img.shields.io/codecov/c/github/amweiss/angular-diff-match-patch.svg?maxAge=2592000)](https://codecov.io/gh/amweiss/angular-diff-match-patch) 6 | 7 | This library is simply a wrapper around [google-diff-match-patch](https://code.google.com/p/google-diff-match-patch/). 8 | 9 | ![Simple](https://i.imgur.com/C2B0pdK.png) 10 | 11 | (Shown here with some custom styles) 12 | 13 | Angular 2 Port 14 | --------------- 15 | 16 | Should you wish to use this in an Angular 2+ project, take a look at this port: [elliotforbes/ng-diff-match-patch](https://github.com/elliotforbes/ng-diff-match-patch) 17 | 18 | Setup 19 | ----- 20 | 21 | Install from [NPM](https://npmjs.com) 22 | 23 | `npm install amweiss/angular-diff-match-patch` 24 | 25 | Install from [Bower](https://bower.io/) 26 | 27 | `bower install angular-diff-match-patch` 28 | 29 | Usage with webpack 30 | 31 | ```javascript 32 | config.plugins = [ 33 | new webpack.ProvidePlugin({ 34 | diff_match_patch: 'diff-match-patch' 35 | }), 36 | ]; 37 | ``` 38 | 39 | Usage 40 | ----- 41 | 42 | See [the included demo](https://amweiss.github.io/angular-diff-match-patch/) for reference or view a sample on [Codepen](https://codepen.io/amweiss/pen/grXNPm). 43 | 44 | ```html 45 |

 46 | ```
 47 | 
 48 | Where `left` and `right` are defined on your scope.  The `options` attribute can be used as well, but it's optional.
 49 | 
 50 | ```javascript
 51 | $scope.options = {
 52 |   editCost: 4,
 53 |   attrs: {
 54 |     insert: {
 55 |       'data-attr': 'insert',
 56 |       'class': 'insertion'
 57 |     },
 58 |     delete: {
 59 |       'data-attr': 'delete'
 60 |     },
 61 |     equal: {
 62 |       'data-attr': 'equal'
 63 |     }
 64 |   }
 65 | };
 66 | ```
 67 | 
 68 | `editCost` is specific to `processingDiff` and controls the tolerence for hunk separation.  `attrs` can contain any/all/none of the following: `insert`, `delete`, and `equal` where the properties in those objects represent attributes that get added to the tags.
 69 | 
 70 | Another option is to skip angular processing the diff, it's useful when you want to show a diff of a code pre-compiled by angular. The attribute you need to add is called: `skipAngularCompilingOnDiff`. If set to `true`, would skip compiling, otherwise it would compile the diff.
 71 | 
 72 | Add some style
 73 | 
 74 | ```css
 75 | .match{
 76 |   color: gray;
 77 | }
 78 | 
 79 | .ins{
 80 |   color: black;
 81 |   background: #bbffbb;
 82 | }
 83 | 
 84 | .del{
 85 |   color: black;
 86 |   background: #ffbbbb;
 87 | }
 88 | ```
 89 | 
 90 | Development
 91 | -----
 92 | 
 93 | Development work requires npm from [Node.js](https://nodejs.org/)
 94 | 
 95 | Begin with:
 96 | 
 97 | `npm install`
 98 | 
 99 | Then you can use:
100 | 
101 | `npm start` To host the directory so you can see the demo
102 | 
103 | `npm test` To run the Jasmine tests once
104 | 
105 | `npm test-watch` To run the Jasmine tests with change detection
106 | 


--------------------------------------------------------------------------------
/angular-diff-match-patch.js:
--------------------------------------------------------------------------------
  1 | /*
  2 |  angular-diff-match-patch
  3 |  http://amweiss.github.io/angular-diff-match-patch/
  4 |  @license: MIT
  5 | */
  6 | angular.module('diff-match-patch', [])
  7 | 	.constant('DiffMatchPatch', diff_match_patch)
  8 | 	.factory('DIFF_INSERT', ['DiffMatchPatch', function (DiffMatchPatch) {
  9 | 		return DiffMatchPatch.DIFF_INSERT === undefined ? DIFF_INSERT : DiffMatchPatch.DIFF_INSERT;
 10 | 	}])
 11 | 	.factory('DIFF_DELETE', ['DiffMatchPatch', function (DiffMatchPatch) {
 12 | 		return DiffMatchPatch.DIFF_DELETE === undefined ? DIFF_DELETE : DiffMatchPatch.DIFF_DELETE;
 13 | 	}])
 14 | 	.factory('dmp', ['DiffMatchPatch', 'DIFF_INSERT', 'DIFF_DELETE', function (DiffMatchPatch, DIFF_INSERT, DIFF_DELETE) {
 15 | 		const displayType = {
 16 | 			INSDEL: 0,
 17 | 			LINEDIFF: 1,
 18 | 		};
 19 | 
 20 | 		function diffClass(op) {
 21 | 			switch (op) {
 22 | 				case DIFF_INSERT: {
 23 | 					return 'ins';
 24 | 				}
 25 | 
 26 | 				case DIFF_DELETE: {
 27 | 					return 'del';
 28 | 				}
 29 | 
 30 | 				default: { // case DIFF_EQUAL:
 31 | 					return 'match';
 32 | 				}
 33 | 			}
 34 | 		}
 35 | 
 36 | 		function diffSymbol(op) {
 37 | 			switch (op) {
 38 | 				case DIFF_INSERT: {
 39 | 					return '+';
 40 | 				}
 41 | 
 42 | 				case DIFF_DELETE: {
 43 | 					return '-';
 44 | 				}
 45 | 
 46 | 				default: { // case DIFF_EQUAL:
 47 | 					return ' ';
 48 | 				}
 49 | 			}
 50 | 		}
 51 | 
 52 | 		function diffTag(op) {
 53 | 			switch (op) {
 54 | 				case DIFF_INSERT: {
 55 | 					return 'ins';
 56 | 				}
 57 | 
 58 | 				case DIFF_DELETE: {
 59 | 					return 'del';
 60 | 				}
 61 | 
 62 | 				default: { // case DIFF_EQUAL:
 63 | 					return 'span';
 64 | 				}
 65 | 			}
 66 | 		}
 67 | 
 68 | 		function diffAttributeName(op) {
 69 | 			switch (op) {
 70 | 				case DIFF_INSERT: {
 71 | 					return 'insert';
 72 | 				}
 73 | 
 74 | 				case DIFF_DELETE: {
 75 | 					return 'delete';
 76 | 				}
 77 | 
 78 | 				default: { // case DIFF_EQUAL:
 79 | 					return 'equal';
 80 | 				}
 81 | 			}
 82 | 		}
 83 | 
 84 | 		function getTagAttributes(options, op, attributes) {
 85 | 			const tagOptions = {};
 86 | 			const returnValue = [];
 87 | 			const opName = diffAttributeName(op);
 88 | 
 89 | 			if (angular.isDefined(options) && angular.isDefined(options.attrs)) {
 90 | 				const attributesFromOptions = options.attrs[opName];
 91 | 				if (angular.isDefined(attributesFromOptions)) {
 92 | 					angular.merge(tagOptions, attributesFromOptions);
 93 | 				}
 94 | 			}
 95 | 
 96 | 			if (angular.isDefined(attributes)) {
 97 | 				angular.merge(tagOptions, attributes);
 98 | 			}
 99 | 
100 | 			if (Object.keys(tagOptions).length === 0) {
101 | 				return '';
102 | 			}
103 | 
104 | 			for (const [key, value] of Object.entries(tagOptions)) {
105 | 				returnValue.push(key + '="' + value + '"');
106 | 			}
107 | 
108 | 			return ' ' + returnValue.join(' ');
109 | 		}
110 | 
111 | 		function getHtmlPrefix(op, display, options) {
112 | 			switch (display) {
113 | 				case displayType.LINEDIFF: {
114 | 					return '
' + diffSymbol(op) + ''; 115 | } 116 | 117 | default: { // case displayType.INSDEL: 118 | return '<' + diffTag(op) + getTagAttributes(options, op) + '>'; 119 | } 120 | } 121 | } 122 | 123 | function getHtmlSuffix(op, display) { 124 | switch (display) { 125 | case displayType.LINEDIFF: { 126 | return '
'; 127 | } 128 | 129 | default: { // case displayType.INSDEL: 130 | return ''; 131 | } 132 | } 133 | } 134 | 135 | function createHtmlLines(text, op, options) { 136 | const lines = text.split('\n'); 137 | let y; 138 | for (y = 0; y < lines.length; y++) { 139 | if (lines[y].length === 0) { 140 | continue; 141 | } 142 | 143 | lines[y] = getHtmlPrefix(op, displayType.LINEDIFF, options) + lines[y] + getHtmlSuffix(op, displayType.LINEDIFF); 144 | } 145 | 146 | return lines.join(''); 147 | } 148 | 149 | function createHtmlFromDiffs(diffs, display, options, excludeOp) { 150 | let x; 151 | const html = []; 152 | let y; 153 | let op; 154 | let text; 155 | const diffData = diffs; 156 | const dmp = (display === displayType.LINEDIFF) ? new DiffMatchPatch() : null; 157 | let intraDiffs; 158 | let intraHtml1; 159 | let intraHtml2; 160 | 161 | for (x = 0; x < diffData.length; x++) { 162 | diffData[x][1] = diffData[x][1].replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); 163 | } 164 | 165 | for (y = 0; y < diffData.length; y++) { 166 | op = diffData[y][0]; 167 | text = diffData[y][1]; 168 | if (display === displayType.LINEDIFF) { 169 | if (angular.isDefined(options) && angular.isDefined(options.interLineDiff) && options.interLineDiff && diffs[y][0] === DIFF_DELETE && angular.isDefined(diffs[y + 1]) && diffs[y + 1][0] === DIFF_INSERT && !diffs[y][1].includes('\n')) { 170 | intraDiffs = dmp.diff_main(diffs[y][1], diffs[y + 1][1]); 171 | dmp.diff_cleanupSemantic(intraDiffs); 172 | intraHtml1 = createHtmlFromDiffs(intraDiffs, displayType.INSDEL, options, DIFF_INSERT); 173 | intraHtml2 = createHtmlFromDiffs(intraDiffs, displayType.INSDEL, options, DIFF_DELETE); 174 | html[y] = createHtmlLines(intraHtml1, DIFF_DELETE, options); 175 | html[y + 1] = createHtmlLines(intraHtml2, DIFF_INSERT, options); 176 | y++; 177 | } else { 178 | html[y] = createHtmlLines(text, op, options); 179 | } 180 | } else if (excludeOp === undefined || op !== excludeOp) { 181 | html[y] = getHtmlPrefix(op, display, options) + text + getHtmlSuffix(op, display); 182 | } 183 | } 184 | 185 | return html.join(''); 186 | } 187 | 188 | function assertArgumentsIsStrings(left, right) { 189 | return angular.isString(left) && angular.isString(right); 190 | } 191 | 192 | // Taken from source https://code.google.com/p/google-diff-match-patch/ 193 | // and then modified for style and to strip newline 194 | function linesToChars(text1, text2, ignoreTrailingNewLines) { 195 | const lineArray = []; 196 | const lineHash = {}; 197 | lineArray[0] = ''; 198 | 199 | function linesToCharsMunge(text) { 200 | let chars = ''; 201 | let lineStart = 0; 202 | let lineEnd = -1; 203 | let lineArrayLength = lineArray.length; 204 | let hasNewLine = false; 205 | while (lineEnd < text.length - 1) { 206 | lineEnd = text.indexOf('\n', lineStart); 207 | hasNewLine = (lineEnd !== -1); 208 | if (!hasNewLine) { 209 | lineEnd = text.length - 1; 210 | } 211 | 212 | const line = text.slice(lineStart, lineEnd + ((ignoreTrailingNewLines && hasNewLine) ? 0 : 1)); 213 | lineStart = lineEnd + 1; 214 | 215 | if (Object.hasOwn(lineHash, line)) { 216 | chars += String.fromCharCode(lineHash[line]); // eslint-disable-line unicorn/prefer-code-point 217 | } else { 218 | chars += String.fromCharCode(lineArrayLength); // eslint-disable-line unicorn/prefer-code-point 219 | lineHash[line] = lineArrayLength; 220 | lineArray[lineArrayLength++] = line; 221 | } 222 | } 223 | 224 | return chars; 225 | } 226 | 227 | const chars1 = linesToCharsMunge(text1); 228 | const chars2 = linesToCharsMunge(text2); 229 | return {chars1, chars2, lineArray}; 230 | } 231 | 232 | // Taken from source https://code.google.com/p/google-diff-match-patch/ 233 | // and then modified for style and to strip newline 234 | function charsToLines(diffs, lineArray, ignoreTrailingNewLines) { 235 | for (let i = 0; i < diffs.length; i++) { // eslint-disable-line unicorn/no-for-loop 236 | const chars = diffs[i][1]; 237 | const text = []; 238 | for (let y = 0; y < chars.length; y++) { 239 | text[y] = lineArray[chars.charCodeAt(y)]; // eslint-disable-line unicorn/prefer-code-point 240 | } 241 | 242 | diffs[i][1] = text.join((ignoreTrailingNewLines) ? '\n' : ''); 243 | } 244 | } 245 | 246 | return { 247 | createDiffHtml(left, right, options) { 248 | let diffs; 249 | if (assertArgumentsIsStrings(left, right)) { 250 | diffs = new DiffMatchPatch().diff_main(left, right); 251 | return createHtmlFromDiffs(diffs, displayType.INSDEL, options); 252 | } 253 | 254 | return ''; 255 | }, 256 | 257 | createProcessingDiffHtml(left, right, options) { 258 | let dmp; 259 | let diffs; 260 | if (assertArgumentsIsStrings(left, right)) { 261 | dmp = new DiffMatchPatch(); 262 | diffs = dmp.diff_main(left, right); 263 | 264 | if (angular.isDefined(options) && angular.isDefined(options.editCost) && Number.isFinite(options.editCost)) { 265 | dmp.Diff_EditCost = options.editCost; // eslint-disable-line camelcase 266 | } 267 | 268 | dmp.diff_cleanupEfficiency(diffs); 269 | return createHtmlFromDiffs(diffs, displayType.INSDEL, options); 270 | } 271 | 272 | return ''; 273 | }, 274 | 275 | createSemanticDiffHtml(left, right, options) { 276 | let dmp; 277 | let diffs; 278 | if (assertArgumentsIsStrings(left, right)) { 279 | dmp = new DiffMatchPatch(); 280 | diffs = dmp.diff_main(left, right); 281 | dmp.diff_cleanupSemantic(diffs); 282 | return createHtmlFromDiffs(diffs, displayType.INSDEL, options); 283 | } 284 | 285 | return ''; 286 | }, 287 | 288 | createLineDiffHtml(left, right, options) { 289 | let dmp; 290 | let chars; 291 | let diffs; 292 | if (assertArgumentsIsStrings(left, right)) { 293 | dmp = new DiffMatchPatch(); 294 | const ignoreTrailingNewLines = angular.isDefined(options) && angular.isDefined(options.ignoreTrailingNewLines) && options.ignoreTrailingNewLines; 295 | chars = linesToChars(left, right, ignoreTrailingNewLines); 296 | diffs = dmp.diff_main(chars.chars1, chars.chars2, false); 297 | charsToLines(diffs, chars.lineArray, ignoreTrailingNewLines); 298 | return createHtmlFromDiffs(diffs, displayType.LINEDIFF, options); 299 | } 300 | 301 | return ''; 302 | }, 303 | }; 304 | }]) 305 | .directive('diff', ['$compile', 'dmp', function ($compile, dmp) { 306 | const ddo = { 307 | scope: { 308 | left: '=leftObj', 309 | right: '=rightObj', 310 | options: '=options', 311 | }, 312 | link(scope, iElement) { 313 | const listener = function () { 314 | iElement.html(dmp.createDiffHtml(scope.left, scope.right, scope.options)); 315 | // If no options given, or, we have been given options and don't want to skip angular compiling 316 | // Then compile angular in the diff. 317 | if (!scope.options || (scope.options && !scope.options.skipAngularCompilingOnDiff)) { 318 | $compile(iElement.contents())(scope); 319 | } 320 | }; 321 | 322 | scope.$watch('left', listener); 323 | scope.$watch('right', listener); 324 | }, 325 | }; 326 | return ddo; 327 | }]) 328 | .directive('processingDiff', ['$compile', 'dmp', function ($compile, dmp) { 329 | const ddo = { 330 | scope: { 331 | left: '=leftObj', 332 | right: '=rightObj', 333 | options: '=options', 334 | }, 335 | link(scope, iElement) { 336 | const listener = function () { 337 | iElement.html(dmp.createProcessingDiffHtml(scope.left, scope.right, scope.options)); 338 | // If no options given, or, we have been given options and don't want to skip angular compiling 339 | // Then compile angular in the diff. 340 | if (!scope.options || (scope.options && !scope.options.skipAngularCompilingOnDiff)) { 341 | $compile(iElement.contents())(scope); 342 | } 343 | }; 344 | 345 | scope.$watch('left', listener); 346 | scope.$watch('right', listener); 347 | scope.$watch('options.editCost', listener, true); 348 | }, 349 | }; 350 | return ddo; 351 | }]) 352 | .directive('semanticDiff', ['$compile', 'dmp', function ($compile, dmp) { 353 | const ddo = { 354 | scope: { 355 | left: '=leftObj', 356 | right: '=rightObj', 357 | options: '=options', 358 | }, 359 | link(scope, iElement) { 360 | const listener = function () { 361 | iElement.html(dmp.createSemanticDiffHtml(scope.left, scope.right, scope.options)); 362 | // If no options given, or, we have been given options and don't want to skip angular compiling 363 | // Then compile angular in the diff. 364 | if (!scope.options || (scope.options && !scope.options.skipAngularCompilingOnDiff)) { 365 | $compile(iElement.contents())(scope); 366 | } 367 | }; 368 | 369 | scope.$watch('left', listener); 370 | scope.$watch('right', listener); 371 | }, 372 | }; 373 | return ddo; 374 | }]) 375 | .directive('lineDiff', ['$compile', 'dmp', function ($compile, dmp) { 376 | const ddo = { 377 | scope: { 378 | left: '=leftObj', 379 | right: '=rightObj', 380 | options: '=options', 381 | }, 382 | link(scope, iElement) { 383 | const listener = function () { 384 | iElement.html(dmp.createLineDiffHtml(scope.left, scope.right, scope.options)); 385 | // If no options given, or, we have been given options and don't want to skip angular compiling 386 | // Then compile angular in the diff. 387 | if (!scope.options || (scope.options && !scope.options.skipAngularCompilingOnDiff)) { 388 | $compile(iElement.contents())(scope); 389 | } 390 | }; 391 | 392 | scope.$watch('left', listener); 393 | scope.$watch('right', listener); 394 | scope.$watch('options.interLineDiff', listener, true); 395 | scope.$watch('options.ignoreTrailingNewLines', listener, true); 396 | }, 397 | }; 398 | return ddo; 399 | }]); 400 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-diff-match-patch", 3 | "authors": [ 4 | "Adam Weiss " 5 | ], 6 | "license": "MIT", 7 | "version": "0.8.13", 8 | "description": "An Angular module to use when dealing with google-diff-match-patch.", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests", 15 | "coverage" 16 | ], 17 | "main": "angular-diff-match-patch.js", 18 | "dependencies": { 19 | "angular": "^1.*", 20 | "google-diff-match-patch": "0.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | angular-diff-match-patch 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 57 | 58 | 59 | 60 | 100 | 101 | 102 |
103 | angular-diff-match-patch 104 | 105 | code 106 | 107 | 108 | edit 109 | 110 |
111 |
112 | 113 | 114 |
115 |
116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 |
128 | 129 | 130 |

diff

131 |

132 |           
133 |
134 | 135 | 136 |
137 |

processingDiff

138 | 139 | 140 | 142 | 143 |
144 |

145 |           
146 |
147 |
148 |
149 | 150 | 151 |

semanticDiff (with custom attributes)

152 |

153 |           
154 |
155 | 156 | 157 |
158 |

lineDiff

159 | 160 | 161 | interLineDiff 162 | 163 | 164 | ignoreTrailingNewLines 165 | 166 |
167 |

168 |           
169 |
170 |
171 |
172 |
173 | 174 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./angular-diff-match-patch'); 2 | 3 | module.exports = 'diff-match-patch'; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-diff-match-patch", 3 | "author": "Adam Weiss ", 4 | "license": "MIT", 5 | "version": "0.8.13", 6 | "main": "index.js", 7 | "description": "An Angular module to use when dealing with google-diff-match-patch.", 8 | "repository": { 9 | "url": "https://github.com/amweiss/angular-diff-match-patch.git" 10 | }, 11 | "dependencies": { 12 | "angular": "^1.8.3", 13 | "diff-match-patch": "^1.0.5" 14 | }, 15 | "files": [ 16 | "index.js", 17 | "angular-diff-match-patch.js" 18 | ], 19 | "scripts": { 20 | "start": "browser-sync start -s", 21 | "pretest": "yarn run lint", 22 | "test": "karma start test/karma.conf.js --single-run", 23 | "lint": "xo ./angular-diff-match-patch.js ./test/diffmatchpatch-spec.js", 24 | "test-watch": "karma start test/karma-watch.conf.js", 25 | "release": "standard-version" 26 | }, 27 | "devDependencies": { 28 | "angular-mocks": "1.8.3", 29 | "browser-sync": "3.0.4", 30 | "eslint-plugin-ie11": "1.0.0", 31 | "jasmine-core": "5.7.1", 32 | "karma": "6.4.4", 33 | "karma-chrome-launcher": "3.2.0", 34 | "karma-coverage": "2.2.1", 35 | "karma-jasmine": "5.1.0", 36 | "karma-junit-reporter": "2.0.1", 37 | "puppeteer": "24.10.0", 38 | "standard-version": "9.5.0", 39 | "xo": "1.0.5" 40 | }, 41 | "xo": { 42 | "plugins": [ 43 | "ie11" 44 | ], 45 | "rules": { 46 | "capitalized-comments": [ 47 | "off" 48 | ], 49 | "ie11/no-collection-args": [ 50 | "error" 51 | ], 52 | "ie11/no-for-in-const": [ 53 | "error" 54 | ], 55 | "ie11/no-loop-func": [ 56 | "warn" 57 | ], 58 | "ie11/no-weak-collections": [ 59 | "error" 60 | ] 61 | }, 62 | "globals": [ 63 | "angular", 64 | "DIFF_INSERT", 65 | "DIFF_DELETE", 66 | "DIFF_EQUAL", 67 | "diff_match_patch" 68 | ] 69 | }, 70 | "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728" 71 | } 72 | -------------------------------------------------------------------------------- /test/diffmatchpatch-spec.js: -------------------------------------------------------------------------------- 1 | /* global inject */ 2 | /* eslint-env jasmine */ 3 | describe('diff-match-patch', () => { 4 | const oneLineBasicLeft = 'hello world'; 5 | const oneLineBasicRight = 'hello'; 6 | const multiLineLeft = ['I know the kings of England, and I quote the fights historical,', 7 | 'From Marathon to Waterloo, in order categorical.'].join('\n'); 8 | const multiLineRight = ['I’m quite adept at funny gags, comedic theory I have read', 9 | 'From wicked puns and stupid jokes to anvils that drop on your head.'].join('\n'); 10 | const diffRegex = /hello<\/span> world<\/del>/; 11 | const oneLineAngularLeft = '{{1 + 2}} hello world'; 12 | const oneLineAngularRight = '{{1 + 2}} hello'; 13 | const angularProcessedDiffRegex = /3 hello<\/span> world<\/del>/; 14 | 15 | beforeEach(angular.mock.module('diff-match-patch')); 16 | 17 | describe('DIFF_INSERT and DIFF_DELETE factories', () => { 18 | let DIFF_INSERT; 19 | let DIFF_DELETE; 20 | 21 | beforeEach(inject((_DIFF_INSERT_, _DIFF_DELETE_) => { 22 | DIFF_INSERT = _DIFF_INSERT_; 23 | DIFF_DELETE = _DIFF_DELETE_; 24 | })); 25 | 26 | it('are defined', () => { 27 | expect(DIFF_INSERT).toBeDefined(); 28 | expect(DIFF_DELETE).toBeDefined(); 29 | }); 30 | }); 31 | 32 | describe('directive', () => { 33 | let $scope; 34 | let $compile; 35 | 36 | beforeEach(inject((_$rootScope_, _$compile_) => { 37 | $scope = _$rootScope_.$new(); 38 | $compile = _$compile_; 39 | })); 40 | 41 | describe('diff', () => { 42 | const diffHtmlNoOptions = '
'; 43 | const diffHtmlWithOptions = '
'; 44 | 45 | it('compile angular tokens in the diff', () => { 46 | const element = $compile(diffHtmlNoOptions)($scope); 47 | $scope.left = ''; 48 | $scope.right = oneLineAngularRight; 49 | $scope.$digest(); 50 | expect(element.html()).toMatch(/3 hello<\/ins>/); 51 | }); 52 | 53 | it('compile angular tokens in the total diff', () => { 54 | const element = $compile(diffHtmlNoOptions)($scope); 55 | $scope.left = oneLineAngularLeft; 56 | $scope.right = oneLineAngularRight; 57 | $scope.$digest(); 58 | expect(element.html()).toMatch(angularProcessedDiffRegex); 59 | }); 60 | 61 | it('compile angular tokens in the diff if no flag has given in options', () => { 62 | const element = $compile(diffHtmlWithOptions)($scope); 63 | $scope.left = ''; 64 | $scope.right = oneLineAngularRight; 65 | $scope.options = {}; 66 | $scope.$digest(); 67 | expect(element.html()).toMatch(/3 hello<\/ins>/); 68 | }); 69 | 70 | it('should not compile angular tokens in the diff if the flag has been set', () => { 71 | const element = $compile(diffHtmlWithOptions)($scope); 72 | $scope.left = ''; 73 | $scope.right = oneLineAngularRight; 74 | $scope.options = { 75 | skipAngularCompilingOnDiff: true, 76 | }; 77 | $scope.$digest(); 78 | expect(element.html()).toMatch(/{{1 \+ 2}} hello<\/ins>/); 79 | }); 80 | 81 | it('no sides returns empty string', () => { 82 | const element = $compile(diffHtmlNoOptions)($scope); 83 | $scope.$digest(); 84 | expect(element.html()).toBe(''); 85 | }); 86 | 87 | it('one side returns empty string', () => { 88 | const element = $compile(diffHtmlNoOptions)($scope); 89 | $scope.left = oneLineBasicLeft; 90 | $scope.$digest(); 91 | expect(element.html()).toBe(''); 92 | }); 93 | 94 | it('left side is empty string', () => { 95 | const element = $compile(diffHtmlNoOptions)($scope); 96 | $scope.left = ''; 97 | $scope.right = oneLineBasicLeft; 98 | $scope.$digest(); 99 | expect(element.html()).toMatch(/hello world<\/ins>/); 100 | }); 101 | 102 | it('single lines return total diff', () => { 103 | const element = $compile(diffHtmlNoOptions)($scope); 104 | $scope.left = oneLineBasicLeft; 105 | $scope.right = oneLineBasicRight; 106 | $scope.$digest(); 107 | expect(element.html()).toMatch(diffRegex); 108 | }); 109 | 110 | it('two lines returns diff HTML', () => { 111 | const element = $compile(diffHtmlNoOptions)($scope); 112 | const regex = /hello[\s\S]*?<\/span>wo<\/del>f<\/ins>r<\/span>l<\/del>ien<\/ins>d<\/span>s!<\/ins>/; 113 | $scope.left = ['hello', 'world'].join('\n'); 114 | $scope.right = ['hello', 'friends!'].join('\n'); 115 | $scope.$digest(); 116 | expect(element.html()).toMatch(regex); 117 | }); 118 | 119 | it('two lines with options returns diff HTML', () => { 120 | const element = $compile(diffHtmlWithOptions)($scope); 121 | const regex = /hello[\s\S]*?<\/span>wo<\/del>f<\/ins>r<\/span>l<\/del>ien<\/ins>d<\/span>s!<\/ins>/; 122 | $scope.left = ['hello', 'world'].join('\n'); 123 | $scope.right = ['hello', 'friends!'].join('\n'); 124 | $scope.options = { 125 | attrs: { 126 | insert: { 127 | 'data-attr': 'insert', 128 | }, 129 | delete: { 130 | 'data-attr': 'delete', 131 | }, 132 | equal: { 133 | 'data-attr': 'equal', 134 | }, 135 | }, 136 | }; 137 | 138 | $scope.$digest(); 139 | expect(element.html()).toMatch(regex); 140 | }); 141 | }); 142 | 143 | describe('processingDiff', () => { 144 | const processingDiffHtml = '
'; 145 | const processingDiffOptionsHtml = '
'; 146 | const twoLineRegex = /I<\/span> know the kings of England, and I quote<\/del>’m quite adept at funny gags, comedic<\/ins> the<\/span> fights historical,<\/del>ory I have read<\/ins>[\s\S]*?From <\/span>Marathon<\/del>wicked puns and stupid jokes<\/ins> to <\/span>Waterloo, in order categorical<\/del>anvils that drop on your head<\/ins>.<\/span>/; 147 | 148 | it('compile angular tokens in the diff', () => { 149 | const element = $compile(processingDiffHtml)($scope); 150 | $scope.left = ''; 151 | $scope.right = oneLineAngularRight; 152 | $scope.$digest(); 153 | expect(element.html()).toMatch(/3 hello<\/ins>/); 154 | }); 155 | 156 | it('compile angular tokens in the total diff', () => { 157 | const element = $compile(processingDiffHtml)($scope); 158 | $scope.left = oneLineAngularLeft; 159 | $scope.right = oneLineAngularRight; 160 | $scope.$digest(); 161 | expect(element.html()).toMatch(angularProcessedDiffRegex); 162 | }); 163 | 164 | it('compile angular tokens in the diff if no flag has given in options', () => { 165 | const element = $compile(processingDiffOptionsHtml)($scope); 166 | $scope.left = ''; 167 | $scope.right = oneLineAngularRight; 168 | $scope.options = {}; 169 | $scope.$digest(); 170 | expect(element.html()).toMatch(/3 hello<\/ins>/); 171 | }); 172 | 173 | it('should not compile angular tokens in the diff if the flag has been set', () => { 174 | const element = $compile(processingDiffOptionsHtml)($scope); 175 | $scope.left = ''; 176 | $scope.right = oneLineAngularRight; 177 | $scope.options = { 178 | skipAngularCompilingOnDiff: true, 179 | }; 180 | $scope.$digest(); 181 | expect(element.html()).toMatch(/{{1 \+ 2}} hello<\/ins>/); 182 | }); 183 | 184 | it('no sides returns empty string', () => { 185 | const element = $compile(processingDiffHtml)($scope); 186 | $scope.$digest(); 187 | expect(element.html()).toBe(''); 188 | }); 189 | 190 | it('one side returns empty string', () => { 191 | const element = $compile(processingDiffHtml)($scope); 192 | $scope.left = oneLineBasicLeft; 193 | $scope.$digest(); 194 | expect(element.html()).toBe(''); 195 | }); 196 | 197 | it('single lines return total diff', () => { 198 | const element = $compile(processingDiffHtml)($scope); 199 | $scope.left = oneLineBasicLeft; 200 | $scope.right = oneLineBasicRight; 201 | $scope.$digest(); 202 | expect(element.html()).toMatch(diffRegex); 203 | }); 204 | 205 | it('two lines returns diff HTML', () => { 206 | const element = $compile(processingDiffHtml)($scope); 207 | $scope.left = multiLineLeft; 208 | $scope.right = multiLineRight; 209 | $scope.$digest(); 210 | expect(element.html()).toMatch(twoLineRegex); 211 | }); 212 | 213 | it('two lines with editCost option returns diff HTML', () => { 214 | const element = $compile(processingDiffOptionsHtml)($scope); 215 | const regex = /I<\/span> know the kings of England, and I quote the fights historical,<\/del>’m quite adept at funny gags, comedic theory I have read<\/ins>[\s\S]*?From <\/span>Marathon to Waterloo, in order categorical<\/del>wicked puns and stupid jokes to anvils that drop on your head<\/ins>.<\/span>/; 216 | $scope.left = multiLineLeft; 217 | $scope.right = multiLineRight; 218 | $scope.options = { 219 | editCost: 5, 220 | }; 221 | 222 | $scope.$digest(); 223 | expect(element.html()).toMatch(regex); 224 | expect(element.html()).not.toMatch(twoLineRegex); 225 | }); 226 | }); 227 | 228 | describe('semanticDiff', () => { 229 | const semanticDiffHtml = '
'; 230 | const semanticDiffHtmlWithOptions = '
'; 231 | 232 | it('compile angular tokens in the diff', () => { 233 | const element = $compile(semanticDiffHtml)($scope); 234 | $scope.left = ''; 235 | $scope.right = oneLineAngularRight; 236 | $scope.$digest(); 237 | expect(element.html()).toMatch(/3 hello<\/ins>/); 238 | }); 239 | 240 | it('compile angular tokens in the total diff', () => { 241 | const element = $compile(semanticDiffHtml)($scope); 242 | $scope.left = oneLineAngularLeft; 243 | $scope.right = oneLineAngularRight; 244 | $scope.$digest(); 245 | expect(element.html()).toMatch(angularProcessedDiffRegex); 246 | }); 247 | 248 | it('compile angular tokens in the diff if no flag has given in options', () => { 249 | const element = $compile(semanticDiffHtmlWithOptions)($scope); 250 | $scope.left = ''; 251 | $scope.right = oneLineAngularRight; 252 | $scope.options = {}; 253 | $scope.$digest(); 254 | expect(element.html()).toMatch(/3 hello<\/ins>/); 255 | }); 256 | 257 | it('should not compile angular tokens in the diff if the flag has been set', () => { 258 | const element = $compile(semanticDiffHtmlWithOptions)($scope); 259 | $scope.left = ''; 260 | $scope.right = oneLineAngularRight; 261 | $scope.options = { 262 | skipAngularCompilingOnDiff: true, 263 | }; 264 | $scope.$digest(); 265 | expect(element.html()).toMatch(/{{1 \+ 2}} hello<\/ins>/); 266 | }); 267 | 268 | it('no sides returns empty string', () => { 269 | const element = $compile(semanticDiffHtml)($scope); 270 | $scope.$digest(); 271 | expect(element.html()).toBe(''); 272 | }); 273 | 274 | it('one side returns empty string', () => { 275 | const element = $compile(semanticDiffHtml)($scope); 276 | $scope.left = oneLineBasicLeft; 277 | $scope.$digest(); 278 | expect(element.html()).toBe(''); 279 | }); 280 | 281 | it('single lines return total diff', () => { 282 | const element = $compile(semanticDiffHtml)($scope); 283 | $scope.left = oneLineBasicLeft; 284 | $scope.right = oneLineBasicRight; 285 | $scope.$digest(); 286 | expect(element.html()).toMatch(diffRegex); 287 | }); 288 | 289 | it('two lines returns diff HTML', () => { 290 | const element = $compile(semanticDiffHtml)($scope); 291 | const regex = /I<\/span> know the kings of England, and I quote the fights historical,[\s\S]*?From Marathon to Waterloo, in order categorical<\/del>’m quite adept at funny gags, comedic theory I have read[\s\S]*?From wicked puns and stupid jokes to anvils that drop on your head<\/ins>.<\/span>/; 292 | $scope.left = multiLineLeft; 293 | $scope.right = multiLineRight; 294 | $scope.$digest(); 295 | expect(element.html()).toMatch(regex); 296 | }); 297 | }); 298 | 299 | describe('lineDiff', () => { 300 | const lineDiffHtml = '
'; 301 | const lineDiffOptionHtml = '
'; 302 | const twoLineRegex = /
<\/span>hello<\/div>
-<\/span>world<\/div>
\+<\/span>friends!<\/div>/; 303 | 304 | it('compile angular tokens in the diff', () => { 305 | const element = $compile(lineDiffHtml)($scope); 306 | $scope.left = ''; 307 | $scope.right = oneLineAngularRight; 308 | $scope.$digest(); 309 | expect(element.html()).toMatch(/.*?ins.*?<\/span>3 hello<\/div>/); 310 | }); 311 | 312 | it('compile angular tokens in the total diff', () => { 313 | const element = $compile(lineDiffHtml)($scope); 314 | $scope.left = oneLineAngularLeft; 315 | $scope.right = oneLineAngularRight; 316 | $scope.$digest(); 317 | expect(element.html()).toMatch(/.*?-<\/span>3 hello world.*?\+<\/span>3 hello.*?/); 318 | }); 319 | 320 | it('compile angular tokens in the diff if no flag has given in options', () => { 321 | const element = $compile(lineDiffOptionHtml)($scope); 322 | $scope.left = oneLineAngularLeft; 323 | $scope.right = oneLineAngularRight; 324 | $scope.options = {}; 325 | $scope.$digest(); 326 | expect(element.html()).toMatch(/.*?-<\/span>3 hello world.*?\+<\/span>3 hello.*?/); 327 | }); 328 | 329 | it('should not compile angular tokens in the diff if the flag has been set', () => { 330 | const element = $compile(lineDiffOptionHtml)($scope); 331 | $scope.left = oneLineAngularLeft; 332 | $scope.right = oneLineAngularRight; 333 | $scope.options = { 334 | skipAngularCompilingOnDiff: true, 335 | }; 336 | $scope.$digest(); 337 | expect(element.html()).toMatch(/.*?-<\/span>{{1 \+ 2}} hello world.*?\+<\/span>{{1 \+ 2}} hello.*?/); 338 | }); 339 | 340 | it('no sides returns empty string', () => { 341 | const element = $compile(lineDiffHtml)($scope); 342 | $scope.$digest(); 343 | expect(element.html()).toBe(''); 344 | }); 345 | 346 | it('one side returns empty string', () => { 347 | const element = $compile(lineDiffHtml)($scope); 348 | $scope.left = oneLineBasicLeft; 349 | $scope.$digest(); 350 | expect(element.html()).toBe(''); 351 | }); 352 | 353 | it('single lines return total diff', () => { 354 | const element = $compile(lineDiffHtml)($scope); 355 | const regex = /
.*?hello world<\/div>
.*?hello<\/div>/; 356 | $scope.left = oneLineBasicLeft; 357 | $scope.right = oneLineBasicRight; 358 | $scope.$digest(); 359 | expect(element.html()).toMatch(regex); 360 | }); 361 | 362 | it('single lines ignoreTrailingNewLines', () => { 363 | const element = $compile(lineDiffOptionHtml)($scope); 364 | const regex = /
.*?hello<\/div>
.*?world<\/div>/; 365 | $scope.right = oneLineBasicLeft.split(' ').join('\n'); 366 | $scope.left = oneLineBasicRight; 367 | $scope.options = { 368 | ignoreTrailingNewLines: true, 369 | }; 370 | $scope.$digest(); 371 | expect(element.html()).toMatch(regex); 372 | }); 373 | 374 | it('two lines returns diff HTML', () => { 375 | const element = $compile(lineDiffHtml)($scope); 376 | $scope.left = ['hello', 'world'].join('\n'); 377 | $scope.right = ['hello', 'friends!'].join('\n'); 378 | $scope.$digest(); 379 | expect(element.html()).toMatch(twoLineRegex); 380 | }); 381 | 382 | it('two lines empty options returns diff HTML', () => { 383 | const element = $compile(lineDiffOptionHtml)($scope); 384 | $scope.left = ['hello', 'world'].join('\n'); 385 | $scope.right = ['hello', 'friends!'].join('\n'); 386 | $scope.options = {}; 387 | $scope.$digest(); 388 | expect(element.html()).toMatch(twoLineRegex); 389 | }); 390 | 391 | it('two lines empty options attrs returns diff HTML', () => { 392 | const element = $compile(lineDiffOptionHtml)($scope); 393 | $scope.left = ['hello', 'world'].join('\n'); 394 | $scope.right = ['hello', 'friends!'].join('\n'); 395 | $scope.options = {attrs: {}}; 396 | $scope.$digest(); 397 | expect(element.html()).toMatch(twoLineRegex); 398 | }); 399 | 400 | it('two lines with options returns diff HTML', () => { 401 | const element = $compile(lineDiffOptionHtml)($scope); 402 | const regex = /
<\/span>hello<\/div>
-<\/span>world<\/div>
\+<\/span>friends!<\/div>/; 403 | $scope.left = ['hello', 'world'].join('\n'); 404 | $scope.right = ['hello', 'friends!'].join('\n'); 405 | $scope.options = { 406 | attrs: { 407 | insert: { 408 | 'data-attr': 'insert', 409 | class: 'insertion', 410 | }, 411 | delete: { 412 | 'data-attr': 'delete', 413 | }, 414 | equal: { 415 | 'data-attr': 'equal', 416 | }, 417 | }, 418 | }; 419 | 420 | $scope.$digest(); 421 | expect(element.html()).toMatch(regex); 422 | }); 423 | 424 | it('two lines interLineDiff options returns diff HTML', () => { 425 | const element = $compile(lineDiffOptionHtml)($scope); 426 | const regex = /
<\/span>hello<\/div>
-<\/span>world<\/del><\/div>
\+<\/span>friends!<\/ins><\/div>/; 427 | $scope.left = ['hello', 'world'].join('\n'); 428 | $scope.right = ['hello', 'friends!'].join('\n'); 429 | $scope.options = { 430 | interLineDiff: true, 431 | }; 432 | $scope.$digest(); 433 | expect(element.html()).toMatch(regex); 434 | }); 435 | }); 436 | }); 437 | }); 438 | -------------------------------------------------------------------------------- /test/karma-watch.conf.js: -------------------------------------------------------------------------------- 1 | var junitReporterConfig = { 2 | outputDir: 'test/results' 3 | }; 4 | 5 | var reporters = [ 6 | 'dots', 7 | 'junit' 8 | ]; 9 | 10 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 11 | 12 | module.exports = function (config) { 13 | config.set({ 14 | 15 | // base path that will be used to resolve all patterns (eg. files, exclude) 16 | basePath: '..', 17 | 18 | // frameworks to use 19 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 20 | frameworks: ['jasmine'], 21 | 22 | // list of files / patterns to load in the browser 23 | files: [ 24 | 'node_modules/angular/angular.js', 25 | 'node_modules/angular-mocks/angular-mocks.js', 26 | 'node_modules/diff-match-patch/index.js', 27 | 'test/*.js', 28 | 'angular-diff-match-patch.js' 29 | ], 30 | 31 | // list of files to exclude 32 | exclude: [ 33 | 'test/karma.conf.js', 34 | 'test/karma-watch.conf.js' 35 | ], 36 | 37 | // preprocess matching files before serving them to the browser 38 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 39 | preprocessors: { 40 | }, 41 | 42 | // test results reporter to use 43 | // possible values: 'dots', 'progress' 44 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 45 | reporters: reporters, 46 | 47 | // web server port 48 | port: 9876, 49 | 50 | // enable / disable colors in the output (reporters and logs) 51 | colors: true, 52 | 53 | // level of logging 54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | // enable / disable watching file and executing tests whenever any file changes 58 | autoWatch: true, 59 | 60 | // start these browsers 61 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 62 | browsers: ['ChromeHeadless'], 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false, 67 | 68 | // Concurrency level 69 | // how many browser should be started simultaneous 70 | concurrency: Infinity, 71 | 72 | junitReporter: junitReporterConfig 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | var coverageReporters = [ 2 | {type: 'text-summary'} 3 | ]; 4 | 5 | var junitReporterConfig = { 6 | outputDir: 'test/results/junit' 7 | }; 8 | 9 | var reporters = [ 10 | 'dots', 11 | 'coverage', 12 | 'junit' 13 | ]; 14 | 15 | if (process.env.CIRCLECI) { 16 | console.log('On CI, generating lcov'); 17 | coverageReporters.push({type: 'lcov', dir: 'coverage/'}); 18 | } else { 19 | console.log('Not on CI, generating html'); 20 | coverageReporters.push({type: 'html', dir: 'coverage/'}); 21 | } 22 | 23 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 24 | 25 | module.exports = function (config) { 26 | config.set({ 27 | 28 | // base path that will be used to resolve all patterns (eg. files, exclude) 29 | basePath: '..', 30 | 31 | // frameworks to use 32 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 33 | frameworks: ['jasmine'], 34 | 35 | // list of files / patterns to load in the browser 36 | files: [ 37 | 'node_modules/angular/angular.js', 38 | 'node_modules/angular-mocks/angular-mocks.js', 39 | 'node_modules/diff-match-patch/index.js', 40 | 'test/*.js', 41 | 'angular-diff-match-patch.js' 42 | ], 43 | 44 | // list of files to exclude 45 | exclude: [ 46 | 'test/karma.conf.js', 47 | 'test/karma-watch.conf.js' 48 | ], 49 | 50 | // preprocess matching files before serving them to the browser 51 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 52 | preprocessors: { 53 | 'angular-diff-match-patch.js': ['coverage'] 54 | }, 55 | 56 | // test results reporter to use 57 | // possible values: 'dots', 'progress' 58 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 59 | reporters: reporters, 60 | 61 | // web server port 62 | port: 9876, 63 | 64 | // enable / disable colors in the output (reporters and logs) 65 | colors: true, 66 | 67 | // level of logging 68 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 69 | logLevel: config.LOG_INFO, 70 | 71 | // enable / disable watching file and executing tests whenever any file changes 72 | autoWatch: true, 73 | 74 | // start these browsers 75 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 76 | browsers: ['ChromeHeadless'], 77 | 78 | // Continuous Integration mode 79 | // if true, Karma captures browsers, runs the tests and exits 80 | singleRun: false, 81 | 82 | // Concurrency level 83 | // how many browser should be started simultaneous 84 | concurrency: Infinity, 85 | 86 | coverageReporter: { 87 | reporters: coverageReporters, 88 | instrumenterOptions: { 89 | istanbul: {noCompact: true} 90 | } 91 | }, 92 | 93 | junitReporter: junitReporterConfig 94 | }); 95 | }; 96 | --------------------------------------------------------------------------------