Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion robotframework_dashboard/js/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ function open_log_file(event, chartElement, callbackData = undefined, directRunS
const index = chartElement[0].index
runStart = event.chart.data.labels[index]
}
var output = runs.find(run => run.run_start.slice(0, 19) === runStart.slice(0, 19))
// Match against filteredRuns: its run_start has been timezone-converted/stripped to
// match what the chart actually displays. Falling back to the original `runs` global
// (UTC strings) breaks when the user has the "convert to local timezone" toggle on,
// since the chart label no longer equals the UTC run_start. See issue #311.
var output = filteredRuns.find(run => run.run_start.slice(0, 19) === runStart.slice(0, 19))
if (!output) { output = filteredRuns.find(run => get_run_label(run) === runStart) }
// Fallback to the raw runs array for safety (e.g. if the run was filtered out)
if (!output) { output = runs.find(run => run.run_start.slice(0, 19) === runStart.slice(0, 19)) }
if (!output) { output = runs.find(run => get_run_label(run) === runStart) }
var path = output ? output.path : ""
if (path == "") {
Expand Down
110 changes: 110 additions & 0 deletions tests/javascript/log.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';

// Mutable mock state for runs/filteredRuns so individual tests can set them up.
const dataMock = {
runs: [],
suites: [],
tests: [],
server: false,
use_logs: true,
};
const globalsMock = {
filteredRuns: [],
filteredSuites: [],
filteredTests: [],
filteredKeywords: [],
};

vi.mock('@js/variables/data.js', () => dataMock);
vi.mock('@js/variables/globals.js', () => globalsMock);
vi.mock('@js/variables/settings.js', () => ({
settings: { show: { aliases: 'run_start' } },
get_run_label: (item) => item.run_start,
}));

// log.js calls window.open and alert; stub them.
beforeEach(() => {
dataMock.runs.length = 0;
dataMock.suites.length = 0;
dataMock.tests.length = 0;
globalsMock.filteredRuns.length = 0;
globalsMock.filteredSuites.length = 0;
globalsMock.filteredTests.length = 0;
globalsMock.filteredKeywords.length = 0;
globalThis.window = { open: vi.fn(() => ({})), location: { href: 'http://localhost/' } };
globalThis.alert = vi.fn();
});

const { open_log_file } = await import('@js/log.js');

// Build a fake chart click event for a non-line, non-doughnut graph that
// reads chart.data.labels[index]. Matches the path log.js takes for e.g.
// the bar statistics graph (the one in the issue's repro).
function makeBarClickEvent(label, graphId = 'runStatisticsGraph') {
return {
chart: {
config: { _config: { type: 'bar' } },
canvas: { id: graphId },
data: { labels: [label] },
},
};
}

describe('open_log_file (issue #311)', () => {
it('resolves log path against filteredRuns when timezone is converted to local time', () => {
// The DB / unfiltered runs hold the original UTC run_start.
const utcStart = '2025-01-15 10:00:00';
// After the "convert to local timezone" toggle, filteredRuns carries
// the local-time wall-clock string — this is what the chart label shows.
const localStart = '2025-01-15 12:00:00';
const path = '/results/output.xml';

dataMock.runs.push({ run_start: utcStart, path, run_alias: null });
globalsMock.filteredRuns.push({ run_start: localStart, path, run_alias: null });

const event = makeBarClickEvent(localStart);
const chartElement = [{ index: 0 }];

open_log_file(event, chartElement);

// Before the fix: lookup happened against `runs` (UTC), no match, alert fired.
expect(globalThis.alert).not.toHaveBeenCalled();
expect(globalThis.window.open).toHaveBeenCalledTimes(1);
const openedUrl = globalThis.window.open.mock.calls[0][0];
// output.xml is rewritten to log.html by transform_file_path
expect(openedUrl).toContain('log.html');
});

it('still resolves correctly in UTC mode (filteredRuns == runs run_start)', () => {
const utcStart = '2025-01-15 10:00:00';
const path = '/results/output.xml';
dataMock.runs.push({ run_start: utcStart, path, run_alias: null });
globalsMock.filteredRuns.push({ run_start: utcStart, path, run_alias: null });

const event = makeBarClickEvent(utcStart);
open_log_file(event, [{ index: 0 }]);

expect(globalThis.alert).not.toHaveBeenCalled();
expect(globalThis.window.open).toHaveBeenCalledTimes(1);
});

it('falls back to raw runs when the clicked run was filtered out', () => {
const utcStart = '2025-01-15 10:00:00';
const path = '/results/output.xml';
dataMock.runs.push({ run_start: utcStart, path, run_alias: null });
// filteredRuns is empty — run filtered away

const event = makeBarClickEvent(utcStart);
open_log_file(event, [{ index: 0 }]);

expect(globalThis.alert).not.toHaveBeenCalled();
expect(globalThis.window.open).toHaveBeenCalledTimes(1);
});

it('alerts only when no matching run exists anywhere', () => {
const event = makeBarClickEvent('2099-12-31 00:00:00');
open_log_file(event, [{ index: 0 }]);
expect(globalThis.alert).toHaveBeenCalledTimes(1);
expect(globalThis.window.open).not.toHaveBeenCalled();
});
});
Loading