diff --git a/src/diff/glyph.js b/src/diff/glyph.js
new file mode 100644
index 00000000..a8306109
--- /dev/null
+++ b/src/diff/glyph.js
@@ -0,0 +1,15 @@
+import Diff from './base';
+
+export const glyphDiff = new Diff();
+glyphDiff.tokenize = function(value) {
+ return [...value];
+};
+glyphDiff.join = function(value) {
+ return value.join('');
+};
+glyphDiff.removeEmpty = function(value) {
+ return value.filter((str) => str !== '');
+};
+export function diffGlyph(oldStr, newStr, options) {
+ return glyphDiff.diff(oldStr, newStr, options);
+}
diff --git a/test/diff/glyph.js b/test/diff/glyph.js
new file mode 100644
index 00000000..5e8ee968
--- /dev/null
+++ b/test/diff/glyph.js
@@ -0,0 +1,23 @@
+import {diffGlyph} from '../../lib/diff/glyph';
+import {convertChangesToXML} from '../../lib/convert/xml';
+
+import {expect} from 'chai';
+
+describe('diff/glyph', function() {
+ describe('#diffGlyph', function() {
+ describe('extended unicode characters', function() {
+ it('are treated as single characters when inserted', function() {
+ const diffResult = diffGlyph('New 🐴.', 'New 🐴🐴MoreData.', {removeEmpty: true});
+ expect(convertChangesToXML(diffResult)).to.equal('New 🐴🐴MoreData.');
+ });
+ it('are treated as single characters when deleted', function() {
+ const diffResult = diffGlyph('New 🐴🐴.', 'New 🐴.', {removeEmpty: true});
+ expect(convertChangesToXML(diffResult)).to.equal('New 🐴🐴.');
+ });
+ it('are treated as single characters when equal', function() {
+ const diffResult = diffGlyph('New 🐴.', 'New 🐴.', {removeEmpty: true});
+ expect(convertChangesToXML(diffResult)).to.equal('New 🐴.');
+ });
+ });
+ });
+});