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