File size: 7,938 Bytes
9332ccf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect } from 'vitest';
import { parseInputForHighlighting } from './highlight.js';
import type { Transformation } from '../components/shared/text-buffer.js';

describe('parseInputForHighlighting', () => {
  it('should handle an empty string', () => {
    expect(parseInputForHighlighting('', 0)).toEqual([
      { text: '', type: 'default' },
    ]);
  });

  it('should handle text with no commands or files', () => {
    const text = 'this is a normal sentence';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text, type: 'default' },
    ]);
  });

  it('should highlight a single command at the beginning when index is 0', () => {
    const text = '/help me';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: '/help', type: 'command' },
      { text: ' me', type: 'default' },
    ]);
  });

  it('should NOT highlight a command at the beginning when index is not 0', () => {
    const text = '/help me';
    expect(parseInputForHighlighting(text, 1)).toEqual([
      { text: '/help', type: 'default' },
      { text: ' me', type: 'default' },
    ]);
  });

  it('should highlight a single file path at the beginning', () => {
    const text = '@path/to/file.txt please';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: '@path/to/file.txt', type: 'file' },
      { text: ' please', type: 'default' },
    ]);
  });

  it('should not highlight a command in the middle', () => {
    const text = 'I need /help with this';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'I need /help with this', type: 'default' },
    ]);
  });

  it('should highlight a file path in the middle', () => {
    const text = 'Please check @path/to/file.txt for details';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'Please check ', type: 'default' },
      { text: '@path/to/file.txt', type: 'file' },
      { text: ' for details', type: 'default' },
    ]);
  });

  it('should highlight files but not commands not at the start', () => {
    const text = 'Use /run with @file.js and also /format @another/file.ts';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'Use /run with ', type: 'default' },
      { text: '@file.js', type: 'file' },
      { text: ' and also /format ', type: 'default' },
      { text: '@another/file.ts', type: 'file' },
    ]);
  });

  it('should handle adjacent highlights at start', () => {
    const text = '/run@file.js';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: '/run', type: 'command' },
      { text: '@file.js', type: 'file' },
    ]);
  });

  it('should not highlight command at the end of the string', () => {
    const text = 'Get help with /help';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'Get help with /help', type: 'default' },
    ]);
  });

  it('should handle file paths with dots and dashes', () => {
    const text = 'Check @./path-to/file-name.v2.txt';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'Check ', type: 'default' },
      { text: '@./path-to/file-name.v2.txt', type: 'file' },
    ]);
  });

  it('should not highlight command with dashes and numbers not at start', () => {
    const text = 'Run /command-123 now';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'Run /command-123 now', type: 'default' },
    ]);
  });

  it('should highlight command with dashes and numbers at start', () => {
    const text = '/command-123 now';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: '/command-123', type: 'command' },
      { text: ' now', type: 'default' },
    ]);
  });

  it('should still highlight a file path on a non-zero line', () => {
    const text = 'some text @path/to/file.txt';
    expect(parseInputForHighlighting(text, 1)).toEqual([
      { text: 'some text ', type: 'default' },
      { text: '@path/to/file.txt', type: 'file' },
    ]);
  });

  it('should not highlight command but highlight file on a non-zero line', () => {
    const text = '/cmd @file.txt';
    expect(parseInputForHighlighting(text, 2)).toEqual([
      { text: '/cmd', type: 'default' },
      { text: ' ', type: 'default' },
      { text: '@file.txt', type: 'file' },
    ]);
  });

  it('should highlight a file path with escaped spaces', () => {
    const text = 'cat @/my\\ path/file.txt';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'cat ', type: 'default' },
      { text: '@/my\\ path/file.txt', type: 'file' },
    ]);
  });

  it('should highlight a file path with narrow non-breaking spaces (NNBSP)', () => {
    const text = 'cat @/my\u202Fpath/file.txt';
    expect(parseInputForHighlighting(text, 0)).toEqual([
      { text: 'cat ', type: 'default' },
      { text: '@/my\u202Fpath/file.txt', type: 'file' },
    ]);
  });
});

describe('parseInputForHighlighting with Transformations', () => {
  const transformations: Transformation[] = [
    {
      logStart: 10,
      logEnd: 19,
      logicalText: '@test.png',
      collapsedText: '[Image test.png]',
      type: 'image',
    },
  ];

  it('should show collapsed transformation when cursor is not on it', () => {
    const line = 'Check out @test.png';
    const result = parseInputForHighlighting(
      line,
      0, // line index
      transformations,
      0, // cursor not on transformation
    );

    expect(result).toEqual([
      { text: 'Check out ', type: 'default' },
      { text: '[Image test.png]', type: 'file' },
    ]);
  });

  it('should show expanded transformation when cursor is on it', () => {
    const line = 'Check out @test.png';
    const result = parseInputForHighlighting(
      line,
      0, // line index
      transformations,
      11, // cursor on transformation
    );

    expect(result).toEqual([
      { text: 'Check out ', type: 'default' },
      { text: '@test.png', type: 'file' },
    ]);
  });

  it('should handle multiple transformations in a line', () => {
    const line = 'Images: @test1.png and @test2.png';
    const multiTransformations: Transformation[] = [
      {
        logStart: 8,
        logEnd: 18,
        logicalText: '@test1.png',
        collapsedText: '[Image test1.png]',
        type: 'image',
      },
      {
        logStart: 23,
        logEnd: 33,
        logicalText: '@test2.png',
        collapsedText: '[Image test2.png]',
        type: 'image',
      },
    ];

    // Cursor not on any transformation
    let result = parseInputForHighlighting(line, 0, multiTransformations, 0);
    expect(result).toEqual([
      { text: 'Images: ', type: 'default' },
      { text: '[Image test1.png]', type: 'file' },
      { text: ' and ', type: 'default' },
      { text: '[Image test2.png]', type: 'file' },
    ]);

    // Cursor on first transformation
    result = parseInputForHighlighting(line, 0, multiTransformations, 10);
    expect(result).toEqual([
      { text: 'Images: ', type: 'default' },
      { text: '@test1.png', type: 'file' },
      { text: ' and ', type: 'default' },
      { text: '[Image test2.png]', type: 'file' },
    ]);
  });

  it('should handle empty transformations array', () => {
    const line = 'Check out @test_no_transform.png';
    const result = parseInputForHighlighting(line, 0, [], 0);

    // Should fall back to default highlighting
    expect(result).toEqual([
      { text: 'Check out ', type: 'default' },
      { text: '@test_no_transform.png', type: 'file' },
    ]);
  });

  it('should handle cursor at transformation boundaries', () => {
    const line = 'Check out @test.png';
    const result = parseInputForHighlighting(
      line,
      0,
      transformations,
      10, // cursor at start of transformation
    );

    expect(result[1]).toEqual({ text: '@test.png', type: 'file' });
  });
});