Issues (105)

reports/coverage_html.js (12 issues)

1
// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
4
// Coverage.py HTML report browser code.
5
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
6
/*global coverage: true, document, window, $ */
7
8
coverage = {};
0 ignored issues
show
The variable coverage seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.coverage.
Loading history...
9
10
// Find all the elements with shortkey_* class, and use them to assign a shortcut key.
11
coverage.assign_shortkeys = function () {
12
    $("*[class*='shortkey_']").each(function (i, e) {
13
        $.each($(e).attr("class").split(" "), function (i, c) {
14
            if (/^shortkey_/.test(c)) {
15
                $(document).bind('keydown', c.substr(9), function () {
16
                    $(e).click();
17
                });
18
            }
19
        });
20
    });
21
};
22
23
// Create the events for the help panel.
24
coverage.wire_up_help_panel = function () {
25
    $("#keyboard_icon").click(function () {
26
        // Show the help panel, and position it so the keyboard icon in the
27
        // panel is in the same place as the keyboard icon in the header.
28
        $(".help_panel").show();
29
        var koff = $("#keyboard_icon").offset();
30
        var poff = $("#panel_icon").position();
31
        $(".help_panel").offset({
32
            top: koff.top-poff.top,
33
            left: koff.left-poff.left
34
        });
35
    });
36
    $("#panel_icon").click(function () {
37
        $(".help_panel").hide();
38
    });
39
};
40
41
// Create the events for the filter box.
42
coverage.wire_up_filter = function () {
43
    // Cache elements.
44
    var table = $("table.index");
45
    var table_rows = table.find("tbody tr");
46
    var table_row_names = table_rows.find("td.name a");
47
    var no_rows = $("#no_rows");
48
49
    // Create a duplicate table footer that we can modify with dynamic summed values.
50
    var table_footer = $("table.index tfoot tr");
51
    var table_dynamic_footer = table_footer.clone();
52
    table_dynamic_footer.attr('class', 'total_dynamic hidden');
53
    table_footer.after(table_dynamic_footer);
54
55
    // Observe filter keyevents.
56
    $("#filter").on("keyup change", $.debounce(150, function (event) {
0 ignored issues
show
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
57
        var filter_value = $(this).val();
58
59
        if (filter_value === "") {
60
            // Filter box is empty, remove all filtering.
61
            table_rows.removeClass("hidden");
62
63
            // Show standard footer, hide dynamic footer.
64
            table_footer.removeClass("hidden");
65
            table_dynamic_footer.addClass("hidden");
66
67
            // Hide placeholder, show table.
68
            if (no_rows.length > 0) {
69
                no_rows.hide();
70
            }
71
            table.show();
72
73
        }
74
        else {
75
            // Filter table items by value.
76
            var hidden = 0;
77
            var shown = 0;
78
79
            // Hide / show elements.
80
            $.each(table_row_names, function () {
81
                var element = $(this).parents("tr");
82
83
                if ($(this).text().indexOf(filter_value) === -1) {
84
                    // hide
85
                    element.addClass("hidden");
86
                    hidden++;
87
                }
88
                else {
89
                    // show
90
                    element.removeClass("hidden");
91
                    shown++;
92
                }
93
            });
94
95
            // Show placeholder if no rows will be displayed.
96
            if (no_rows.length > 0) {
97
                if (shown === 0) {
98
                    // Show placeholder, hide table.
99
                    no_rows.show();
100
                    table.hide();
101
                }
102
                else {
103
                    // Hide placeholder, show table.
104
                    no_rows.hide();
105
                    table.show();
106
                }
107
            }
108
109
            // Manage dynamic header:
110
            if (hidden > 0) {
111
                // Calculate new dynamic sum values based on visible rows.
112
                for (var column = 2; column < 20; column++) {
113
                    // Calculate summed value.
114
                    var cells = table_rows.find('td:nth-child(' + column + ')');
115
                    if (!cells.length) {
116
                        // No more columns...!
117
                        break;
118
                    }
119
120
                    var sum = 0, numer = 0, denom = 0;
121
                    $.each(cells.filter(':visible'), function () {
122
                        var ratio = $(this).data("ratio");
123
                        if (ratio) {
124
                            var splitted = ratio.split(" ");
125
                            numer += parseInt(splitted[0], 10);
0 ignored issues
show
The variable numer is changed as part of the for loop for example by 0 on line 120. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
126
                            denom += parseInt(splitted[1], 10);
0 ignored issues
show
The variable denom is changed as part of the for loop for example by 0 on line 120. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
127
                        }
128
                        else {
129
                            sum += parseInt(this.innerHTML, 10);
0 ignored issues
show
The variable sum is changed as part of the for loop for example by 0 on line 120. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
130
                        }
131
                    });
132
133
                    // Get footer cell element.
134
                    var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')');
135
136
                    // Set value into dynamic footer cell element.
137
                    if (cells[0].innerHTML.indexOf('%') > -1) {
138
                        // Percentage columns use the numerator and denominator,
139
                        // and adapt to the number of decimal places.
140
                        var match = /\.([0-9]+)/.exec(cells[0].innerHTML);
141
                        var places = 0;
142
                        if (match) {
143
                            places = match[1].length;
144
                        }
145
                        var pct = numer * 100 / denom;
146
                        footer_cell.text(pct.toFixed(places) + '%');
147
                    }
148
                    else {
149
                        footer_cell.text(sum);
150
                    }
151
                }
152
153
                // Hide standard footer, show dynamic footer.
154
                table_footer.addClass("hidden");
155
                table_dynamic_footer.removeClass("hidden");
156
            }
157
            else {
158
                // Show standard footer, hide dynamic footer.
159
                table_footer.removeClass("hidden");
160
                table_dynamic_footer.addClass("hidden");
161
            }
162
        }
163
    }));
164
165
    // Trigger change event on setup, to force filter on page refresh
166
    // (filter value may still be present).
167
    $("#filter").trigger("change");
168
};
169
170
// Loaded on index.html
171
coverage.index_ready = function ($) {
172
    // Look for a localStorage item containing previous sort settings:
173
    var sort_list = [];
174
    var storage_name = "COVERAGE_INDEX_SORT";
175
    var stored_list = undefined;
0 ignored issues
show
Unused Code Comprehensibility introduced by
The assignment of undefined is not necessary as stored_list is implicitly marked as undefined by the declaration.
Loading history...
176
    try {
177
        stored_list = localStorage.getItem(storage_name);
0 ignored issues
show
The variable localStorage seems to be never declared. If this is a global, consider adding a /** global: localStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
178
    } catch(err) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
179
180
    if (stored_list) {
181
        sort_list = JSON.parse('[[' + stored_list + ']]');
182
    }
183
184
    // Create a new widget which exists only to save and restore
185
    // the sort order:
186
    $.tablesorter.addWidget({
187
        id: "persistentSort",
188
189
        // Format is called by the widget before displaying:
190
        format: function (table) {
191
            if (table.config.sortList.length === 0 && sort_list.length > 0) {
192
                // This table hasn't been sorted before - we'll use
193
                // our stored settings:
194
                $(table).trigger('sorton', [sort_list]);
195
            }
196
            else {
197
                // This is not the first load - something has
198
                // already defined sorting so we'll just update
199
                // our stored value to match:
200
                sort_list = table.config.sortList;
201
            }
202
        }
203
    });
204
205
    // Configure our tablesorter to handle the variable number of
206
    // columns produced depending on report options:
207
    var headers = [];
208
    var col_count = $("table.index > thead > tr > th").length;
209
210
    headers[0] = { sorter: 'text' };
211
    for (i = 1; i < col_count-1; i++) {
0 ignored issues
show
The variable i seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.i.
Loading history...
212
        headers[i] = { sorter: 'digit' };
213
    }
214
    headers[col_count-1] = { sorter: 'percent' };
215
216
    // Enable the table sorter:
217
    $("table.index").tablesorter({
218
        widgets: ['persistentSort'],
219
        headers: headers
220
    });
221
222
    coverage.assign_shortkeys();
223
    coverage.wire_up_help_panel();
224
    coverage.wire_up_filter();
225
226
    // Watch for page unload events so we can save the final sort settings:
227
    $(window).unload(function () {
228
        try {
229
            localStorage.setItem(storage_name, sort_list.toString())
0 ignored issues
show
The variable localStorage seems to be never declared. If this is a global, consider adding a /** global: localStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
230
        } catch(err) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
231
    });
232
};
233
234
// -- pyfile stuff --
235
236
coverage.pyfile_ready = function ($) {
237
    // If we're directed to a particular line number, highlight the line.
238
    var frag = location.hash;
239
    if (frag.length > 2 && frag[1] === 't') {
240
        $(frag).addClass('highlight');
241
        coverage.set_sel(parseInt(frag.substr(2), 10));
242
    }
243
    else {
244
        coverage.set_sel(0);
245
    }
246
247
    $(document)
248
        .bind('keydown', 'j', coverage.to_next_chunk_nicely)
249
        .bind('keydown', 'k', coverage.to_prev_chunk_nicely)
250
        .bind('keydown', '0', coverage.to_top)
251
        .bind('keydown', '1', coverage.to_first_chunk)
252
        ;
253
254
    $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");});
255
    $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");});
256
    $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
257
    $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
258
259
    coverage.assign_shortkeys();
260
    coverage.wire_up_help_panel();
261
262
    coverage.init_scroll_markers();
263
264
    // Rebuild scroll markers when the window height changes.
265
    $(window).resize(coverage.build_scroll_markers);
266
};
267
268
coverage.toggle_lines = function (btn, cls) {
269
    btn = $(btn);
270
    var show = "show_"+cls;
271
    if (btn.hasClass(show)) {
272
        $("#source ." + cls).removeClass(show);
273
        btn.removeClass(show);
274
    }
275
    else {
276
        $("#source ." + cls).addClass(show);
277
        btn.addClass(show);
278
    }
279
    coverage.build_scroll_markers();
280
};
281
282
// Return the nth line div.
283
coverage.line_elt = function (n) {
284
    return $("#t" + n);
285
};
286
287
// Return the nth line number div.
288
coverage.num_elt = function (n) {
289
    return $("#n" + n);
290
};
291
292
// Set the selection.  b and e are line numbers.
293
coverage.set_sel = function (b, e) {
294
    // The first line selected.
295
    coverage.sel_begin = b;
296
    // The next line not selected.
297
    coverage.sel_end = (e === undefined) ? b+1 : e;
298
};
299
300
coverage.to_top = function () {
301
    coverage.set_sel(0, 1);
302
    coverage.scroll_window(0);
303
};
304
305
coverage.to_first_chunk = function () {
306
    coverage.set_sel(0, 1);
307
    coverage.to_next_chunk();
308
};
309
310
// Return a string indicating what kind of chunk this line belongs to,
311
// or null if not a chunk.
312
coverage.chunk_indicator = function (line_elt) {
313
    var klass = line_elt.attr('class');
314
    if (klass) {
315
        var m = klass.match(/\bshow_\w+\b/);
316
        if (m) {
317
            return m[0];
318
        }
319
    }
320
    return null;
321
};
322
323
coverage.to_next_chunk = function () {
324
    var c = coverage;
325
326
    // Find the start of the next colored chunk.
327
    var probe = c.sel_end;
328
    var chunk_indicator, probe_line;
329
    while (true) {
330
        probe_line = c.line_elt(probe);
331
        if (probe_line.length === 0) {
332
            return;
333
        }
334
        chunk_indicator = c.chunk_indicator(probe_line);
335
        if (chunk_indicator) {
336
            break;
337
        }
338
        probe++;
339
    }
340
341
    // There's a next chunk, `probe` points to it.
342
    var begin = probe;
343
344
    // Find the end of this chunk.
345
    var next_indicator = chunk_indicator;
0 ignored issues
show
Comprehensibility Bug introduced by
The variable chunk_indicator does not seem to be initialized in case the while loop on line 329 is not entered. Are you sure this can never be the case?
Loading history...
346
    while (next_indicator === chunk_indicator) {
347
        probe++;
348
        probe_line = c.line_elt(probe);
349
        next_indicator = c.chunk_indicator(probe_line);
350
    }
351
    c.set_sel(begin, probe);
352
    c.show_selection();
353
};
354
355
coverage.to_prev_chunk = function () {
356
    var c = coverage;
357
358
    // Find the end of the prev colored chunk.
359
    var probe = c.sel_begin-1;
360
    var probe_line = c.line_elt(probe);
361
    if (probe_line.length === 0) {
362
        return;
363
    }
364
    var chunk_indicator = c.chunk_indicator(probe_line);
365
    while (probe > 0 && !chunk_indicator) {
366
        probe--;
367
        probe_line = c.line_elt(probe);
368
        if (probe_line.length === 0) {
369
            return;
370
        }
371
        chunk_indicator = c.chunk_indicator(probe_line);
372
    }
373
374
    // There's a prev chunk, `probe` points to its last line.
375
    var end = probe+1;
376
377
    // Find the beginning of this chunk.
378
    var prev_indicator = chunk_indicator;
379
    while (prev_indicator === chunk_indicator) {
380
        probe--;
381
        probe_line = c.line_elt(probe);
382
        prev_indicator = c.chunk_indicator(probe_line);
383
    }
384
    c.set_sel(probe+1, end);
385
    c.show_selection();
386
};
387
388
// Return the line number of the line nearest pixel position pos
389
coverage.line_at_pos = function (pos) {
390
    var l1 = coverage.line_elt(1),
391
        l2 = coverage.line_elt(2),
392
        result;
393
    if (l1.length && l2.length) {
394
        var l1_top = l1.offset().top,
395
            line_height = l2.offset().top - l1_top,
396
            nlines = (pos - l1_top) / line_height;
397
        if (nlines < 1) {
398
            result = 1;
399
        }
400
        else {
401
            result = Math.ceil(nlines);
402
        }
403
    }
404
    else {
405
        result = 1;
406
    }
407
    return result;
408
};
409
410
// Returns 0, 1, or 2: how many of the two ends of the selection are on
411
// the screen right now?
412
coverage.selection_ends_on_screen = function () {
413
    if (coverage.sel_begin === 0) {
414
        return 0;
415
    }
416
417
    var top = coverage.line_elt(coverage.sel_begin);
418
    var next = coverage.line_elt(coverage.sel_end-1);
419
420
    return (
421
        (top.isOnScreen() ? 1 : 0) +
422
        (next.isOnScreen() ? 1 : 0)
423
    );
424
};
425
426
coverage.to_next_chunk_nicely = function () {
427
    coverage.finish_scrolling();
428
    if (coverage.selection_ends_on_screen() === 0) {
429
        // The selection is entirely off the screen: select the top line on
430
        // the screen.
431
        var win = $(window);
432
        coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop()));
433
    }
434
    coverage.to_next_chunk();
435
};
436
437
coverage.to_prev_chunk_nicely = function () {
438
    coverage.finish_scrolling();
439
    if (coverage.selection_ends_on_screen() === 0) {
440
        var win = $(window);
441
        coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height()));
442
    }
443
    coverage.to_prev_chunk();
444
};
445
446
// Select line number lineno, or if it is in a colored chunk, select the
447
// entire chunk
448
coverage.select_line_or_chunk = function (lineno) {
449
    var c = coverage;
450
    var probe_line = c.line_elt(lineno);
451
    if (probe_line.length === 0) {
452
        return;
453
    }
454
    var the_indicator = c.chunk_indicator(probe_line);
455
    if (the_indicator) {
456
        // The line is in a highlighted chunk.
457
        // Search backward for the first line.
458
        var probe = lineno;
459
        var indicator = the_indicator;
460
        while (probe > 0 && indicator === the_indicator) {
461
            probe--;
462
            probe_line = c.line_elt(probe);
463
            if (probe_line.length === 0) {
464
                break;
465
            }
466
            indicator = c.chunk_indicator(probe_line);
467
        }
468
        var begin = probe + 1;
469
470
        // Search forward for the last line.
471
        probe = lineno;
472
        indicator = the_indicator;
473
        while (indicator === the_indicator) {
474
            probe++;
475
            probe_line = c.line_elt(probe);
476
            indicator = c.chunk_indicator(probe_line);
477
        }
478
479
        coverage.set_sel(begin, probe);
480
    }
481
    else {
482
        coverage.set_sel(lineno);
483
    }
484
};
485
486
coverage.show_selection = function () {
487
    var c = coverage;
488
489
    // Highlight the lines in the chunk
490
    $(".linenos .highlight").removeClass("highlight");
491
    for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) {
492
        c.num_elt(probe).addClass("highlight");
493
    }
494
495
    c.scroll_to_selection();
496
};
497
498
coverage.scroll_to_selection = function () {
499
    // Scroll the page if the chunk isn't fully visible.
500
    if (coverage.selection_ends_on_screen() < 2) {
501
        // Need to move the page. The html,body trick makes it scroll in all
502
        // browsers, got it from http://stackoverflow.com/questions/3042651
503
        var top = coverage.line_elt(coverage.sel_begin);
504
        var top_pos = parseInt(top.offset().top, 10);
505
        coverage.scroll_window(top_pos - 30);
506
    }
507
};
508
509
coverage.scroll_window = function (to_pos) {
510
    $("html,body").animate({scrollTop: to_pos}, 200);
511
};
512
513
coverage.finish_scrolling = function () {
514
    $("html,body").stop(true, true);
515
};
516
517
coverage.init_scroll_markers = function () {
518
    var c = coverage;
519
    // Init some variables
520
    c.lines_len = $('#source p').length;
521
    c.body_h = $('body').height();
522
    c.header_h = $('div#header').height();
523
524
    // Build html
525
    c.build_scroll_markers();
526
};
527
528
coverage.build_scroll_markers = function () {
529
    var c = coverage,
530
        min_line_height = 3,
531
        max_line_height = 10,
532
        visible_window_h = $(window).height();
533
534
    c.lines_to_mark = $('#source').find('p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par');
535
    $('#scroll_marker').remove();
536
    // Don't build markers if the window has no scroll bar.
537
    if (c.body_h <= visible_window_h) {
538
        return;
539
    }
540
541
    $("body").append("<div id='scroll_marker'>&nbsp;</div>");
542
    var scroll_marker = $('#scroll_marker'),
543
        marker_scale = scroll_marker.height() / c.body_h,
544
        line_height = scroll_marker.height() / c.lines_len;
545
546
    // Line height must be between the extremes.
547
    if (line_height > min_line_height) {
548
        if (line_height > max_line_height) {
549
            line_height = max_line_height;
550
        }
551
    }
552
    else {
553
        line_height = min_line_height;
554
    }
555
556
    var previous_line = -99,
557
        last_mark,
558
        last_top,
559
        offsets = {};
560
561
    // Calculate line offsets outside loop to prevent relayouts
562
    c.lines_to_mark.each(function() {
563
        offsets[this.id] = $(this).offset().top;
564
    });
565
    c.lines_to_mark.each(function () {
566
        var id_name = $(this).attr('id'),
567
            line_top = Math.round(offsets[id_name] * marker_scale),
568
            line_number = parseInt(id_name.substring(1, id_name.length));
569
570
        if (line_number === previous_line + 1) {
571
            // If this solid missed block just make previous mark higher.
572
            last_mark.css({
573
                'height': line_top + line_height - last_top
574
            });
575
        }
576
        else {
577
            // Add colored line in scroll_marker block.
578
            scroll_marker.append('<div id="m' + line_number + '" class="marker"></div>');
579
            last_mark = $('#m' + line_number);
580
            last_mark.css({
581
                'height': line_height,
582
                'top': line_top
583
            });
584
            last_top = line_top;
585
        }
586
587
        previous_line = line_number;
588
    });
589
};
590