1
2
3
4
5
6
7
8
9
10
11
12
13
14 import unittest
15 from datetime import timedelta
16
17 from trac.test import locale_en
18 from trac.ticket.query import QueryModule
19 from trac.ticket.report import ReportModule
20 from trac.ticket.roadmap import RoadmapModule
21 from trac.ticket.model import Milestone, Ticket
22 from trac.util.datefmt import (datetime_now, format_datetime, pretty_timedelta,
23 utc)
24 from trac.wiki.tests import formatter
25
26
27 TICKET_TEST_CASES = u"""
28 ============================== ticket: link resolver
29 bug:1
30 issue:1
31 ticket:1
32 ticket:12
33 ticket:abc
34 ------------------------------
35 <p>
36 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">bug:1</a>
37 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">issue:1</a>
38 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">ticket:1</a>
39 <a class="missing ticket">ticket:12</a>
40 <a class="missing ticket">ticket:abc</a>
41 </p>
42 ------------------------------
43 ============================== ticket: link resolver + arguments
44 bug:1#comment:3
45 issue:1#comment:3
46 ticket:1#comment:3
47 ticket:1?format=csv
48 ------------------------------
49 <p>
50 <a class="new ticket" href="/ticket/1#comment:3" title="#1: This is the summary (new)">bug:1#comment:3</a>
51 <a class="new ticket" href="/ticket/1#comment:3" title="#1: This is the summary (new)">issue:1#comment:3</a>
52 <a class="new ticket" href="/ticket/1#comment:3" title="#1: This is the summary (new)">ticket:1#comment:3</a>
53 <a class="new ticket" href="/ticket/1?format=csv" title="#1: This is the summary (new)">ticket:1?format=csv</a>
54 </p>
55 ------------------------------
56 ============================== ticket: link resolver with ranges
57 bug:12-14,33
58 issue:12-14,33
59 ticket:12-14,33
60 ticket:12,33?order=created
61 ------------------------------
62 <p>
63 <a href="/query?id=12-14%2C33" title="Tickets 12-14, 33">bug:12-14,33</a>
64 <a href="/query?id=12-14%2C33" title="Tickets 12-14, 33">issue:12-14,33</a>
65 <a href="/query?id=12-14%2C33" title="Tickets 12-14, 33">ticket:12-14,33</a>
66 <a href="/query?id=12%2C33&order=created" title="Tickets 12, 33">ticket:12,33?order=created</a>
67 </p>
68 ------------------------------
69 ============================== ticket link shorthand form
70 #1, #2
71 #12, #abc
72 ------------------------------
73 <p>
74 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">#1</a>, <a class="missing ticket">#2</a>
75 <a class="missing ticket">#12</a>, #abc
76 </p>
77 ------------------------------
78 ============================== ticket link shorthand form with ranges
79 #1-5,42
80 #1,3,5,7
81 ------------------------------
82 <p>
83 <a href="/query?id=1-5%2C42" title="Tickets 1-5, 42">#1-5,42</a>
84 <a href="/query?id=1%2C3%2C5%2C7" title="Tickets 1, 3, 5, 7">#1,3,5,7</a>
85 </p>
86 ------------------------------
87 ============================== ticket link shorthand form with long ranges (#10111 regression)
88 #1-123456789012345678901234
89 ------------------------------
90 <p>
91 <a href="/query?id=1-123456789012345678901234" title="Tickets 1-123456789012345678901234">#1-123456789012345678901234</a>
92 </p>
93 ------------------------------
94 ============================== escaping the above
95 !#1
96 ------------------------------
97 <p>
98 #1
99 </p>
100 ------------------------------
101 #1
102 ============================== InterTrac for tickets
103 trac:ticket:2041
104 [trac:ticket:2041 Trac #2041]
105 #T2041
106 #trac2041
107 ------------------------------
108 <p>
109 <a class="ext-link" href="https://trac.edgewall.org/intertrac/ticket%3A2041" title="ticket:2041 in The Trac Project"><span class="icon"></span>trac:ticket:2041</a>
110 <a class="ext-link" href="https://trac.edgewall.org/intertrac/ticket%3A2041" title="ticket:2041 in The Trac Project"><span class="icon"></span>Trac #2041</a>
111 <a class="ext-link" href="https://trac.edgewall.org/intertrac/ticket%3A2041" title="ticket:2041 in The Trac Project"><span class="icon"></span>#T2041</a>
112 <a class="ext-link" href="https://trac.edgewall.org/intertrac/ticket%3A2041" title="ticket:2041 in The Trac Project"><span class="icon"></span>#trac2041</a>
113 </p>
114 ------------------------------
115 ============================== Ticket InterTrac shorthands
116 T:#2041
117 trac:#2041
118 ------------------------------
119 <p>
120 <a class="ext-link" href="https://trac.edgewall.org/intertrac/%232041" title="#2041 in The Trac Project"><span class="icon"></span>T:#2041</a>
121 <a class="ext-link" href="https://trac.edgewall.org/intertrac/%232041" title="#2041 in The Trac Project"><span class="icon"></span>trac:#2041</a>
122 </p>
123 ------------------------------
124 ============================== ticket syntax with unicode digits
125 #⁴²
126 #1-⁵,42
127 #1,³,5,7
128 #T²⁰⁴¹
129 #trac²⁰⁴¹
130 ------------------------------
131 <p>
132 #⁴²
133 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">#1</a>-⁵,42
134 <a class="new ticket" href="/ticket/1" title="#1: This is the summary (new)">#1</a>,³,5,7
135 #T²⁰⁴¹
136 #trac²⁰⁴¹
137 </p>
138 ------------------------------
139 """
140
141
152
158
159
160
161 REPORT_TEST_CASES = u"""
162 ============================== report link shorthand form
163 {1}, {2}
164 {12}, {abc}
165 ------------------------------
166 <p>
167 <a class="report" href="/report/1">{1}</a>, <a class="report" href="/report/2">{2}</a>
168 <a class="missing report" title="report does not exist">{12}</a>, {abc}
169 </p>
170 ------------------------------
171 ============================== escaping the above
172 !{1}
173 ------------------------------
174 <p>
175 {1}
176 </p>
177 ------------------------------
178 {1}
179 ============================== ticket shorthands, not numerical HTML entities
180  
181 ------------------------------
182 <p>
183 &#1; &#23;
184 </p>
185 ------------------------------
186 &#1; &#23;
187 ============================== report link with non-digits
188 report:blah
189 ------------------------------
190 <p>
191 <a class="missing report" title="report does not exist">report:blah</a>
192 </p>
193 ------------------------------
194 <a class="missing report" title="report does not exist">report:blah</a>
195 ============================== InterTrac for reports
196 trac:report:1
197 [trac:report:1 Trac r1]
198 {T1}
199 {trac1}
200 {trac 1}
201 ------------------------------
202 <p>
203 <a class="ext-link" href="https://trac.edgewall.org/intertrac/report%3A1" title="report:1 in The Trac Project"><span class="icon"></span>trac:report:1</a>
204 <a class="ext-link" href="https://trac.edgewall.org/intertrac/report%3A1" title="report:1 in The Trac Project"><span class="icon"></span>Trac r1</a>
205 <a class="ext-link" href="https://trac.edgewall.org/intertrac/report%3A1" title="report:1 in The Trac Project"><span class="icon"></span>{T1}</a>
206 <a class="ext-link" href="https://trac.edgewall.org/intertrac/report%3A1" title="report:1 in The Trac Project"><span class="icon"></span>{trac1}</a>
207 <a class="ext-link" href="https://trac.edgewall.org/intertrac/report%3A1" title="report:1 in The Trac Project"><span class="icon"></span>{trac 1}</a>
208 </p>
209 ------------------------------
210 ============================== report syntax with unicode digits
211 {⁴²} !{⁴²}
212 {T⁴²}
213 {trac⁴²}
214 ------------------------------
215 <p>
216 {⁴²} !{⁴²}
217 {T⁴²}
218 {trac⁴²}
219 </p>
220 ------------------------------
221 """
222
224 def create_report(tc, id):
225 tc.env.db_transaction("""
226 INSERT INTO report (id,title,query,description)
227 VALUES (%s,%s,'SELECT 1','')""", (id, 'Report %s' % id))
228 create_report(tc, 1)
229 create_report(tc, 2)
230
231
232 dt_past = datetime_now(utc) - timedelta(days=1)
233 dt_future = datetime_now(utc) + timedelta(days=1)
234
235
236 MILESTONE_TEST_CASES = u"""
237 ============================== milestone: link resolver
238 milestone:foo
239 [milestone:boo Milestone Boo]
240 [milestone:roo Milestone Roo]
241 [milestone:woo Milestone Woo]
242 [milestone:zoo Milestone Zoo]
243 ------------------------------
244 <p>
245 <a class="missing milestone" href="/milestone/foo" rel="nofollow">milestone:foo</a>
246 <a class="milestone" href="/milestone/boo" title="No date set">Milestone Boo</a>
247 <a class="closed milestone" href="/milestone/roo" title="Completed %(dt_past)s ago (%(datestr_past)s)">Milestone Roo</a>
248 <a class="milestone" href="/milestone/woo" title="Due in %(dt_future)s (%(datestr_future)s)">Milestone Woo</a>
249 <a class="milestone" href="/milestone/zoo" title="%(dt_past)s late (%(datestr_past)s)">Milestone Zoo</a>
250 </p>
251 ------------------------------
252 ============================== milestone: link resolver + arguments
253 milestone:?action=new
254 [milestone:boo#KnownIssues Known Issues for 1.0]
255 ------------------------------
256 <p>
257 <a class="missing milestone" href="/milestone/?action=new" rel="nofollow">milestone:?action=new</a>
258 <a class="milestone" href="/milestone/boo#KnownIssues" title="No date set">Known Issues for 1.0</a>
259 </p>
260 ------------------------------
261 """ % {'dt_past': pretty_timedelta(dt_past),
262 'dt_future': pretty_timedelta(dt_future),
263 'datestr_past': format_datetime(dt_past, locale=locale_en, tzinfo=utc),
264 'datestr_future': format_datetime(dt_future, locale=locale_en,
265 tzinfo=utc)}
266
287
290
291
292
293 QUERY_TEST_CASES = u"""
294 ============================== query: link resolver
295 query:?order=priority
296
297 query:?order=priority&owner=me
298
299 query:?type=résumé
300
301 query:status=new|reopened
302
303 query:reporter!=
304
305 query:reporter=joe|jack&owner=me
306
307 query:group=owner
308
309 query:verbose=1
310
311 query:summary=résumé
312 ------------------------------
313 <p>
314 <a class="query" href="/query?order=priority">query:?order=priority</a>
315 </p>
316 <p>
317 <a class="query" href="/query?order=priority&owner=me">query:?order=priority&owner=me</a>
318 </p>
319 <p>
320 <a class="query" href="/query?type=r%C3%A9sum%C3%A9">query:?type=résumé</a>
321 </p>
322 <p>
323 <a class="query" href="/query?status=new&status=reopened&order=priority">query:status=new|reopened</a>
324 </p>
325 <p>
326 <a class="query" href="/query?reporter=!&order=priority">query:reporter!=</a>
327 </p>
328 <p>
329 <a class="query" href="/query?owner=me&reporter=joe&reporter=jack&order=priority">query:reporter=joe|jack&owner=me</a>
330 </p>
331 <p>
332 <a class="query" href="/query?group=owner&order=priority">query:group=owner</a>
333 </p>
334 <p>
335 <a class="query" href="/query?order=priority&row=description">query:verbose=1</a>
336 </p>
337 <p>
338 <a class="query" href="/query?summary=r%C3%A9sum%C3%A9&order=priority">query:summary=résumé</a>
339 </p>
340 ------------------------------
341 ============================== TicketQuery macro: no results, list form
342 Reopened tickets: [[TicketQuery(status=reopened)]]
343 ------------------------------
344 <p>
345 Reopened tickets: <span class="query_no_results">No results</span>
346 </p>
347 ------------------------------
348 ============================== TicketQuery macro: no results, count 0 (raw)
349 Reopened tickets: [[TicketQuery(status=reopened, format=rawcount)]]
350 ------------------------------
351 <p>
352 Reopened tickets: <span class="query_count" title="0 tickets matching status=reopened, max=0, order=id">0</span>
353 </p>
354 ------------------------------
355 ============================== TicketQuery macro: no results, count 0
356 Reopened tickets: [[TicketQuery(status=reopened, format=count)]]
357 ------------------------------
358 <p>
359 Reopened tickets: <a href="/query?status=reopened&max=0&order=id" title="0 tickets matching status=reopened, max=0, order=id">0</a>
360 </p>
361 ------------------------------
362 ============================== TicketQuery macro: no results, compact form
363 Reopened tickets: [[TicketQuery(status=reopened, format=compact)]]
364 ------------------------------
365 <p>
366 Reopened tickets: <span class="query_no_results">No results</span>
367 </p>
368 ------------------------------
369 ============================== TicketQuery macro: one result, list form
370 New tickets: [[TicketQuery(status=new)]]
371 ------------------------------
372 <p>
373 New tickets: </p><div><dl class="wiki compact"><dt><a class="new" href="/ticket/1" title="This is the summary">#1</a></dt><dd>This is the summary</dd></dl></div><p>
374 </p>
375 ------------------------------
376 ============================== TicketQuery macro: one result, count 1 (raw)
377 New tickets: [[TicketQuery(status=new, format=rawcount)]]
378 ------------------------------
379 <p>
380 New tickets: <span class="query_count" title="1 ticket matching status=new, max=0, order=id">1</span>
381 </p>
382 ------------------------------
383 ============================== TicketQuery macro: one result, count 1
384 New tickets: [[TicketQuery(status=new, format=count)]]
385 ------------------------------
386 <p>
387 New tickets: <a href="/query?status=new&max=0&order=id" title="1 ticket matching status=new, max=0, order=id">1</a>
388 </p>
389 ------------------------------
390 ============================== TicketQuery macro: one result, compact form
391 New tickets: [[TicketQuery(status=new, format=compact)]]
392 ------------------------------
393 <p>
394 New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
395 </p>
396 ------------------------------
397 ============================== TicketQuery macro: duplicated fields
398 New tickets: [[TicketQuery(status=new, format=compact, col=summary|status|status)]]
399 ------------------------------
400 <p>
401 New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
402 </p>
403 ------------------------------
404 ============================== TicketQuery macro: duplicated custom fields
405 New tickets: [[TicketQuery(status=new, format=compact, col=summary|custom1|custom1)]]
406 ------------------------------
407 <p>
408 New tickets: <span><a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
409 </p>
410 ------------------------------
411 """
412
413 QUERY2_TEST_CASES = u"""
414 ============================== TicketQuery macro: two results, list form
415 New tickets: [[TicketQuery(status=new, order=reporter)]]
416 ------------------------------
417 <p>
418 New tickets: </p><div><dl class="wiki compact"><dt><a class="new" href="/ticket/2" title="This is another summary">#2</a></dt><dd>This is another summary</dd><dt><a class="new" href="/ticket/1" title="This is the summary">#1</a></dt><dd>This is the summary</dd></dl></div><p>
419 </p>
420 ------------------------------
421 ============================== TicketQuery macro: two results, count 2 (raw)
422 New tickets: [[TicketQuery(status=new, order=reporter, format=rawcount)]]
423 ------------------------------
424 <p>
425 New tickets: <span class="query_count" title="2 tickets matching status=new, max=0, order=reporter">2</span>
426 </p>
427 ------------------------------
428 ============================== TicketQuery macro: two results, count 2
429 New tickets: [[TicketQuery(status=new, order=reporter, format=count)]]
430 ------------------------------
431 <p>
432 New tickets: <a href="/query?status=new&max=0&order=reporter" title="2 tickets matching status=new, max=0, order=reporter">2</a>
433 </p>
434 ------------------------------
435 ============================== TicketQuery macro: two results, compact form
436 New tickets: [[TicketQuery(status=new, order=reporter, format=compact)]]
437 ------------------------------
438 <p>
439 New tickets: <span><a class="new" href="/ticket/2" title="This is another summary">#2</a>, <a class="new" href="/ticket/1" title="This is the summary">#1</a></span>
440 </p>
441 ------------------------------
442 """
443
455
458
459
460 COMMENT_TEST_CASES = u"""
461 ============================== comment: link resolver (deprecated)
462 comment:ticket:1:1 (deprecated)
463 [comment:ticket:1:1 see above] (deprecated)
464 comment:ticket:1:description (deprecated)
465 [comment:ticket:1:description see descr] (deprecated)
466 comment:ticket:2:1 (deprecated)
467 comment:ticket:2:3 (deprecated)
468 comment:ticket:3:1 (deprecated)
469 comment:tiket:2:1 (deprecated)
470 comment:ticket:two:1 (deprecated)
471 comment:ticket:2:1a (deprecated)
472 comment:ticket:2:one (deprecated)
473 comment:ticket:1: (deprecated)
474 comment:ticket::2 (deprecated)
475 comment:ticket:: (deprecated)
476 ------------------------------
477 <p>
478 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">comment:ticket:1:1</a> (deprecated)
479 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">see above</a> (deprecated)
480 <a class="new ticket" href="/ticket/1#comment:description" title="Description for #1: This is the summary for ticket 1 (new)">comment:ticket:1:description</a> (deprecated)
481 <a class="new ticket" href="/ticket/1#comment:description" title="Description for #1: This is the summary for ticket 1 (new)">see descr</a> (deprecated)
482 <a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:ticket:2:1</a> (deprecated)
483 <a class="missing ticket" title="ticket comment does not exist">comment:ticket:2:3</a> (deprecated)
484 <a class="missing ticket" title="ticket does not exist">comment:ticket:3:1</a> (deprecated)
485 comment:tiket:2:1 (deprecated)
486 comment:ticket:two:1 (deprecated)
487 comment:ticket:2:1a (deprecated)
488 comment:ticket:2:one (deprecated)
489 comment:ticket:1: (deprecated)
490 comment:ticket::2 (deprecated)
491 comment:ticket:: (deprecated)
492 </p>
493 ------------------------------
494 ============================== comment: link resolver
495 comment:1
496 comment:₁
497 [comment:1 see above]
498 comment:description
499 [comment:description see descr]
500 comment:
501 comment:one
502 comment:1a
503 ------------------------------
504 <p>
505 <a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:1</a>
506 <a class="missing ticket" title="ticket comment does not exist">comment:₁</a>
507 <a class="ticket" href="/ticket/2#comment:1" title="Comment 1">see above</a>
508 <a class="ticket" href="/ticket/2#comment:description" title="Description">comment:description</a>
509 <a class="ticket" href="/ticket/2#comment:description" title="Description">see descr</a>
510 comment:
511 comment:one
512 comment:1a
513 </p>
514 ------------------------------
515 ============================== comment: link resolver with ticket number
516 comment:1:bug:1
517 comment:1:issue:1
518 comment:1:ticket:1
519 comment:₁:ticket:1
520 [comment:1:ticket:1 see above]
521 comment:description:ticket:1
522 [comment:description:ticket:1 see descr]
523 comment:1:ticket:2
524 comment:3:ticket:2
525 comment:1:ticket:3
526 comment:2:tiket:1
527 comment:1:ticket:two
528 comment:one:ticket:1
529 comment:1a:ticket:1
530 comment:ticket:1
531 comment:2:ticket:
532 comment::ticket:
533 ------------------------------
534 <p>
535 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">comment:1:bug:1</a>
536 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">comment:1:issue:1</a>
537 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">comment:1:ticket:1</a>
538 <a class="missing ticket" title="ticket comment does not exist">comment:₁:ticket:1</a>
539 <a class="new ticket" href="/ticket/1#comment:1" title="Comment 1 for #1: This is the summary for ticket 1 (new)">see above</a>
540 <a class="new ticket" href="/ticket/1#comment:description" title="Description for #1: This is the summary for ticket 1 (new)">comment:description:ticket:1</a>
541 <a class="new ticket" href="/ticket/1#comment:description" title="Description for #1: This is the summary for ticket 1 (new)">see descr</a>
542 <a class="ticket" href="/ticket/2#comment:1" title="Comment 1">comment:1:ticket:2</a>
543 <a class="missing ticket" title="ticket comment does not exist">comment:3:ticket:2</a>
544 <a class="missing ticket" title="ticket does not exist">comment:1:ticket:3</a>
545 comment:2:tiket:1
546 comment:1:ticket:two
547 comment:one:ticket:1
548 comment:1a:ticket:1
549 comment:ticket:1
550 comment:2:ticket:
551 comment::ticket:
552 </p>
553 ------------------------------
554 """
555
556
557
558
559
560
561
562
563
564
578
581
582
584 suite = unittest.TestSuite()
585 suite.addTest(formatter.test_suite(TICKET_TEST_CASES, ticket_setup,
586 __file__, ticket_teardown))
587 suite.addTest(formatter.test_suite(REPORT_TEST_CASES, report_setup,
588 __file__))
589 suite.addTest(formatter.test_suite(MILESTONE_TEST_CASES, milestone_setup,
590 __file__, milestone_teardown))
591 suite.addTest(formatter.test_suite(QUERY_TEST_CASES, ticket_setup,
592 __file__, ticket_teardown))
593 suite.addTest(formatter.test_suite(QUERY2_TEST_CASES, query2_setup,
594 __file__, query2_teardown))
595 suite.addTest(formatter.test_suite(COMMENT_TEST_CASES, comment_setup,
596 __file__, comment_teardown,
597 ('ticket', 2)))
598 return suite
599
600 if __name__ == '__main__':
601 unittest.main(defaultTest='test_suite')
602