+
+
+
+ }
+ resultComponent={}
+ initialValues={{}}
+ getGroups={null}
+ setInput={() => {
+ setInputA('');
+ setInputB('');
+ setResult('');
+ }}
+ compute={compute}
+ toolInfo={{
+ title: t('textCompare.toolInfo.title'),
+ description: t('textCompare.toolInfo.description')
+ }}
+ />
+ );
+}
diff --git a/src/pages/tools/string/text-compare/meta.ts b/src/pages/tools/string/text-compare/meta.ts
new file mode 100644
index 0000000..8329b2c
--- /dev/null
+++ b/src/pages/tools/string/text-compare/meta.ts
@@ -0,0 +1,15 @@
+import { defineTool } from '@tools/defineTool';
+import { lazy } from 'react';
+
+export const tool = defineTool('string', {
+ i18n: {
+ name: 'string:textCompare.title',
+ description: 'string:textCompare.description',
+ shortDescription: 'string:textCompare.shortDescription',
+ longDescription: 'string:textCompare.longDescription'
+ },
+ path: 'text-compare',
+ icon: 'material-symbols-light:search',
+ keywords: ['text', 'compare'],
+ component: lazy(() => import('./index'))
+});
diff --git a/src/pages/tools/string/text-compare/service.ts b/src/pages/tools/string/text-compare/service.ts
new file mode 100644
index 0000000..2b4141c
--- /dev/null
+++ b/src/pages/tools/string/text-compare/service.ts
@@ -0,0 +1,24 @@
+import { diffWordsWithSpace } from 'diff';
+
+function escapeHtml(str: string): string {
+ return str.replace(//g, '>');
+}
+
+export function compareTextsHtml(textA: string, textB: string): string {
+ const diffs = diffWordsWithSpace(textA, textB);
+
+ const html = diffs
+ .map((part) => {
+ const val = escapeHtml(part.value).replace(/ /g, ' ');
+ if (part.added) {
+ return `${val}`;
+ }
+ if (part.removed) {
+ return `${val}`;
+ }
+ return `${val}`;
+ })
+ .join('');
+
+ return `${html}
`;
+}
diff --git a/src/pages/tools/string/text-compare/text-compare.service.test.ts b/src/pages/tools/string/text-compare/text-compare.service.test.ts
new file mode 100644
index 0000000..c2e3578
--- /dev/null
+++ b/src/pages/tools/string/text-compare/text-compare.service.test.ts
@@ -0,0 +1,72 @@
+import { describe, it, expect } from 'vitest';
+import { compareTextsHtml } from './service';
+
+describe('compareTextsHtml', () => {
+ it('should highlight added text', () => {
+ const textA = 'Bonjour tout le monde';
+ const textB = 'Bonjour tout le monde ici';
+
+ const result = compareTextsHtml(textA, textB);
+ expect(result).toContain(' ici');
+ });
+
+ it('should highlight removed text', () => {
+ const textA = 'Bonjour tout le monde ici';
+ const textB = 'Bonjour tout le monde';
+
+ const result = compareTextsHtml(textA, textB);
+ expect(result).toContain(' ici');
+ });
+
+ it('should highlight changes in the middle of a sentence', () => {
+ const textA = 'Je suis à Lyon';
+ const textB = 'Je suis à Marseille';
+
+ const result = compareTextsHtml(textA, textB);
+ expect(result).toContain('Je suis à ');
+ expect(result).toContain('Lyon');
+ expect(result).toContain('Marseille');
+ });
+
+ it('should return empty diff if texts are identical', () => {
+ const input = 'Même texte partout';
+ const result = compareTextsHtml(input, input);
+ expect(result).toContain('Même texte partout');
+ expect(result).not.toContain('diff-added');
+ expect(result).not.toContain('diff-removed');
+ });
+
+ it('should handle HTML escaping', () => {
+ const textA = 'Hello';
+ const textB = 'Hello world';
+
+ const result = compareTextsHtml(textA, textB);
+ expect(result).toContain('<b>Hello');
+ expect(result).toContain(' world');
+ });
+
+ it('should return a single div with class diff-line', () => {
+ const result = compareTextsHtml('foo', 'bar');
+ expect(result.startsWith('')).toBe(true);
+ expect(result.endsWith('
')).toBe(true);
+ });
+
+ it('should handle empty strings', () => {
+ const result = compareTextsHtml('', '');
+ expect(result).toBe('');
+ });
+
+ it('should handle only added content', () => {
+ const result = compareTextsHtml('', 'Nouveau texte');
+ expect(result).toContain(
+ 'Nouveau texte'
+ );
+ });
+
+ it('should handle only removed content', () => {
+ const result = compareTextsHtml('Ancien texte', '');
+ expect(result).toContain(
+ 'Ancien texte'
+ );
+ });
+});