@daviddh/llm-markdown-whatsapp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +17 -0
- package/CLAUDE.md +155 -0
- package/README.md +304 -0
- package/eslint.config.mjs +28 -0
- package/jest.config.js +40 -0
- package/package.json +61 -0
- package/packages/core/dist/__tests__/splitChatText.basic.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.basic.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.basic.test.js +100 -0
- package/packages/core/dist/__tests__/splitChatText.coverageLists.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.coverageLists.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.coverageLists.test.js +88 -0
- package/packages/core/dist/__tests__/splitChatText.coverageProcessors.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.coverageProcessors.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.coverageProcessors.test.js +108 -0
- package/packages/core/dist/__tests__/splitChatText.coverageQuestions.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.coverageQuestions.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.coverageQuestions.test.js +74 -0
- package/packages/core/dist/__tests__/splitChatText.dataProtection.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.dataProtection.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.dataProtection.test.js +80 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests1.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests1.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests1.test.js +124 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests2.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests2.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.dataTests2.test.js +122 -0
- package/packages/core/dist/__tests__/splitChatText.edgeCases.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.edgeCases.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.edgeCases.test.js +132 -0
- package/packages/core/dist/__tests__/splitChatText.helpers.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.helpers.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.helpers.js +5 -0
- package/packages/core/dist/__tests__/splitChatText.punctuation.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.punctuation.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.punctuation.test.js +98 -0
- package/packages/core/dist/__tests__/splitChatText.realWorld.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.realWorld.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.realWorld.test.js +104 -0
- package/packages/core/dist/__tests__/splitChatText.urlProtection.test.d.ts +2 -0
- package/packages/core/dist/__tests__/splitChatText.urlProtection.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/splitChatText.urlProtection.test.js +82 -0
- package/packages/core/dist/__tests__/strs.splitChatText.test.d.ts +2 -0
- package/packages/core/dist/__tests__/strs.splitChatText.test.d.ts.map +1 -0
- package/packages/core/dist/__tests__/strs.splitChatText.test.js +992 -0
- package/packages/core/dist/chatSplit/breakProcessor.d.ts +4 -0
- package/packages/core/dist/chatSplit/breakProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/breakProcessor.js +67 -0
- package/packages/core/dist/chatSplit/constants.d.ts +35 -0
- package/packages/core/dist/chatSplit/constants.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/constants.js +34 -0
- package/packages/core/dist/chatSplit/index.d.ts +2 -0
- package/packages/core/dist/chatSplit/index.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/index.js +1 -0
- package/packages/core/dist/chatSplit/listNormalization.d.ts +13 -0
- package/packages/core/dist/chatSplit/listNormalization.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/listNormalization.js +140 -0
- package/packages/core/dist/chatSplit/listProcessor.d.ts +6 -0
- package/packages/core/dist/chatSplit/listProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/listProcessor.js +61 -0
- package/packages/core/dist/chatSplit/mergeProcessor.d.ts +3 -0
- package/packages/core/dist/chatSplit/mergeProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/mergeProcessor.js +88 -0
- package/packages/core/dist/chatSplit/paragraphProcessor.d.ts +14 -0
- package/packages/core/dist/chatSplit/paragraphProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/paragraphProcessor.js +66 -0
- package/packages/core/dist/chatSplit/periodProcessor.d.ts +4 -0
- package/packages/core/dist/chatSplit/periodProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/periodProcessor.js +110 -0
- package/packages/core/dist/chatSplit/positionHelpers.d.ts +12 -0
- package/packages/core/dist/chatSplit/positionHelpers.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/positionHelpers.js +57 -0
- package/packages/core/dist/chatSplit/productCardProcessor.d.ts +12 -0
- package/packages/core/dist/chatSplit/productCardProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/productCardProcessor.js +138 -0
- package/packages/core/dist/chatSplit/punctuationNormalization.d.ts +5 -0
- package/packages/core/dist/chatSplit/punctuationNormalization.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/punctuationNormalization.js +103 -0
- package/packages/core/dist/chatSplit/questionProcessor.d.ts +6 -0
- package/packages/core/dist/chatSplit/questionProcessor.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/questionProcessor.js +212 -0
- package/packages/core/dist/chatSplit/sections.d.ts +23 -0
- package/packages/core/dist/chatSplit/sections.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/sections.js +153 -0
- package/packages/core/dist/chatSplit/splitChatText.d.ts +6 -0
- package/packages/core/dist/chatSplit/splitChatText.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/splitChatText.js +119 -0
- package/packages/core/dist/chatSplit/splitConstants.d.ts +3 -0
- package/packages/core/dist/chatSplit/splitConstants.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/splitConstants.js +2 -0
- package/packages/core/dist/chatSplit/splitProcessors.d.ts +22 -0
- package/packages/core/dist/chatSplit/splitProcessors.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/splitProcessors.js +105 -0
- package/packages/core/dist/chatSplit/textHelpers.d.ts +27 -0
- package/packages/core/dist/chatSplit/textHelpers.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/textHelpers.js +77 -0
- package/packages/core/dist/chatSplit/urlNormalization.d.ts +7 -0
- package/packages/core/dist/chatSplit/urlNormalization.d.ts.map +1 -0
- package/packages/core/dist/chatSplit/urlNormalization.js +13 -0
- package/packages/core/dist/index.d.ts +2 -0
- package/packages/core/dist/index.d.ts.map +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/jest.config.js +23 -0
- package/packages/core/package.json +38 -0
- package/packages/core/src/__tests__/splitChatText.basic.test.ts +123 -0
- package/packages/core/src/__tests__/splitChatText.coverageLists.test.ts +108 -0
- package/packages/core/src/__tests__/splitChatText.coverageProcessors.test.ts +172 -0
- package/packages/core/src/__tests__/splitChatText.coverageQuestions.test.ts +95 -0
- package/packages/core/src/__tests__/splitChatText.dataProtection.test.ts +96 -0
- package/packages/core/src/__tests__/splitChatText.dataTests1.test.ts +137 -0
- package/packages/core/src/__tests__/splitChatText.dataTests2.test.ts +134 -0
- package/packages/core/src/__tests__/splitChatText.edgeCases.test.ts +157 -0
- package/packages/core/src/__tests__/splitChatText.helpers.ts +6 -0
- package/packages/core/src/__tests__/splitChatText.punctuation.test.ts +113 -0
- package/packages/core/src/__tests__/splitChatText.realWorld.test.ts +118 -0
- package/packages/core/src/__tests__/splitChatText.urlProtection.test.ts +102 -0
- package/packages/core/src/chatSplit/breakProcessor.ts +103 -0
- package/packages/core/src/chatSplit/constants.ts +50 -0
- package/packages/core/src/chatSplit/index.ts +1 -0
- package/packages/core/src/chatSplit/listNormalization.ts +189 -0
- package/packages/core/src/chatSplit/listProcessor.ts +74 -0
- package/packages/core/src/chatSplit/mergeProcessor.ts +124 -0
- package/packages/core/src/chatSplit/paragraphProcessor.ts +86 -0
- package/packages/core/src/chatSplit/periodProcessor.ts +148 -0
- package/packages/core/src/chatSplit/positionHelpers.ts +66 -0
- package/packages/core/src/chatSplit/productCardProcessor.ts +184 -0
- package/packages/core/src/chatSplit/punctuationNormalization.ts +142 -0
- package/packages/core/src/chatSplit/questionProcessor.ts +298 -0
- package/packages/core/src/chatSplit/sections.ts +243 -0
- package/packages/core/src/chatSplit/splitChatText.ts +156 -0
- package/packages/core/src/chatSplit/splitConstants.ts +2 -0
- package/packages/core/src/chatSplit/splitProcessors.ts +153 -0
- package/packages/core/src/chatSplit/textHelpers.ts +86 -0
- package/packages/core/src/chatSplit/urlNormalization.ts +17 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/tsconfig.build.json +4 -0
- package/packages/core/tsconfig.json +25 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
describe('Basic question splitting', () => {
|
|
4
|
+
test('should split at question mark when text follows', () => {
|
|
5
|
+
const input = '¿Qué te parece el Nike Air Max 90 en tonos hueso/oliva/gris, o el Nike Pegasus Plus en color negro? También tengo otras opciones disponibles si prefieres ver más estilos.';
|
|
6
|
+
const expected = [
|
|
7
|
+
'¿Qué te parece el Nike Air Max 90 en tonos hueso/oliva/gris, o el Nike Pegasus Plus en color negro?',
|
|
8
|
+
'También tengo otras opciones disponibles si prefieres ver más estilos.',
|
|
9
|
+
];
|
|
10
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
11
|
+
});
|
|
12
|
+
test('should not split when only one sentence', () => {
|
|
13
|
+
const input = '¿Cómo estás hoy?';
|
|
14
|
+
const expected = ['¿Cómo estás hoy?'];
|
|
15
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
16
|
+
});
|
|
17
|
+
test('should not split short text even with question', () => {
|
|
18
|
+
const input = 'Me gusta mucho. ¿Te parece bien?';
|
|
19
|
+
const expected = ['Me gusta mucho. ¿Te parece bien?'];
|
|
20
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('Contiguous questions', () => {
|
|
24
|
+
test('should split contiguous questions at the last question mark', () => {
|
|
25
|
+
const input = '¿Qué te parece esta? ¿quieres ver más fotos? También tengo otras opciones disponibles si prefieres ver más estilos.';
|
|
26
|
+
const expected = [
|
|
27
|
+
'¿Qué te parece esta? ¿quieres ver más fotos? ',
|
|
28
|
+
'También tengo otras opciones disponibles si prefieres ver más estilos.',
|
|
29
|
+
];
|
|
30
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
31
|
+
});
|
|
32
|
+
test('should handle multiple contiguous questions', () => {
|
|
33
|
+
const input = '¿Te gusta? ¿Quieres comprarlo? ¿O prefieres ver más opciones? Perfecto, entonces procedamos.';
|
|
34
|
+
const expected = [
|
|
35
|
+
'¿Te gusta? ¿Quieres comprarlo? ¿O prefieres ver más opciones? ',
|
|
36
|
+
'Perfecto, entonces procedamos.',
|
|
37
|
+
];
|
|
38
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
39
|
+
});
|
|
40
|
+
test('should not treat questions as contiguous when separated by periods', () => {
|
|
41
|
+
const input = '¿Qué te parece esta opción? Me encanta este modelo. También tengo otras opciones. ¿Te gustaría ver más?';
|
|
42
|
+
const expected = [
|
|
43
|
+
'¿Qué te parece esta opción? Me encanta este modelo.',
|
|
44
|
+
'También tengo otras opciones. ¿Te gustaría ver más?',
|
|
45
|
+
];
|
|
46
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('Period splitting for long text', () => {
|
|
50
|
+
test('should split at periods when text is over 100 chars', () => {
|
|
51
|
+
const input = 'Me encanta el estilo de ese sweater, yo creo que podría favorecerte demasiado, así que te propongo que lo lleves, hermosa, sé que es la opción correcta. Además, te ofrezco 20% de descuento.';
|
|
52
|
+
const expected = [
|
|
53
|
+
'Me encanta el estilo de ese sweater, yo creo que podría favorecerte demasiado, así que te propongo que lo lleves, hermosa, sé que es la opción correcta.',
|
|
54
|
+
'Además, te ofrezco 20% de descuento.',
|
|
55
|
+
];
|
|
56
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
57
|
+
});
|
|
58
|
+
test('should not split when text is under 100 chars', () => {
|
|
59
|
+
const input = 'Me encanta el estilo de ese sweater, yo creo que podría favorecerte demasiado, llévalo. Aceptas?';
|
|
60
|
+
const expected = [
|
|
61
|
+
'Me encanta el estilo de ese sweater, yo creo que podría favorecerte demasiado, llévalo. Aceptas?',
|
|
62
|
+
];
|
|
63
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
64
|
+
});
|
|
65
|
+
test('should handle multiple period splits', () => {
|
|
66
|
+
const input = 'Este producto es excelente para uso diario y tiene una calidad premium que durará años. Además, viene con garantía extendida de 2 años sin costo adicional. También incluye envío gratuito a toda la ciudad y instalación profesional gratuita. ¿Te interesa conocer más detalles?';
|
|
67
|
+
const expected = [
|
|
68
|
+
'Este producto es excelente para uso diario y tiene una calidad premium que durará años.',
|
|
69
|
+
'Además, viene con garantía extendida de 2 años sin costo adicional.',
|
|
70
|
+
'También incluye envío gratuito a toda la ciudad y instalación profesional gratuita. ¿Te interesa conocer más detalles?',
|
|
71
|
+
];
|
|
72
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('Smart question-period combination', () => {
|
|
76
|
+
test('should combine short question with following sentence to avoid fragmentation', () => {
|
|
77
|
+
const input = '¿Qué te parece este Nike Air Max 90 en color Hueso claro? También lo tengo en Blanco/Gris universitario. Si prefieres otro modelo, el Nike Pegasus Plus está disponible en Negro o en Azul glacial. ¿Te gustaría ver más opciones?';
|
|
78
|
+
const expected = [
|
|
79
|
+
'¿Qué te parece este Nike Air Max 90 en color Hueso claro? También lo tengo en Blanco/Gris universitario.',
|
|
80
|
+
'Si prefieres otro modelo, el Nike Pegasus Plus está disponible en Negro o en Azul glacial. ¿Te gustaría ver más opciones?',
|
|
81
|
+
];
|
|
82
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
83
|
+
});
|
|
84
|
+
test('should split after long question even if followed by another sentence', () => {
|
|
85
|
+
const input = '¿Qué te parece esta opción del Nike Air Max 90 en color Hueso claro/Oliva neutro/Gris universitario/Cueva? También tengo disponibles otras opciones como el mismo modelo en Blanco/Gris universitario/Gris vasto/Rojo universitario, o podríamos ver el Nike Pegasus Plus en Negro o el Tenis de skateboarding en Blanco/Rosa óxido/Negro. ¿Te gustaría explorar alguna de estas alternativas?';
|
|
86
|
+
const expected = [
|
|
87
|
+
'¿Qué te parece esta opción del Nike Air Max 90 en color Hueso claro/Oliva neutro/Gris universitario/Cueva?',
|
|
88
|
+
'También tengo disponibles otras opciones como el mismo modelo en Blanco/Gris universitario/Gris vasto/Rojo universitario, o podríamos ver el Nike Pegasus Plus en Negro o el Tenis de skateboarding en Blanco/Rosa óxido/Negro.',
|
|
89
|
+
'¿Te gustaría explorar alguna de estas alternativas?',
|
|
90
|
+
];
|
|
91
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
92
|
+
});
|
|
93
|
+
test('should avoid splitting before short questions', () => {
|
|
94
|
+
const input = 'Este producto tiene excelentes características y viene con garantía extendida de por vida, además incluye soporte técnico 24/7. ¿Te interesa?';
|
|
95
|
+
const expected = [
|
|
96
|
+
'Este producto tiene excelentes características y viene con garantía extendida de por vida, además incluye soporte técnico 24/7. ¿Te interesa?',
|
|
97
|
+
];
|
|
98
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.coverageLists.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.coverageLists.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
describe('List normalization - colon before number', () => {
|
|
4
|
+
test('should normalize inline list when colon immediately precedes number', () => {
|
|
5
|
+
const input = 'Necesito estos datos:1. Nombre completo 2. Email 3. Cédula';
|
|
6
|
+
const result = splitChatText(input);
|
|
7
|
+
const hasIntro = result.some((chunk) => chunk.includes('Necesito estos datos:'));
|
|
8
|
+
expect(hasIntro).toBe(true);
|
|
9
|
+
const hasList = result.some((chunk) => chunk.includes('1. Nombre completo'));
|
|
10
|
+
expect(hasList).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('List normalization - question mark before list', () => {
|
|
14
|
+
test('should normalize list items after question mark with non-first item', () => {
|
|
15
|
+
const input = '¿Cuál prefieres? 3. Ciudad A 4. Ciudad B 5. Ciudad C';
|
|
16
|
+
const result = splitChatText(input);
|
|
17
|
+
const hasContent = result.some((chunk) => chunk.includes('3. Ciudad A') && chunk.includes('4. Ciudad B'));
|
|
18
|
+
expect(hasContent).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('Sections - numbered list with text continuation', () => {
|
|
22
|
+
test('should end numbered list when non-list text follows', () => {
|
|
23
|
+
const input = '1. Opción primera disponible ahora\n' +
|
|
24
|
+
'2. Opción segunda disponible también\n' +
|
|
25
|
+
'Este texto no es parte de la lista sino que continúa la descripción del producto con información adicional.';
|
|
26
|
+
const result = splitChatText(input);
|
|
27
|
+
const hasListItems = result.some((chunk) => chunk.includes('1. Opción primera') && chunk.includes('2. Opción segunda'));
|
|
28
|
+
expect(hasListItems).toBe(true);
|
|
29
|
+
const hasTextSeparate = result.some((chunk) => chunk.includes('Este texto no es parte'));
|
|
30
|
+
expect(hasTextSeparate).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('Sections - bullet list with text after', () => {
|
|
34
|
+
test('should end bullet list when regular text follows', () => {
|
|
35
|
+
const input = '- Primera opción disponible en la tienda\n' +
|
|
36
|
+
'- Segunda opción disponible en la tienda\n' +
|
|
37
|
+
'Este texto no es parte de la lista de opciones disponibles sino información adicional del producto.';
|
|
38
|
+
const result = splitChatText(input);
|
|
39
|
+
const hasBulletList = result.some((chunk) => chunk.includes('- Primera opción') && chunk.includes('- Segunda opción'));
|
|
40
|
+
expect(hasBulletList).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('Sections - numbered list ending with blank lines', () => {
|
|
44
|
+
test('should handle numbered list followed by blank lines only', () => {
|
|
45
|
+
const input = '1. Primera opción\n2. Segunda opción\n\n';
|
|
46
|
+
const result = splitChatText(input);
|
|
47
|
+
const hasList = result.some((chunk) => chunk.includes('1. Primera opción') && chunk.includes('2. Segunda opción'));
|
|
48
|
+
expect(hasList).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('List processor - short bullet list kept together', () => {
|
|
52
|
+
test('should keep short bullet items as one chunk', () => {
|
|
53
|
+
const input = 'Opciones:\n- Rojo\n- Azul\n- Verde\n\nElige tu favorito.';
|
|
54
|
+
const result = splitChatText(input);
|
|
55
|
+
const hasIntro = result.some((chunk) => chunk.includes('Opciones:'));
|
|
56
|
+
expect(hasIntro).toBe(true);
|
|
57
|
+
const hasBulletsTogetherOrSplit = result.some((chunk) => chunk.includes('- Rojo'));
|
|
58
|
+
expect(hasBulletsTogetherOrSplit).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('Split processors - response prompt adjustment', () => {
|
|
62
|
+
test('should handle intro containing puedes responder con pattern', () => {
|
|
63
|
+
const input = 'Datos necesarios: puedes responder con:\n- Nombre\n- Email';
|
|
64
|
+
const result = splitChatText(input);
|
|
65
|
+
const hasIntro = result.some((chunk) => chunk.includes('Datos necesarios:'));
|
|
66
|
+
expect(hasIntro).toBe(true);
|
|
67
|
+
const hasList = result.some((chunk) => chunk.includes('- Nombre'));
|
|
68
|
+
expect(hasList).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('Split processors - question with non-list lines', () => {
|
|
72
|
+
test('should not treat question with mixed content as question-with-options', () => {
|
|
73
|
+
const input = '¿Cuál de estos productos prefieres para tu compra?\n1. Opción A\nEsto no es un item de lista sino texto libre';
|
|
74
|
+
const result = splitChatText(input);
|
|
75
|
+
const hasQuestion = result.some((chunk) => chunk.includes('¿Cuál de estos productos'));
|
|
76
|
+
expect(hasQuestion).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('Sections - markdown section with bullet after double newline', () => {
|
|
80
|
+
test('should handle markdown section followed by bullet after break', () => {
|
|
81
|
+
const input = '*Información del producto*\n' +
|
|
82
|
+
'Detalles del artículo seleccionado para tu compra\n\n' +
|
|
83
|
+
'- Característica especial del producto disponible';
|
|
84
|
+
const result = splitChatText(input);
|
|
85
|
+
const hasMarkdownSection = result.some((chunk) => chunk.includes('*Información del producto*'));
|
|
86
|
+
expect(hasMarkdownSection).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.coverageProcessors.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.coverageProcessors.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
const MIN_SPLIT_COUNT = 2;
|
|
4
|
+
const SINGLE_CHUNK = 1;
|
|
5
|
+
const FIRST_ELEMENT = 0;
|
|
6
|
+
const LONG_TEXT_PAD = 'Este es un contenido suficientemente largo para que el procesador lo considere válido para dividir.';
|
|
7
|
+
describe('Break processor - text after break with no further newlines', () => {
|
|
8
|
+
test('should split at double newline when after-text has no newlines', () => {
|
|
9
|
+
const input = `${LONG_TEXT_PAD}\n\nEste texto viene después del salto sin más saltos internos`;
|
|
10
|
+
const result = splitChatText(input);
|
|
11
|
+
expect(result.length).toBeGreaterThanOrEqual(MIN_SPLIT_COUNT);
|
|
12
|
+
const hasBefore = result.some((chunk) => chunk.includes('suficientemente largo'));
|
|
13
|
+
expect(hasBefore).toBe(true);
|
|
14
|
+
const hasAfter = result.some((chunk) => chunk.includes('viene después del salto'));
|
|
15
|
+
expect(hasAfter).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe('Break processor - question with short intro and bullets', () => {
|
|
19
|
+
test('should not split when question precedes short intro with bullets', () => {
|
|
20
|
+
const input = '¿Qué quieres hacer con tu pedido actual de la tienda?\n\nOpciones:\n- Cancelar\n- Modificar';
|
|
21
|
+
const result = splitChatText(input);
|
|
22
|
+
const allTogether = result.some((chunk) => chunk.includes('¿Qué quieres') && chunk.includes('- Cancelar'));
|
|
23
|
+
expect(allTogether).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('Break processor - lowercase response prompt', () => {
|
|
27
|
+
test('should not split when before ends with puedes responder con', () => {
|
|
28
|
+
const input = 'Algo corto aquí.\nOtro texto mediano puedes responder con:\n\n- Opción primera\n- Opción segunda';
|
|
29
|
+
const result = splitChatText(input);
|
|
30
|
+
const hasAll = result.some((chunk) => chunk.includes('puedes responder con:') && chunk.includes('- Opción primera'));
|
|
31
|
+
expect(hasAll).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('Break processor - long paragraphs before break', () => {
|
|
35
|
+
test('should not split when before-text has long paragraphs', () => {
|
|
36
|
+
const longParagraph = 'Este es un párrafo extremadamente largo que describe en detalle todas las características del producto,' +
|
|
37
|
+
' incluyendo materiales, dimensiones, colores disponibles y opciones de envío para el comprador.';
|
|
38
|
+
const input = `${longParagraph}\nOtro párrafo corto.\n\nTexto después del salto.`;
|
|
39
|
+
const result = splitChatText(input);
|
|
40
|
+
const hasLongParagraph = result.some((chunk) => chunk.includes('extremadamente largo'));
|
|
41
|
+
expect(hasLongParagraph).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('Merge processor - next chunk ends with colon', () => {
|
|
45
|
+
test('should not merge small chunk with next chunk ending in colon', () => {
|
|
46
|
+
const input = 'Hola. Mira estas opciones de productos:\n- Zapatillas Nike\n- Camiseta Adidas';
|
|
47
|
+
const result = splitChatText(input);
|
|
48
|
+
const hasContent = result.some((chunk) => chunk.includes('opciones de productos:'));
|
|
49
|
+
expect(hasContent).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('Paragraph processor - intro with long paragraphs', () => {
|
|
53
|
+
test('should split intro from long paragraphs after colon', () => {
|
|
54
|
+
const input = 'Detalles del producto:\n' +
|
|
55
|
+
'Este es un párrafo extremadamente largo que describe el producto con muchos detalles importantes sobre sus' +
|
|
56
|
+
' características, materiales, dimensiones y disponibilidad en stock actualmente.\n' +
|
|
57
|
+
'Otro párrafo corto aquí con información adicional.';
|
|
58
|
+
const result = splitChatText(input);
|
|
59
|
+
const hasIntro = result.some((chunk) => chunk === 'Detalles del producto:' || chunk.startsWith('Detalles del producto:'));
|
|
60
|
+
expect(hasIntro).toBe(true);
|
|
61
|
+
expect(result.length).toBeGreaterThanOrEqual(MIN_SPLIT_COUNT);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('Period processor - period inside parentheses', () => {
|
|
65
|
+
test('should not split at period inside parentheses', () => {
|
|
66
|
+
const input = 'El producto que elegiste tiene un valor especial (aprox. $500.000) y es muy recomendable para uso diario.' +
|
|
67
|
+
' Además incluye garantía de dos años completos.';
|
|
68
|
+
const result = splitChatText(input);
|
|
69
|
+
const hasParenIntact = result.some((chunk) => chunk.includes('(aprox. $500.000)'));
|
|
70
|
+
expect(hasParenIntact).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('Period processor - period at end of text', () => {
|
|
74
|
+
test('should not split when period is at the very end of text', () => {
|
|
75
|
+
const input = 'Este producto tiene características especiales que lo hacen único en su categoría de productos deportivos.';
|
|
76
|
+
const result = splitChatText(input);
|
|
77
|
+
expect(result).toHaveLength(SINGLE_CHUNK);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('Product card - trailing question by findQuestionByStart', () => {
|
|
81
|
+
test('should separate trailing question starting with inverted mark from last card', () => {
|
|
82
|
+
const input = 'Encontré opciones:\n\n' +
|
|
83
|
+
'1. 🛍️ Zapatillas Nike Air: 💵 $500.000\n📏 Talla: 40, 41, 42.\n' +
|
|
84
|
+
'¿Te gusta este producto?';
|
|
85
|
+
const result = splitChatText(input);
|
|
86
|
+
const hasQuestion = result.some((chunk) => chunk.includes('¿Te gusta este producto?'));
|
|
87
|
+
expect(hasQuestion).toBe(true);
|
|
88
|
+
const hasCard = result.some((chunk) => chunk.includes('Zapatillas Nike Air'));
|
|
89
|
+
expect(hasCard).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('Punctuation normalization - mark at end of text', () => {
|
|
93
|
+
test('should handle inverted mark followed by only spaces at end', () => {
|
|
94
|
+
const input = 'hola ¿ ';
|
|
95
|
+
const result = splitChatText(input);
|
|
96
|
+
expect(result).toHaveLength(SINGLE_CHUNK);
|
|
97
|
+
expect(result[FIRST_ELEMENT]).toContain('¿');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('Position helpers - unbalanced close paren', () => {
|
|
101
|
+
test('should handle text with close paren before open paren', () => {
|
|
102
|
+
const input = 'Texto con paréntesis cerrado) aquí y luego (más información) sobre el producto disponible.' +
|
|
103
|
+
' También tenemos otras opciones para ti.';
|
|
104
|
+
const result = splitChatText(input);
|
|
105
|
+
const hasContent = result.some((chunk) => chunk.includes('paréntesis cerrado)'));
|
|
106
|
+
expect(hasContent).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.coverageQuestions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.coverageQuestions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
const MIN_SPLIT_COUNT = 2;
|
|
4
|
+
const SINGLE_CHUNK = 1;
|
|
5
|
+
describe('Question processor - parenthetical clarification split', () => {
|
|
6
|
+
test('should split after parenthetical when text follows and gap is large', () => {
|
|
7
|
+
const input = 'Este texto tiene información bastante detallada sobre nuestros productos y servicios disponibles actualmente?' +
|
|
8
|
+
' (en cualquier talla y color disponible actualmente en nuestra tienda del centro comercial y sus alrededores)?' +
|
|
9
|
+
' Si necesitas otra opción avísame por favor.';
|
|
10
|
+
const result = splitChatText(input);
|
|
11
|
+
const hasParenthetical = result.some((chunk) => chunk.includes('centro comercial'));
|
|
12
|
+
expect(hasParenthetical).toBe(true);
|
|
13
|
+
const hasContinuation = result.some((chunk) => chunk.includes('Si necesitas otra opción'));
|
|
14
|
+
expect(hasContinuation).toBe(true);
|
|
15
|
+
expect(result.length).toBeGreaterThanOrEqual(MIN_SPLIT_COUNT);
|
|
16
|
+
});
|
|
17
|
+
test('should not split when parenthetical has no text after and gap is large', () => {
|
|
18
|
+
const input = 'Este texto tiene información bastante detallada sobre nuestros productos y servicios disponibles actualmente?' +
|
|
19
|
+
' (en cualquier talla y color disponible actualmente en nuestra tienda del centro comercial y sus alrededores)?';
|
|
20
|
+
const result = splitChatText(input);
|
|
21
|
+
expect(result).toHaveLength(SINGLE_CHUNK);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('Question processor - long question emoji paths', () => {
|
|
25
|
+
test('should not split long question when only emoji follows', () => {
|
|
26
|
+
const input = '¿Qué te parece esta opción del Nike Air Max 90 en color Hueso claro y Oliva neutro y Gris universitario? 😊';
|
|
27
|
+
const result = splitChatText(input);
|
|
28
|
+
expect(result).toEqual([input]);
|
|
29
|
+
});
|
|
30
|
+
test('should split long question with emoji and text after', () => {
|
|
31
|
+
const input = '¿Qué te parece esta opción del Nike Air Max 90 en color Hueso claro y Oliva neutro y Gris universitario de Nike?' +
|
|
32
|
+
' 😊 Tenemos muchas más opciones disponibles en nuestra tienda.';
|
|
33
|
+
const result = splitChatText(input);
|
|
34
|
+
const hasQuestionWithEmoji = result.some((chunk) => chunk.includes('Gris universitario de Nike?') && chunk.includes('😊'));
|
|
35
|
+
expect(hasQuestionWithEmoji).toBe(true);
|
|
36
|
+
const hasAfter = result.some((chunk) => chunk.includes('Tenemos muchas más opciones'));
|
|
37
|
+
expect(hasAfter).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('Question processor - short question emoji paths', () => {
|
|
41
|
+
test('should split short question with emoji and text after', () => {
|
|
42
|
+
const input = '¿Te gusta este producto? 😊 Podemos buscar otras opciones de productos disponibles para ti en la tienda virtual ahora mismo.';
|
|
43
|
+
const result = splitChatText(input);
|
|
44
|
+
const hasQuestionWithEmoji = result.some((chunk) => chunk.includes('¿Te gusta este producto? 😊'));
|
|
45
|
+
expect(hasQuestionWithEmoji).toBe(true);
|
|
46
|
+
const hasTextAfter = result.some((chunk) => chunk.includes('Podemos buscar otras opciones'));
|
|
47
|
+
expect(hasTextAfter).toBe(true);
|
|
48
|
+
expect(result.length).toBeGreaterThanOrEqual(MIN_SPLIT_COUNT);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('Question processor - contiguous questions emoji paths', () => {
|
|
52
|
+
test('should split contiguous questions with emoji and text after', () => {
|
|
53
|
+
const input = '¿Te gusta? ¿Lo quieres? 😊 Tenemos descuentos especiales hoy disponibles para todos nuestros clientes.';
|
|
54
|
+
const result = splitChatText(input);
|
|
55
|
+
const hasQuestionsWithEmoji = result.some((chunk) => chunk.includes('¿Te gusta?') && chunk.includes('¿Lo quieres?') && chunk.includes('😊'));
|
|
56
|
+
expect(hasQuestionsWithEmoji).toBe(true);
|
|
57
|
+
const hasTextAfter = result.some((chunk) => chunk.includes('Tenemos descuentos especiales'));
|
|
58
|
+
expect(hasTextAfter).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
test('should not split contiguous questions when only emoji follows', () => {
|
|
61
|
+
const input = '¿Te gusta? ¿Lo quieres? 😊';
|
|
62
|
+
const result = splitChatText(input);
|
|
63
|
+
expect(result).toEqual([input]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('Question processor - non-emoji uppercase after question', () => {
|
|
67
|
+
test('should split when uppercase text follows short question', () => {
|
|
68
|
+
const input = '¿Te gusta este producto? Podemos buscar otras opciones para ti en la tienda de productos y accesorios deportivos.';
|
|
69
|
+
const result = splitChatText(input);
|
|
70
|
+
expect(result.length).toBeGreaterThanOrEqual(MIN_SPLIT_COUNT);
|
|
71
|
+
const hasQuestion = result.some((chunk) => chunk.includes('¿Te gusta este producto?'));
|
|
72
|
+
expect(hasQuestion).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.dataProtection.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.dataProtection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
describe('Number and price protection', () => {
|
|
4
|
+
test('should not split formatted numbers with periods', () => {
|
|
5
|
+
const input = 'El precio total es $1.000.000 y puedes pagarlo en cuotas. También tenemos un descuento del 20% si pagas de contado.';
|
|
6
|
+
const expected = [
|
|
7
|
+
'El precio total es $1.000.000 y puedes pagarlo en cuotas.',
|
|
8
|
+
'También tenemos un descuento del 20% si pagas de contado.',
|
|
9
|
+
];
|
|
10
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
11
|
+
});
|
|
12
|
+
test('should handle multiple formatted numbers', () => {
|
|
13
|
+
const input = 'Los precios van desde $100.000 hasta $5.000.000 dependiendo del modelo. También ofrecemos financiamiento desde $50.000 mensuales.';
|
|
14
|
+
const expected = [
|
|
15
|
+
'Los precios van desde $100.000 hasta $5.000.000 dependiendo del modelo.',
|
|
16
|
+
'También ofrecemos financiamiento desde $50.000 mensuales.',
|
|
17
|
+
];
|
|
18
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
19
|
+
});
|
|
20
|
+
test('should handle decimal numbers', () => {
|
|
21
|
+
const input = 'La medida exacta es 15.5 centímetros y el peso es aproximadamente 2.3 kilogramos. La precisión es del 99.9% según las especificaciones técnicas.';
|
|
22
|
+
const expected = [
|
|
23
|
+
'La medida exacta es 15.5 centímetros y el peso es aproximadamente 2.3 kilogramos.',
|
|
24
|
+
'La precisión es del 99.9% según las especificaciones técnicas.',
|
|
25
|
+
];
|
|
26
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
27
|
+
});
|
|
28
|
+
test('should handle numbers without currency symbols', () => {
|
|
29
|
+
const input = 'La producción anual es de 1.500.000 unidades y el crecimiento proyectado es del 25.5% para el próximo año. Las ventas superaron los 2.000.000 de unidades.';
|
|
30
|
+
const expected = [
|
|
31
|
+
'La producción anual es de 1.500.000 unidades y el crecimiento proyectado es del 25.5% para el próximo año.',
|
|
32
|
+
'Las ventas superaron los 2.000.000 de unidades.',
|
|
33
|
+
];
|
|
34
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('Email protection - basic', () => {
|
|
38
|
+
test('should not split email addresses', () => {
|
|
39
|
+
const input = 'Para contactarnos puedes escribir a juan.perez@example.com y te responderemos pronto. También puedes llamar al teléfono disponible.';
|
|
40
|
+
const expected = [
|
|
41
|
+
'Para contactarnos puedes escribir a juan.perez@example.com y te responderemos pronto.',
|
|
42
|
+
'También puedes llamar al teléfono disponible.',
|
|
43
|
+
];
|
|
44
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
45
|
+
});
|
|
46
|
+
test('should handle multiple email addresses', () => {
|
|
47
|
+
const input = 'Contacta a support.team@company.co.uk para soporte técnico o a ventas.info@company.co.uk para información comercial. Nuestro equipo está disponible.';
|
|
48
|
+
const expected = [
|
|
49
|
+
'Contacta a support.team@company.co.uk para soporte técnico o a ventas.info@company.co.uk para información comercial.',
|
|
50
|
+
'Nuestro equipo está disponible.',
|
|
51
|
+
];
|
|
52
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('Email protection - lists', () => {
|
|
56
|
+
test('should handle emails in numbered lists', () => {
|
|
57
|
+
const input = 'Por favor envíame los siguientes datos:\n\n1. Nombre completo\n2. Correo electrónico (ejemplo: juan.perez@gmail.com)\n3. Número de teléfono\n\nTe contactaremos pronto.';
|
|
58
|
+
const expected = [
|
|
59
|
+
'Por favor envíame los siguientes datos:',
|
|
60
|
+
'1. Nombre completo\n2. Correo electrónico (ejemplo: juan.perez@gmail.com)\n3. Número de teléfono',
|
|
61
|
+
'Te contactaremos pronto.',
|
|
62
|
+
];
|
|
63
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
64
|
+
});
|
|
65
|
+
test('should keep numbered lists together without splitting at periods', () => {
|
|
66
|
+
const input = `📋 Perfecto, David 😊. Para procesar tu pedido contra‑entrega, necesito algunos datos:
|
|
67
|
+
1. Nombre completo
|
|
68
|
+
2. Email
|
|
69
|
+
3. Cédula
|
|
70
|
+
4. Dirección completa (incluye barrio y, si aplica, detalles del apartamento/torre/conjunto).`;
|
|
71
|
+
const expected = [
|
|
72
|
+
'📋 Perfecto, David 😊. Para procesar tu pedido contra‑entrega, necesito algunos datos:',
|
|
73
|
+
`1. Nombre completo
|
|
74
|
+
2. Email
|
|
75
|
+
3. Cédula
|
|
76
|
+
4. Dirección completa (incluye barrio y, si aplica, detalles del apartamento/torre/conjunto).`,
|
|
77
|
+
];
|
|
78
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.dataTests1.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.dataTests1.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
import { splitChatText } from '../index.js';
|
|
3
|
+
describe('Data tests - numbered list normalization', () => {
|
|
4
|
+
test('Test 1: Numbered lists should not split at periods', () => {
|
|
5
|
+
const input = `📋 Para procesar tu pedido necesito algunos datos: 1. Nombre completo 2. Email 3. Cédula 4. Dirección completa (dirección exacta con barrio y si aplican detalles del apartamento/torre/conjunto).`;
|
|
6
|
+
const expected = [
|
|
7
|
+
`📋 Para procesar tu pedido necesito algunos datos:`,
|
|
8
|
+
`1. Nombre completo\n2. Email\n3. Cédula\n4. Dirección completa (dirección exacta con barrio y si aplican detalles del apartamento/torre/conjunto).`,
|
|
9
|
+
];
|
|
10
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
11
|
+
});
|
|
12
|
+
test('Test 2: Bullet lists should not split within items', () => {
|
|
13
|
+
const input = `Encontré estas opciones:\n\n- Nike Pegasus Plus – Zapatillas de alto rendimiento para maratones y running, con amortiguación ZoomX Foam y parte superior Flyknit que se adapta al pie. Disponibles en negro y en una combinación multicolor.\n- Nike Air Max 90 – Modelo clásico con suela tipo waffle y la icónica amortiguación Air visible, en tonos neutros como hueso claro/oliva/gris universitario.\n- Tenis de skateboarding – Zapatillas diseñadas para skate con suela vulcanizada y Zoom Air, disponibles en blanco con varios materiales (cuero, gamuza, algodón) y también en negro.\n¿Cuál de estos modelos te interesa más? 😊`;
|
|
14
|
+
const expected = [
|
|
15
|
+
`Encontré estas opciones:`,
|
|
16
|
+
`- Nike Pegasus Plus – Zapatillas de alto rendimiento para maratones y running, con amortiguación ZoomX Foam y parte superior Flyknit que se adapta al pie. Disponibles en negro y en una combinación multicolor.`,
|
|
17
|
+
`- Nike Air Max 90 – Modelo clásico con suela tipo waffle y la icónica amortiguación Air visible, en tonos neutros como hueso claro/oliva/gris universitario.`,
|
|
18
|
+
`- Tenis de skateboarding – Zapatillas diseñadas para skate con suela vulcanizada y Zoom Air, disponibles en blanco con varios materiales (cuero, gamuza, algodón) y también en negro.`,
|
|
19
|
+
`¿Cuál de estos modelos te interesa más? 😊`,
|
|
20
|
+
];
|
|
21
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('Data tests - bullet list integrity', () => {
|
|
25
|
+
test('Test 3: Bullet lists should keep each item intact', () => {
|
|
26
|
+
const input = `Encontré estas opciones:\n\n- Tenis de skateboarding: Zapatillas diseñadas para skateboarding con suela vulcanizada, amortiguación Zoom Air y una parte superior rediseñada para un mejor ajuste y comodidad. Disponibles en varios colores y materiales como cuero, gamuza, lona y algodón. 👟\n- Nike Pegasus Plus: Zapatillas de alto rendimiento para running con espuma ZoomX Foam de largo completo, parte superior Flyknit transpirable y suela de goma resistente para tracción. Ideales para maratones y entrenamientos diarios. 🏃♂️\n- Nike Air Max 90: Calzado clásico de running con suela tipo waffle y amortiguación Air visible. Ofrece ventilación y comodidad con un diseño icónico y materiales de alta calidad. 👟\n- Nike Dunk Low Retro: Modelo clásico con parte superior de cuero auténtico y sintético, entresuela de espuma ligera y suela de goma con punto de pivote. Disponible en combinaciones de colores.`;
|
|
27
|
+
const expected = [
|
|
28
|
+
`Encontré estas opciones:`,
|
|
29
|
+
`- Tenis de skateboarding: Zapatillas diseñadas para skateboarding con suela vulcanizada, amortiguación Zoom Air y una parte superior rediseñada para un mejor ajuste y comodidad. Disponibles en varios colores y materiales como cuero, gamuza, lona y algodón. 👟`,
|
|
30
|
+
`- Nike Pegasus Plus: Zapatillas de alto rendimiento para running con espuma ZoomX Foam de largo completo, parte superior Flyknit transpirable y suela de goma resistente para tracción. Ideales para maratones y entrenamientos diarios. 🏃♂️`,
|
|
31
|
+
`- Nike Air Max 90: Calzado clásico de running con suela tipo waffle y amortiguación Air visible. Ofrece ventilación y comodidad con un diseño icónico y materiales de alta calidad. 👟`,
|
|
32
|
+
`- Nike Dunk Low Retro: Modelo clásico con parte superior de cuero auténtico y sintético, entresuela de espuma ligera y suela de goma con punto de pivote. Disponible en combinaciones de colores.`,
|
|
33
|
+
];
|
|
34
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('Data tests - emoji and product descriptions', () => {
|
|
38
|
+
test('Test 4: emoji with question splitting', () => {
|
|
39
|
+
const input = `Lamentablemente, el Nike Pegasus Plus no está disponible en algodón. Sin embargo, tenemos tenis de skateboarding en algodón (color blanco) y otras opciones en este material. 😊 ¿Te gustaría continuar con alguna de estas alternativas o buscar otro producto?`;
|
|
40
|
+
const expected = [
|
|
41
|
+
'Lamentablemente, el Nike Pegasus Plus no está disponible en algodón.',
|
|
42
|
+
'Sin embargo, tenemos tenis de skateboarding en algodón (color blanco) y otras opciones en este material.',
|
|
43
|
+
'😊 ¿Te gustaría continuar con alguna de estas alternativas o buscar otro producto?',
|
|
44
|
+
];
|
|
45
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
46
|
+
});
|
|
47
|
+
test('Test 5: product description with question', () => {
|
|
48
|
+
const input = `Encontré esta opción: Tenis de skateboarding (Algodón, Color: Blanco) – Zapatillas diseñadas para skateboarding con suela vulcanizada, amortiguación Zoom Air y una parte superior rediseñada para un mejor ajuste y comodidad. 👍 ¿Te gusta el producto Tenis de skateboarding?`;
|
|
49
|
+
const expected = [
|
|
50
|
+
'Encontré esta opción: Tenis de skateboarding (Algodón, Color: Blanco) – Zapatillas diseñadas para skateboarding con suela vulcanizada, amortiguación Zoom Air y una parte superior rediseñada para un mejor ajuste y comodidad.',
|
|
51
|
+
'👍 ¿Te gusta el producto Tenis de skateboarding?',
|
|
52
|
+
];
|
|
53
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('Data tests - small chunk merging', () => {
|
|
57
|
+
test('Test 6: Small chunks should merge with next chunk', () => {
|
|
58
|
+
const input = `Mónica, ¿qué te parece la Nike Pegasus Plus? 👟 Precio: $1.015.000. Tall. 38, 41, 43. Colores: Negro, Azul/Espuma/Verde/Negro. Amortiguación ligera y transpirable. ¿Te gusta?`;
|
|
59
|
+
const expected = [
|
|
60
|
+
'Mónica, ¿qué te parece la Nike Pegasus Plus? 👟 Precio: $1.015.000.',
|
|
61
|
+
'Tall. 38, 41, 43. Colores: Negro, Azul/Espuma/Verde/Negro. Amortiguación ligera y transpirable. ¿Te gusta?',
|
|
62
|
+
];
|
|
63
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
64
|
+
});
|
|
65
|
+
test('Test 8: Small chunks with price should merge with next chunk', () => {
|
|
66
|
+
const input = `Nike Air Max 90 – $724.950. Tallas: 40‑43. Colores: Blanco, Gris, Rojo, Hueso, Oliva, Cueva. Suela waffle y Air visible. ¿Qué talla? 👟`;
|
|
67
|
+
const expected = [
|
|
68
|
+
`Nike Air Max 90 – $724.950.`,
|
|
69
|
+
`Tallas: 40‑43. Colores: Blanco, Gris, Rojo, Hueso, Oliva, Cueva. Suela waffle y Air visible. ¿Qué talla? 👟`,
|
|
70
|
+
];
|
|
71
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
72
|
+
});
|
|
73
|
+
test('Test 11: Small chunks with tallas should merge', () => {
|
|
74
|
+
const input = `Kiara, Nike Dunk Low Retro: $724.950. Tallas 39-43. Colores: Burdeos/Vinotinto, Azul, Oliva neutro/Caqui claro, Blanco/Blanco/Negro. Diseño icónico. Gusta o quieres otro color? 👟✨`;
|
|
75
|
+
const expected = [
|
|
76
|
+
`Kiara, Nike Dunk Low Retro: $724.950.`,
|
|
77
|
+
`Tallas 39-43. Colores: Burdeos/Vinotinto, Azul, Oliva neutro/Caqui claro, Blanco/Blanco/Negro. Diseño icónico. Gusta o quieres otro color? 👟✨`,
|
|
78
|
+
];
|
|
79
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('Data tests - numbered list item splitting', () => {
|
|
83
|
+
test('Test 7: Numbered lists should not split items', () => {
|
|
84
|
+
const input = `Encontré dos opciones geniales para correr, Sebastián 👟:\n1. Nike Air Max 90 – $724.950, tallas 40-43. Colores variados, suela waffle.\n2. Nike Pegasus Plus – $1.015.000, tallas 38, 41, 43. Negro, azul y verde. ¿Cuál te gusta más?`;
|
|
85
|
+
const expected = [
|
|
86
|
+
`Encontré dos opciones geniales para correr, Sebastián 👟:`,
|
|
87
|
+
`1. Nike Air Max 90 – $724.950, tallas 40-43. Colores variados, suela waffle.`,
|
|
88
|
+
`2. Nike Pegasus Plus – $1.015.000, tallas 38, 41, 43. Negro, azul y verde. ¿Cuál te gusta más?`,
|
|
89
|
+
];
|
|
90
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
91
|
+
});
|
|
92
|
+
test('Test 9: Long list items (> 150 chars) should split by items', () => {
|
|
93
|
+
const input = `Encontré estas opciones:\nNike Trail – Chaqueta de running impermeable con acabado repelente al agua, ajuste holgado y tonos tierra. Disponible en color multicolor y tallas XS, S, M. Es ideal para correr en el bosque o en la montaña y mantenerse seco. 🏃♂️\nNike Sportswear Breaking Windrunner – Chaqueta amplia con acabado repelente al agua en color negro, con gráficos de átomos giratorios. Disponible en tallas XS, S, M. Perfecta para actividades urbanas o al aire libre, manteniendo la comodidad y la protección contra la lluvia. ☔\n\n¿Cuál de estas chaquetas te interesa más?`;
|
|
94
|
+
const expected = [
|
|
95
|
+
`Encontré estas opciones:`,
|
|
96
|
+
`Nike Trail – Chaqueta de running impermeable con acabado repelente al agua, ajuste holgado y tonos tierra. Disponible en color multicolor y tallas XS, S, M. Es ideal para correr en el bosque o en la montaña y mantenerse seco. 🏃♂️`,
|
|
97
|
+
`Nike Sportswear Breaking Windrunner – Chaqueta amplia con acabado repelente al agua en color negro, con gráficos de átomos giratorios. Disponible en tallas XS, S, M. Perfecta para actividades urbanas o al aire libre, manteniendo la comodidad y la protección contra la lluvia. ☔`,
|
|
98
|
+
`¿Cuál de estas chaquetas te interesa más?`,
|
|
99
|
+
];
|
|
100
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('Data tests - long product list and emoji merge', () => {
|
|
104
|
+
test('Test 10: Long product list with bullet items', () => {
|
|
105
|
+
const input = `¡Perfecto, Leydi! Encontré varias opciones de zapatos deportivos que podrían interesarte. Aquí te detallo algunas:\n\n- Tenis de skateboarding Janoski por 430.000: Son ideales para un estilo casual y dinámico, con excelente agarre para actividades como el skate o uso diario, ofreciendo comodidad y flexibilidad gracias a su diseño vulcanizado y amortiguación Zoom Air.\n\n- Nike Pegasus Plus por 1.015.000: Perfectos para running y entrenamientos intensos, con espuma ZoomX para un retorno de energía superior y una parte superior Flyknit transpirable que se adapta perfectamente al pie.\n\n- Nike Air Max 90 por 724.950: Un clásico con amortiguación Air visible para comodidad todo el día, suela waffle para tracción y un diseño versátil que combina estilo retro con rendimiento en running o uso casual.\n\n- Nike Air Force 1 por 749.950: Icónicos y duraderos, con cuero premium y amortiguación Nike Air para un confort excepcional, ideales para la calle o la cancha con un toque atemporal.\n\n¿Cuál de estos te gusta más, o prefieres que busque algo específico como color o talla?`;
|
|
106
|
+
const expected = [
|
|
107
|
+
`¡Perfecto, Leydi! Encontré varias opciones de zapatos deportivos que podrían interesarte. Aquí te detallo algunas:`,
|
|
108
|
+
`- Tenis de skateboarding Janoski por 430.000: Son ideales para un estilo casual y dinámico, con excelente agarre para actividades como el skate o uso diario, ofreciendo comodidad y flexibilidad gracias a su diseño vulcanizado y amortiguación Zoom Air.`,
|
|
109
|
+
`- Nike Pegasus Plus por 1.015.000: Perfectos para running y entrenamientos intensos, con espuma ZoomX para un retorno de energía superior y una parte superior Flyknit transpirable que se adapta perfectamente al pie.`,
|
|
110
|
+
`- Nike Air Max 90 por 724.950: Un clásico con amortiguación Air visible para comodidad todo el día, suela waffle para tracción y un diseño versátil que combina estilo retro con rendimiento en running o uso casual.`,
|
|
111
|
+
`- Nike Air Force 1 por 749.950: Icónicos y duraderos, con cuero premium y amortiguación Nike Air para un confort excepcional, ideales para la calle o la cancha con un toque atemporal.`,
|
|
112
|
+
`¿Cuál de estos te gusta más, o prefieres que busque algo específico como color o talla?`,
|
|
113
|
+
];
|
|
114
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
115
|
+
});
|
|
116
|
+
test('Test 12: Small emoji-only chunks should merge backward', () => {
|
|
117
|
+
const input = `¡Perfecto! ✅❤️\n\nDe las dos opciones, la *Nike Trail* es ideal si buscas algo ligero y con ajuste holgado para terrenos complicados, mientras que la *Nike Sportswear Breaking Windrunner* te ofrece un tejido más absorbente y un diseño más clásico en negro.\n\n¿Te inclinas por alguna de ellas?\n\nY, si ya sabes la talla, dime cuál prefieres para que pueda confirmar disponibilidad y enviarte los detalles de envío. 🚚💨`;
|
|
118
|
+
const expected = [
|
|
119
|
+
`¡Perfecto! ✅❤️\n\nDe las dos opciones, la *Nike Trail* es ideal si buscas algo ligero y con ajuste holgado para terrenos complicados, mientras que la *Nike Sportswear Breaking Windrunner* te ofrece un tejido más absorbente y un diseño más clásico en negro.\n\n¿Te inclinas por alguna de ellas?`,
|
|
120
|
+
`Y, si ya sabes la talla, dime cuál prefieres para que pueda confirmar disponibilidad y enviarte los detalles de envío. 🚚💨`,
|
|
121
|
+
];
|
|
122
|
+
expect(splitChatText(input)).toEqual(expected);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitChatText.dataTests2.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/splitChatText.dataTests2.test.ts"],"names":[],"mappings":""}
|