1 package com.acumenvelocity.ath.common;
2
3 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4 import static org.junit.jupiter.api.Assertions.assertEquals;
5 import static org.junit.jupiter.api.Assertions.assertNotNull;
6 import static org.junit.jupiter.api.Assertions.assertTimeout;
7 import static org.junit.jupiter.api.Assertions.assertTrue;
8
9 import java.time.Duration;
10 import java.util.ArrayList;
11 import java.util.List;
12
13 import org.junit.jupiter.api.BeforeEach;
14 import org.junit.jupiter.api.DisplayName;
15 import org.junit.jupiter.api.Test;
16
17 import net.sf.okapi.common.resource.Code;
18 import net.sf.okapi.common.resource.TextFragment;
19
20 class RearrangeCodesTest {
21
22 private TextFragment textFragment;
23 private List<Code> codes;
24
25 @BeforeEach
26 void setUp() {
27 textFragment = new TextFragment();
28 codes = new ArrayList<>();
29 }
30
31 @Test
32 @DisplayName("Should handle null codes list")
33 void testNullCodes() {
34 textFragment.setCodedText("Plain text", null);
35 assertDoesNotThrow(() -> OkapiUtil.rearrangeCodes(null, textFragment));
36 }
37
38 @Test
39 @DisplayName("Should handle null target TextFragment")
40 void testNullTextFragment() {
41 assertDoesNotThrow(() -> OkapiUtil.rearrangeCodes(codes, null));
42 }
43
44 @Test
45 @DisplayName("Should handle TextFragment without codes")
46 void testTextFragmentWithoutCodes() {
47 textFragment.setCodedText("Plain text without codes", codes);
48 assertDoesNotThrow(() -> OkapiUtil.rearrangeCodes(codes, textFragment));
49 assertEquals("Plain text without codes", textFragment.getCodedText());
50 }
51
52 @Test
53 @DisplayName("Should not modify properly ordered codes")
54 void testProperlyOrderedCodes() {
55 Code open = textFragment.append(TextFragment.TagType.OPENING, "b", "<b>");
56 int id = open.getId();
57 textFragment.append("bold text");
58 textFragment.append(TextFragment.TagType.CLOSING, "b", "</b>", id);
59
60 String original = textFragment.getCodedText();
61 OkapiUtil.rearrangeCodes(codes, textFragment);
62 assertEquals(original, textFragment.getCodedText());
63 }
64
65 @Test
66 @DisplayName("Should swap closing code that comes before opening code")
67 void testSwapClosingBeforeOpening() {
68 textFragment.append("Text before ");
69
70
71 List<Code> fragCodes = new ArrayList<>();
72 Code open = new Code(TextFragment.TagType.OPENING, "tag1", "<tag1>");
73 open.setId(1);
74 fragCodes.add(open);
75
76 Code close = new Code(TextFragment.TagType.CLOSING, "tag1", "</tag1>");
77 close.setId(1);
78 fragCodes.add(close);
79
80
81 char openMarkerId = TextFragment.toChar(0);
82 char closeMarkerId = TextFragment.toChar(1);
83
84 String current = textFragment.getCodedText();
85 String badPart = String.valueOf((char) TextFragment.MARKER_CLOSING) + closeMarkerId
86 + " middle text "
87 + String.valueOf((char) TextFragment.MARKER_OPENING) + openMarkerId
88 + " text after";
89
90 textFragment.setCodedText(current + badPart, fragCodes);
91
92
93 int openingIndex = textFragment.getIndexForOpening(1);
94 int closingIndex = textFragment.getIndexForClosing(1);
95 int openingPosBefore = textFragment.getCodePosition(openingIndex);
96 int closingPosBefore = textFragment.getCodePosition(closingIndex);
97
98 assertTrue(closingPosBefore < openingPosBefore,
99 "Before fix: closing should come before opening");
100
101
102 OkapiUtil.rearrangeCodes(codes, textFragment);
103
104
105 openingIndex = textFragment.getIndexForOpening(1);
106 closingIndex = textFragment.getIndexForClosing(1);
107 int openingPosAfter = textFragment.getCodePosition(openingIndex);
108 int closingPosAfter = textFragment.getCodePosition(closingIndex);
109
110 assertTrue(openingPosAfter < closingPosAfter,
111 "After fix: opening code should come before closing code");
112 }
113
114 @Test
115 @DisplayName("Should handle multiple swapped code pairs")
116 void testMultipleSwappedPairs() {
117 List<Code> fragCodes = new ArrayList<>();
118
119
120 Code open1 = new Code(TextFragment.TagType.OPENING, "tag1", "<tag1>");
121 open1.setId(1);
122 fragCodes.add(open1);
123 Code close1 = new Code(TextFragment.TagType.CLOSING, "tag1", "</tag1>");
124 close1.setId(1);
125 fragCodes.add(close1);
126
127
128 Code open2 = new Code(TextFragment.TagType.OPENING, "tag2", "<tag2>");
129 open2.setId(2);
130 fragCodes.add(open2);
131 Code close2 = new Code(TextFragment.TagType.CLOSING, "tag2", "</tag2>");
132 close2.setId(2);
133 fragCodes.add(close2);
134
135 char open1Char = TextFragment.toChar(0);
136 char close1Char = TextFragment.toChar(1);
137 char open2Char = TextFragment.toChar(2);
138 char close2Char = TextFragment.toChar(3);
139
140
141 String all = "Start "
142 + (char) TextFragment.MARKER_CLOSING + close1Char + " middle1 "
143 + (char) TextFragment.MARKER_OPENING + open1Char + " middle2 "
144 + (char) TextFragment.MARKER_CLOSING + close2Char + " middle3 "
145 + (char) TextFragment.MARKER_OPENING + open2Char + " end";
146
147 textFragment.setCodedText(all, fragCodes);
148
149
150 OkapiUtil.rearrangeCodes(codes, textFragment);
151
152
153 int open1Index = textFragment.getIndexForOpening(1);
154 int close1Index = textFragment.getIndexForClosing(1);
155 int open2Index = textFragment.getIndexForOpening(2);
156 int close2Index = textFragment.getIndexForClosing(2);
157
158 int o1Pos = textFragment.getCodePosition(open1Index);
159 int c1Pos = textFragment.getCodePosition(close1Index);
160 int o2Pos = textFragment.getCodePosition(open2Index);
161 int c2Pos = textFragment.getCodePosition(close2Index);
162
163 assertTrue(o1Pos < c1Pos, "Tag1 opening should come before closing");
164 assertTrue(o2Pos < c2Pos, "Tag2 opening should come before closing");
165 }
166
167 @Test
168 @DisplayName("Should handle nested codes correctly")
169 void testNestedCodes() {
170 Code outerOpen = textFragment.append(TextFragment.TagType.OPENING, "outer", "<outer>");
171 int outerId = outerOpen.getId();
172 textFragment.append("text1 ");
173
174 Code innerOpen = textFragment.append(TextFragment.TagType.OPENING, "inner", "<inner>");
175 int innerId = innerOpen.getId();
176 textFragment.append("nested");
177 textFragment.append(TextFragment.TagType.CLOSING, "inner", "</inner>", innerId);
178
179 textFragment.append(" text2");
180 textFragment.append(TextFragment.TagType.CLOSING, "outer", "</outer>", outerId);
181
182 String before = textFragment.getCodedText();
183
184 OkapiUtil.rearrangeCodes(codes, textFragment);
185
186
187 assertEquals(before, textFragment.getCodedText());
188 }
189
190 @Test
191 @DisplayName("Should stop after max iterations to prevent infinite loops")
192 void testMaxIterationsPreventsInfiniteLoop() {
193 List<Code> allCodes = new ArrayList<>();
194 StringBuilder sb = new StringBuilder();
195
196
197 for (int i = 0; i < 15; i++) {
198 Code open = new Code(TextFragment.TagType.OPENING, "tag" + i, "<tag" + i + ">");
199 open.setId(i + 1);
200 allCodes.add(open);
201
202 Code close = new Code(TextFragment.TagType.CLOSING, "tag" + i, "</tag" + i + ">");
203 close.setId(i + 1);
204 allCodes.add(close);
205
206 char closeChar = TextFragment.toChar(2 * i + 1);
207 char openChar = TextFragment.toChar(2 * i);
208
209
210 sb.append((char) TextFragment.MARKER_CLOSING).append(closeChar).append(" x ")
211 .append((char) TextFragment.MARKER_OPENING).append(openChar);
212 }
213
214 textFragment.setCodedText(sb.toString(), allCodes);
215
216
217 assertTimeout(Duration.ofSeconds(5),
218 () -> OkapiUtil.rearrangeCodes(codes, textFragment));
219 }
220
221 @Test
222 @DisplayName("Should preserve text content during rearrangement")
223 void testPreservesTextContent() {
224 textFragment.append("Before ");
225
226 List<Code> fragCodes = new ArrayList<>();
227 Code open = new Code(TextFragment.TagType.OPENING, "tag1", "<tag1>");
228 open.setId(1);
229 fragCodes.add(open);
230
231 Code close = new Code(TextFragment.TagType.CLOSING, "tag1", "</tag1>");
232 close.setId(1);
233 fragCodes.add(close);
234
235 char openChar = TextFragment.toChar(0);
236 char closeChar = TextFragment.toChar(1);
237
238 String current = textFragment.getCodedText();
239 String badPart = String.valueOf((char) TextFragment.MARKER_CLOSING) + closeChar
240 + "middle"
241 + String.valueOf((char) TextFragment.MARKER_OPENING) + openChar
242 + " after";
243
244 textFragment.setCodedText(current + badPart, fragCodes);
245
246
247 String beforeText = textFragment.getText();
248
249 OkapiUtil.rearrangeCodes(codes, textFragment);
250
251
252 String afterText = textFragment.getText();
253 assertEquals(beforeText, afterText, "Text content should be preserved");
254 }
255
256 @Test
257 @DisplayName("Should handle empty TextFragment")
258 void testEmptyTextFragment() {
259 textFragment.setCodedText("", codes);
260 assertDoesNotThrow(() -> OkapiUtil.rearrangeCodes(codes, textFragment));
261 assertEquals("", textFragment.getCodedText());
262 }
263
264 @Test
265 @DisplayName("Should handle single code without pair")
266 void testSingleCodeWithoutPair() {
267 textFragment.append("Text ");
268 textFragment.append(TextFragment.TagType.OPENING, "solo", "<solo>");
269 textFragment.append(" more text");
270
271 assertDoesNotThrow(() -> OkapiUtil.rearrangeCodes(codes, textFragment));
272 assertNotNull(textFragment.getCodedText());
273
274
275 String after = textFragment.getCodedText();
276 assertNotNull(after);
277 }
278
279 @Test
280 @DisplayName("Should handle adjacent swapped codes")
281 void testAdjacentSwappedCodes() {
282 List<Code> fragCodes = new ArrayList<>();
283
284 Code open = new Code(TextFragment.TagType.OPENING, "span", "<span>");
285 open.setId(1);
286 fragCodes.add(open);
287
288 Code close = new Code(TextFragment.TagType.CLOSING, "span", "</span>");
289 close.setId(1);
290 fragCodes.add(close);
291
292 char openChar = TextFragment.toChar(0);
293 char closeChar = TextFragment.toChar(1);
294
295
296 String coded = String.valueOf((char) TextFragment.MARKER_CLOSING) + closeChar
297 + String.valueOf((char) TextFragment.MARKER_OPENING) + openChar;
298
299 textFragment.setCodedText(coded, fragCodes);
300
301 OkapiUtil.rearrangeCodes(codes, textFragment);
302
303
304 int openingIndex = textFragment.getIndexForOpening(1);
305 int closingIndex = textFragment.getIndexForClosing(1);
306 int openingPos = textFragment.getCodePosition(openingIndex);
307 int closingPos = textFragment.getCodePosition(closingIndex);
308
309 assertTrue(openingPos < closingPos, "Opening should come before closing");
310 }
311 }