返回 CodeWhale
tests.rs
1 // Copyright 2012-2025 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use std::{
12 fs::File,
13 io::{BufRead, BufReader},
14 };
15
16 use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
17
18 macro_rules! assert_width {
19 ($s:expr, $nocjk:expr, $cjk:expr $(,)?) => {{
20 assert_eq!($s.width(), $nocjk, "{:?} has the wrong width", $s);
21 #[cfg(feature = "cjk")]
22 assert_eq!($s.width_cjk(), $cjk, "{:?} has the wrong width (CJK)", $s);
23 }};
24 }
25
26 #[test]
27 fn test_str() {
28 assert_width!("hello", 10, 10);
29 assert_width!("\0\0\0\x01\x01", 5, 5);
30 assert_width!("", 0, 0);
31 assert_width!("\u{2081}\u{2082}\u{2083}\u{2084}", 4, 8);
32 }
33
34 #[test]
35 fn test_emoji() {
36 assert_width!("👩", 2, 2); // Woman
37 assert_width!("🔬", 2, 2); // Microscope
38 assert_width!("👩‍🔬", 2, 2); // Woman scientist
39 }
40
41 // From README
42 #[test]
43 fn test_bad_devanagari() {
44 assert_eq!("क".width(), 1); // Devanagari letter Ka
45 assert_eq!("ष".width(), 1); // Devanagari letter Ssa
46 assert_eq!("क्ष".width(), 2); // Ka + Virama + Ssa
47 }
48
49 #[test]
50 fn test_char() {
51 assert_width!('h', Some(2), Some(2));
52 assert_width!('\x00', None, None);
53 assert_width!('\x01', None, None);
54 assert_width!('\u{2081}', Some(1), Some(2));
55 }
56
57 #[test]
58 fn test_char2() {
59 assert_width!('\x0A', None, None);
60 assert_width!('w', Some(1), Some(1));
61 assert_width!('h', Some(2), Some(2));
62 assert_width!('\u{AD}', Some(0), Some(0));
63 assert_width!('\u{1160}', Some(0), Some(0));
64 assert_width!('\u{a1}', Some(1), Some(2));
65 assert_width!('\u{300}', Some(0), Some(0));
66 }
67
68 #[test]
69 fn unicode_12() {
70 assert_width!('\u{1F971}', Some(2), Some(2));
71 }
72
73 #[test]
74 fn test_default_ignorable() {
75 assert_width!('\u{1160}', Some(0), Some(0));
76 assert_width!('\u{3164}', Some(0), Some(0));
77 assert_width!('\u{FFA0}', Some(0), Some(0));
78 assert_width!('\u{E0000}', Some(0), Some(0));
79 }
80
81 #[test]
82 fn test_ambiguous() {
83 assert_width!("\u{B7}", 1, 2);
84 assert_width!("\u{0387}", 1, 2);
85 assert_width!("\u{A8}", 1, 1);
86 assert_width!("\u{02C9}", 1, 1);
87 }
88
89 #[test]
90 fn test_jamo() {
91 assert_width!('\u{1100}', Some(2), Some(2));
92 assert_width!('\u{A97C}', Some(2), Some(2));
93 // Special case: U+115F HANGUL CHOSEONG FILLER
94 assert_width!('\u{115F}', Some(2), Some(2));
95 assert_width!('\u{1160}', Some(0), Some(0));
96 assert_width!('\u{D7C6}', Some(0), Some(0));
97 assert_width!('\u{11A8}', Some(0), Some(0));
98 assert_width!('\u{D7FB}', Some(0), Some(0));
99 }
100
101 #[test]
102 fn test_prepended_concatenation_marks() {
103 for c in [
104 '\u{0600}',
105 '\u{0601}',
106 '\u{0602}',
107 '\u{0603}',
108 '\u{0604}',
109 '\u{06DD}',
110 '\u{110BD}',
111 '\u{110CD}',
112 ] {
113 assert_width!(c, Some(1), Some(1));
114 }
115
116 for c in ['\u{0605}', '\u{070F}', '\u{0890}', '\u{0891}', '\u{08E2}'] {
117 assert_width!(c, Some(0), Some(0));
118 }
119 }
120
121 #[test]
122 fn test_gcb_prepend() {
123 assert_width!("ൎഉ", 1, 1);
124 assert_width!("\u{11A89}", 0, 0);
125 }
126
127 #[test]
128 fn test_interlinear_annotation_chars() {
129 assert_width!('\u{FFF9}', Some(1), Some(1));
130 assert_width!('\u{FFFA}', Some(1), Some(1));
131 assert_width!('\u{FFFB}', Some(1), Some(1));
132 }
133
134 #[test]
135 fn test_hieroglyph_format_controls() {
136 assert_width!('\u{13430}', Some(1), Some(1));
137 assert_width!('\u{13436}', Some(1), Some(1));
138 assert_width!('\u{1343C}', Some(1), Some(1));
139 }
140
141 #[test]
142 fn test_marks() {
143 // Nonspacing marks have 0 width
144 assert_width!('\u{0301}', Some(0), Some(0));
145 // Enclosing marks have 0 width
146 assert_width!('\u{20DD}', Some(0), Some(0));
147 // Some spacing marks have width 1
148 assert_width!('\u{09CB}', Some(1), Some(1));
149 // But others have width 0
150 assert_width!('\u{09BE}', Some(0), Some(0));
151 }
152
153 #[test]
154 fn test_devanagari_caret() {
155 assert_width!('\u{A8FA}', Some(0), Some(0));
156 }
157
158 #[test]
159 fn test_solidus_overlay() {
160 assert_width!("<\u{338}", 1, 2);
161 assert_width!("=\u{338}", 1, 2);
162 assert_width!(">\u{338}", 1, 2);
163 assert_width!("=\u{301}\u{338}", 1, 2);
164 assert_width!("=\u{338}\u{301}", 1, 2);
165 assert_width!("=\u{FE0F}\u{338}", 1, 2);
166 assert_width!("#\u{FE0F}\u{338}", 2, 2);
167 assert_width!("#\u{338}\u{FE0F}", 1, 1);
168
169 assert_width!("\u{06B8}\u{338}\u{0627}", 1, 1);
170 assert_width!("\u{06B8}\u{338}\u{FE0E}\u{0627}", 1, 1);
171 assert_width!("\u{06B8}\u{338}\u{FE0F}\u{0627}", 1, 1);
172 assert_width!("\u{06B8}\u{FE0E}\u{338}\u{0627}", 1, 1);
173 assert_width!("\u{06B8}\u{FE0F}\u{338}\u{0627}", 1, 1);
174
175 assert_width!("=\u{338}\u{0627}", 2, 3);
176 }
177
178 #[test]
179 fn test_emoji_presentation() {
180 assert_width!('\u{0023}', Some(1), Some(1));
181 assert_width!('\u{FE0F}', Some(0), Some(0));
182 assert_width!("\u{0023}\u{FE0F}", 2, 2);
183 assert_width!("a\u{0023}\u{FE0F}a", 4, 4);
184 assert_width!("\u{0023}a\u{FE0F}", 2, 2);
185 assert_width!("a\u{FE0F}", 1, 1);
186 assert_width!("\u{0023}\u{0023}\u{FE0F}a", 4, 4);
187 assert_width!("\u{002A}\u{FE0F}", 2, 2);
188 assert_width!("\u{23F9}\u{FE0F}", 2, 2);
189 assert_width!("\u{24C2}\u{FE0F}", 2, 2);
190 assert_width!("\u{1F6F3}\u{FE0F}", 2, 2);
191 assert_width!("\u{1F700}\u{FE0F}", 1, 1);
192 assert_width!("\u{002A}\u{301}\u{FE0F}", 1, 1);
193 assert_width!("\u{002A}\u{200D}\u{FE0F}", 1, 1);
194 assert_width!("\u{002A}\u{FE0E}\u{FE0F}", 1, 1);
195 }
196
197 #[test]
198 fn test_text_presentation() {
199 assert_width!('\u{FE0E}', Some(0), Some(0));
200 assert_width!('\u{2648}', Some(2), Some(2));
201 assert_width!("\u{2648}\u{FE0E}", 1, 2);
202 assert_width!("\u{2648}\u{FE0E}\u{FE0F}", 1, 2);
203 assert_width!("\u{2648}\u{FE0F}\u{FE0E}", 2, 2);
204 assert_width!("\u{1F21A}\u{FE0E}", 2, 2);
205 assert_width!("\u{0301}\u{FE0E}", 0, 0);
206 assert_width!("a\u{FE0E}", 1, 1);
207 assert_width!("𘀀\u{FE0E}", 2, 2);
208 assert_width!("\u{2648}\u{0301}\u{FE0E}", 2, 2);
209 assert_width!("\u{2648}\u{200D}\u{FE0E}", 2, 2);
210 }
211
212 #[test]
213 fn test_control_line_break() {
214 assert_width!('\u{2028}', Some(1), Some(1));
215 assert_width!('\u{2029}', Some(1), Some(1));
216 assert_width!('\r', None, None);
217 assert_width!('\n', None, None);
218 assert_width!("\r", 1, 1);
219 assert_width!("\n", 1, 1);
220 assert_width!("\r\n", 1, 1);
221 assert_width!("\0", 1, 1);
222 assert_width!("1\t2\r\n3\u{85}4", 7, 7);
223 assert_width!("\r\u{FE0F}\n", 2, 2);
224 assert_width!("\r\u{200D}\n", 2, 2);
225 }
226
227 #[test]
228 fn char_str_consistent() {
229 let mut s = String::with_capacity(4);
230 for c in '\0'..=char::MAX {
231 s.clear();
232 s.push(c);
233 assert_eq!(c.width().unwrap_or(1), s.width());
234 #[cfg(feature = "cjk")]
235 assert_eq!(c.width_cjk().unwrap_or(1), s.width_cjk());
236 }
237 }
238
239 #[test]
240 fn test_lisu_tones() {
241 for c in '\u{A4F8}'..='\u{A4FD}' {
242 assert_width!(c, Some(1), Some(1));
243 assert_width!(String::from(c), 1, 1);
244 }
245 for c1 in '\u{A4F8}'..='\u{A4FD}' {
246 for c2 in '\u{A4F8}'..='\u{A4FD}' {
247 let mut s = String::with_capacity(8);
248 s.push(c1);
249 s.push(c2);
250 match (c1, c2) {
251 ('\u{A4F8}'..='\u{A4FB}', '\u{A4FC}'..='\u{A4FD}') => assert_width!(s, 1, 1),
252 _ => assert_width!(s, 2, 2),
253 }
254 }
255 }
256
257 assert_width!("ꓪꓹ", 2, 2);
258 assert_width!("ꓪꓹꓼ", 2, 2);
259 assert_width!("ꓪꓹ\u{FE0F}ꓼ", 2, 2);
260 assert_width!("ꓪꓹ\u{200D}ꓼ", 2, 2);
261 assert_width!("ꓪꓹꓼ\u{FE0F}", 2, 2);
262 assert_width!("ꓪꓹ\u{0301}ꓼ", 3, 3);
263 assert_width!("ꓪꓹꓹ", 3, 3);
264 assert_width!("ꓪꓼꓼ", 3, 3);
265 }
266
267 #[test]
268 fn test_hebrew_alef_lamed() {
269 assert_width!("\u{05D0}", 1, 1);
270 assert_width!("\u{05DC}", 1, 1);
271 assert_width!("\u{05D0}\u{05DC}", 2, 2);
272 assert_width!("\u{05D0}\u{200D}\u{05DC}", 1, 1);
273 assert_width!(
274 "\u{05D0}\u{200D}\u{200D}\u{200D}\u{200D}\u{200D}\u{200D}\u{200D}\u{05DC}",
275 1,
276 1,
277 );
278 assert_width!("\u{05D0}\u{05D0}\u{200D}\u{05DC}", 2, 2);
279 assert_width!(
280 "\u{05D0}\u{05D0}\u{200D}\u{200D}\u{200D}\u{200D}\u{200D}\u{200D}\u{05DC}",
281 2,
282 2,
283 );
284 assert_width!("\u{05D0}\u{FE0F}\u{200D}\u{FE0F}\u{05DC}\u{FE0F}", 1, 1);
285 assert_width!("\u{05D0}\u{FE0E}\u{200D}\u{FE0E}\u{05DC}\u{FE0E}", 1, 1);
286 }
287
288 #[test]
289 fn test_arabic_lam_alef() {
290 assert_width!("\u{0644}", 1, 1);
291 assert_width!("\u{06B8}", 1, 1);
292
293 assert_width!("\u{0623}", 1, 1);
294 assert_width!("\u{0627}", 1, 1);
295
296 assert_width!("\u{0644}\u{0623}", 1, 1);
297 assert_width!("\u{0644}\u{0627}", 1, 1);
298 assert_width!("\u{06B8}\u{0623}", 1, 1);
299 assert_width!("\u{06B8}\u{0627}", 1, 1);
300
301 assert_width!("\u{0644}\u{065F}\u{065E}\u{0623}", 1, 1);
302 assert_width!("\u{0644}\u{065F}\u{065E}\u{0627}", 1, 1);
303 assert_width!("\u{06B8}\u{065F}\u{065E}\u{0623}", 1, 1);
304 assert_width!("\u{06B8}\u{065F}\u{065E}\u{0627}", 1, 1);
305
306 assert_width!("\u{06B8}\u{FE0E}\u{0627}", 1, 1);
307 assert_width!("\u{06B8}\u{FE0F}\u{0627}", 1, 1);
308 assert_width!("\u{06B8}\u{17B5}\u{0627}", 1, 1);
309
310 assert_width!("\u{0644}\u{0644}\u{0623}", 2, 2);
311 assert_width!("\u{0644}\u{0644}\u{0627}", 2, 2);
312 assert_width!("\u{06B8}\u{06B8}\u{0623}", 2, 2);
313 assert_width!("\u{06B8}\u{06B8}\u{0627}", 2, 2);
314
315 assert_width!("\u{0644}\u{200D}\u{0623}", 2, 2);
316 assert_width!("\u{0644}\u{200D}\u{0627}", 2, 2);
317 assert_width!("\u{06B8}\u{200D}\u{0623}", 2, 2);
318 assert_width!("\u{06B8}\u{200D}\u{0627}", 2, 2);
319
320 assert_width!("\u{0644}\u{1E94B}\u{0623}", 3, 3);
321 assert_width!("\u{0644}\u{1E94B}\u{0627}", 3, 3);
322 assert_width!("\u{06B8}\u{1E94B}\u{0623}", 3, 3);
323 assert_width!("\u{06B8}\u{1E94B}\u{0627}", 3, 3);
324 }
325
326 #[test]
327 fn test_buginese_a_i_ya() {
328 assert_width!("\u{1A15}", 1, 1);
329 assert_width!("\u{1A17}", 0, 0);
330 assert_width!("\u{1A10}", 1, 1);
331
332 assert_width!("\u{1A15}\u{1A17}\u{200D}\u{1A10}", 1, 1);
333 assert_width!(
334 "\u{1A15}\u{1A17}\u{200D}\u{200D}\u{200D}\u{200D}\u{1A10}",
335 1,
336 1,
337 );
338 assert_width!("\u{1A15}\u{1A17}\u{200D}\u{338}", 1, 1);
339 assert_width!("\u{1A15}\u{FE0E}\u{1A17}\u{200D}", 1, 1);
340 assert_width!("\u{1A15}\u{FE0F}\u{1A17}\u{200D}", 1, 1);
341 assert_width!("\u{1A15}\u{1A17}\u{FE0E}\u{200D}", 1, 1);
342 assert_width!("\u{1A15}\u{1A17}\u{FE0F}\u{200D}", 1, 1);
343 assert_width!("\u{1A15}\u{1A17}\u{200D}\u{FE0E}", 1, 1);
344 assert_width!("\u{1A15}\u{1A17}\u{200D}\u{FE0F}", 1, 1);
345 assert_width!(
346 "\u{1A15}\u{17B5}\u{200D}\u{FE0E}\u{1A17}\u{200D}\u{FE0F}\u{200D}\u{FE0F}",
347 1,
348 1,
349 );
350
351 assert_width!("\u{1A15}\u{1A15}\u{1A17}\u{200D}\u{1A10}", 2, 2);
352 assert_width!(
353 "\u{1A15}\u{1A15}\u{1A17}\u{200D}\u{200D}\u{200D}\u{200D}\u{1A10}",
354 2,
355 2,
356 );
357
358 assert_width!("\u{1A15}\u{1A17}\u{1A10}", 2, 2);
359 assert_width!("\u{1A15}\u{200D}\u{1A10}", 2, 2);
360 assert_width!("\u{1A15}\u{1A10}", 2, 2);
361 assert_width!("\u{1A15}\u{1A17}\u{1A17}\u{200D}\u{1A10}", 2, 2);
362 assert_width!("\u{1A15}\u{1A17}\u{338}\u{200D}\u{1A10}", 2, 2);
363 }
364
365 #[test]
366 fn test_tifinagh_biconsonants() {
367 assert_width!("\u{2D4F}", 1, 1);
368 assert_width!("\u{2D3E}", 1, 1);
369 assert_width!("\u{2D7F}", 1, 1);
370
371 assert_width!("\u{2D4F}\u{200D}\u{2D3E}", 1, 1);
372 assert_width!("\u{2D4F}\u{2D7F}\u{2D3E}", 1, 1);
373 assert_width!("\u{2D4F}\u{200D}\u{2D3E}", 1, 1);
374 assert_width!(
375 "\u{2D4F}\u{FE0F}\u{200D}\u{2D7F}\u{FE0E}\u{200D}\u{17B5}\u{2D3E}",
376 1,
377 1,
378 );
379
380 assert_width!("\u{2D4F}\u{301}\u{2D7F}\u{2D3E}", 3, 3);
381 assert_width!("\u{2D4F}\u{301}\u{200D}\u{2D3E}", 2, 2);
382 assert_width!("\u{2D4F}\u{2D3E}", 2, 2);
383 assert_width!("\u{2D4F}\u{2D7F}\u{2D7F}\u{2D3E}", 4, 4);
384 assert_width!("\u{2D7F}\u{2D3E}", 2, 2);
385 assert_width!("\u{2D7F}\u{2D7F}\u{2D66}", 3, 3);
386 assert_width!("\u{2D66}\u{2D7F}\u{2D3E}", 3, 3);
387 }
388
389 #[test]
390 fn test_old_turkic_ligature() {
391 assert_width!("\u{10C32}", 1, 1);
392 assert_width!("\u{10C03}", 1, 1);
393 assert_width!("\u{10C32}\u{10C03}", 2, 2);
394
395 assert_width!("\u{10C32}\u{200D}\u{10C03}", 1, 1);
396 assert_width!("\u{10C32}\u{FE0F}\u{200D}\u{FE0E}\u{10C03}", 1, 1);
397
398 assert_width!("\u{10C32}\u{2D7F}\u{10C03}", 3, 3);
399 assert_width!("\u{10C32}\u{0301}\u{200D}\u{10C03}", 2, 2);
400 assert_width!("\u{10C03}\u{200D}\u{10C32}", 2, 2);
401 assert_width!("\u{200D}\u{10C32}", 1, 1);
402 }
403
404 #[test]
405 fn test_khmer_coeng() {
406 assert_width!("ល", 1, 1);
407 assert_width!("ង", 1, 1);
408 assert_width!("លង", 2, 2);
409 assert_width!("ល្ង", 1, 1);
410
411 for c in '\0'..=char::MAX {
412 if matches!(
413 c,
414 '\u{1780}'..='\u{1782}' | '\u{1784}'..='\u{1787}'
415 | '\u{1789}'..='\u{178C}' | '\u{178E}'..='\u{1793}'
416 | '\u{1795}'..='\u{1798}' | '\u{179B}'..='\u{179D}'
417 | '\u{17A0}' | '\u{17A2}' | '\u{17A7}'
418 | '\u{17AB}'..='\u{17AC}' | '\u{17AF}'
419 ) {
420 assert_width!(format!("\u{17D2}{c}"), 0, 0);
421 assert_width!(format!("\u{17D2}\u{200D}\u{200D}{c}"), 0, 0);
422 } else {
423 assert_width!(
424 format!("\u{17D2}{c}"),
425 c.width().unwrap_or(1),
426 c.width_cjk().unwrap_or(1)
427 );
428 }
429 }
430 }
431
432 #[test]
433 fn test_khmer_qaa() {
434 assert_width!("\u{17A4}", 2, 2);
435 assert_width!("\u{17A2}\u{17A6}", 2, 2);
436 }
437
438 #[test]
439 fn test_khmer_sign_beyyal() {
440 assert_width!("\u{17D8}", 3, 3);
441 assert_width!("\u{17D4}\u{179B}\u{17D4}", 3, 3);
442 }
443
444 #[test]
445 fn test_emoji_modifier() {
446 assert_width!("\u{1F46A}", 2, 2);
447 assert_width!("\u{1F3FB}", 2, 2);
448 assert_width!("\u{1F46A}\u{1F3FB}", 2, 2);
449 assert_width!("\u{1F46A}\u{200D}\u{200D}\u{1F3FB}", 4, 4);
450 }
451
452 #[test]
453 fn test_emoji_zwj() {
454 assert_width!("🧑‍🤝‍🧑", 2, 2);
455
456 assert_width!("🇮🇱🕊️🇵🇸", 6, 6);
457 assert_width!("🇵🇸\u{200D}🕊️\u{200D}🇮🇱", 2, 2);
458 assert_width!("🇮🇱\u{200D}🕊️\u{200D}\u{200D}🇵🇸", 4, 4);
459 assert_width!("🇵🇸\u{200D}\u{200D}🕊️\u{200D}🇮🇱", 4, 4);
460
461 assert_width!("🇦🇦\u{200D}🇦🇦", 2, 2);
462 assert_width!("🇦🇦\u{200D}🇦🇦🇦", 3, 3);
463 assert_width!("🇦🇦\u{200D}🇦🇦🇦", 3, 3);
464
465 assert_width!("🇦🇦\u{200D}\u{200D}🇦🇦", 4, 4);
466 assert_width!("🇦🇦\u{200D}🇦\u{200D}🇦🇦", 5, 5);
467 assert_width!("🇦🇦\u{200D}🇦🇦\u{200D}🇦🇦", 2, 2);
468 assert_width!("🇦🇦\u{200D}🇦🇦🇦\u{200D}🇦🇦", 5, 5);
469 assert_width!("🇦🇦\u{200D}🇦🇦🇦🇦\u{200D}🇦🇦", 4, 4);
470 assert_width!("🇦🇦\u{200D}🇦🇦🇦🇦🇦\u{200D}🇦🇦", 7, 7);
471 assert_width!("🇦🇦\u{200D}🇦🇦🇦🇦🇦🇦\u{200D}🇦🇦", 6, 6);
472 assert_width!("🇦🇦\u{200D}🇦🇦🇦🇦🇦🇦🇦\u{200D}🇦🇦", 9, 9);
473
474 assert_width!("🏴󠁧󠁢󠁷󠁬󠁳󠁿", 2, 2);
475 assert_width!("🏴󠁧󠁢󠁥󠁮󠁧󠁿\u{200D}🏴󠁧󠁢󠁳󠁣󠁴󠁿\u{200D}🏴󠁧󠁢󠁷󠁬󠁳󠁿", 2, 2);
476
477 assert_width!("🇦👪\u{200D}🏿", 3, 3);
478 assert_width!("🇦🏿\u{200D}🏿", 3, 3);
479
480 assert_width!('🏴', Some(2), Some(2));
481 assert_width!("\u{E0031}", 0, 0);
482 assert_width!("\u{E0063}", 0, 0);
483 assert_width!("\u{E007F}", 0, 0);
484 assert_width!("🏴\u{200D}Ⓜ️", 2, 2);
485 assert_width!("🏴\u{E0031}\u{200D}Ⓜ️", 4, 4);
486 assert_width!("🏴\u{E0063}\u{200D}Ⓜ️", 4, 4);
487 assert_width!("🏴\u{E007F}\u{200D}Ⓜ️", 4, 4);
488 assert_width!("🏴\u{E0031}\u{E007F}\u{200D}Ⓜ️", 4, 4);
489 assert_width!("🏴\u{E0031}\u{E0031}\u{E007F}\u{200D}Ⓜ️", 4, 4);
490 assert_width!("🏴\u{E0031}\u{E0031}\u{E0031}\u{E007F}\u{200D}Ⓜ️", 2, 2);
491 assert_width!(
492 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0031}\u{E007F}\u{200D}Ⓜ️",
493 4,
494 4,
495 );
496 assert_width!(
497 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
498 2,
499 2,
500 );
501 assert_width!(
502 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
503 2,
504 2,
505 );
506 assert_width!(
507 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
508 2,
509 2,
510 );
511 assert_width!(
512 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
513 2,
514 2,
515 );
516 assert_width!(
517 "🏴\u{E0031}\u{E0031}\u{E0031}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
518 4,
519 4,
520 );
521 assert_width!("🏴\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️", 4, 4);
522 assert_width!("🏴\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️", 2, 2);
523 assert_width!(
524 "🏴\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
525 2,
526 2,
527 );
528 assert_width!(
529 "🏴\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
530 2,
531 2,
532 );
533 assert_width!(
534 "🏴\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
535 2,
536 2,
537 );
538 assert_width!(
539 "🏴\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E0063}\u{E007F}\u{200D}Ⓜ️",
540 4,
541 4,
542 );
543
544 assert_width!("a\u{200D}🏴󠁧󠁢󠁷󠁬󠁳󠁿", 3, 3);
545 assert_width!("👪\u{200D}a", 3, 3);
546 assert_width!("a\u{200D}a", 2, 2);
547
548 assert_width!("*\u{FE0F}", 2, 2);
549 assert_width!("*\u{20E3}", 1, 1);
550 assert_width!("*️⃣", 2, 2);
551 assert_width!("*\u{FE0F}", 2, 2);
552 assert_width!("*\u{20E3}\u{FE0F}", 1, 1);
553 assert_width!("*️⃣\u{200D}👪", 2, 2);
554 assert_width!("*\u{20E3}\u{FE0F}\u{200D}👪", 3, 3);
555 assert_width!("*\u{20E3}\u{200D}👪", 3, 3);
556 assert_width!("*\u{FE0F}\u{200D}👪", 2, 2);
557 assert_width!("*️⃣\u{20E3}\u{200D}👪", 4, 4);
558 assert_width!("*\u{FE0F}\u{FE0F}\u{20E3}\u{200D}👪", 4, 4);
559
560 assert_width!(
561 "🇦👪\u{200D}🏿\u{200D}👪🏻\u{200D}Ⓜ️\u{200D}*\u{FE0F}\u{200D}🇦🇦\u{200D}🏴󠁧󠁢󠁷󠁬󠁳󠁿\u{200D}👪",
562 3,
563 3,
564 );
565 }
566
567 #[test]
568 fn emoji_test_file() {
569 let norm_file = BufReader::new(
570 File::open("tests/emoji-test.txt")
571 .expect("run `unicode.py` first to download `emoji-test.txt`"),
572 );
573 for line in norm_file.lines() {
574 let line = line.unwrap();
575 if line.is_empty() || line.starts_with('#') {
576 continue;
577 }
578
579 let (cps, status) = line.split_once(';').unwrap();
580 let status = status.trim();
581 if status.starts_with("fully-qualified") || status.starts_with("component") {
582 let emoji: String = cps
583 .trim()
584 .split(' ')
585 .map(|s| char::try_from(u32::from_str_radix(s, 16).unwrap()).unwrap())
586 .collect();
587 dbg!(&emoji);
588 assert_width!(emoji, 2, 2);
589 }
590 }
591 }
592
593 #[test]
594 fn ambiguous_line_break() {
595 assert_width!("\u{24EA}", 1, 2);
596 assert_width!("\u{2616}", 1, 2);
597 assert_width!("\u{2780}", 1, 2);
598 }
599
600 #[test]
601 fn test_vs1_vs2_vs3() {
602 assert_width!('\u{FE00}', Some(0), Some(0));
603 assert_width!('\u{FE01}', Some(0), Some(0));
604 assert_width!('\u{FE02}', Some(0), Some(0));
605 for c in '\0'..=char::MAX {
606 if matches!(c, '\u{2018}' | '\u{2019}' | '\u{201C}' | '\u{201D}') {
607 assert_width!(c, Some(1), Some(2));
608 assert_width!(format!("{c}\u{FE00}"), 1, 1);
609 assert_width!(format!("{c}\u{FE00}\u{FE01}"), 1, 1);
610 assert_width!(format!("{c}\u{FE01}"), 2, 2);
611 assert_width!(format!("{c}\u{FE01}\u{FE00}"), 2, 2);
612 assert_width!(format!("{c}\u{FE02}"), 1, 1);
613 assert_width!(format!("{c}\u{FE02}\u{FE01}"), 1, 1);
614 } else {
615 assert_eq!(
616 format!("{c}\u{FE00}").width(),
617 c.width().unwrap_or(1),
618 "{c:?}"
619 );
620 #[cfg(feature = "cjk")]
621 assert_eq!(
622 format!("{c}\u{FE00}").width_cjk(),
623 c.width_cjk().unwrap_or(1),
624 "{c:?}"
625 );
626 assert_eq!(
627 format!("{c}\u{FE01}").width(),
628 c.width().unwrap_or(1),
629 "{c:?}"
630 );
631 #[cfg(feature = "cjk")]
632 assert_eq!(
633 format!("{c}\u{FE01}").width_cjk(),
634 c.width_cjk().unwrap_or(1),
635 "{c:?}"
636 );
637 assert_eq!(
638 format!("{c}\u{FE02}").width(),
639 c.width().unwrap_or(1),
640 "{c:?}"
641 );
642 #[cfg(feature = "cjk")]
643 assert_eq!(
644 format!("{c}\u{FE02}").width_cjk(),
645 c.width_cjk().unwrap_or(1),
646 "{c:?}"
647 );
648 }
649 }
650 }
651
652 // Test traits are unsealed
653
654 #[cfg(feature = "cjk")]
655 #[allow(dead_code)]
656 struct Foo;
657
658 #[cfg(feature = "cjk")]
659 impl UnicodeWidthChar for Foo {
660 fn width(self) -> Option<usize> {
661 Some(0)
662 }
663
664 fn width_cjk(self) -> Option<usize> {
665 Some(0)
666 }
667 }
668
669 #[cfg(feature = "cjk")]
670 impl UnicodeWidthStr for Foo {
671 fn width(&self) -> usize {
672 0
673 }
674
675 fn width_cjk(&self) -> usize {
676 0
677 }
678 }
679
679 lines RUST