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