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: 3 additions & 6 deletions src/components/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ export default function timeline({ apiUrl, entityType, defaultView }) {
_previewAborter: null,

init() {
this.calculateColumns();
this.columns = this.calculateColumns();
this.fetchBuckets();

this._resizeObserver = new ResizeObserver(() => {
// P3 fix: calculate without mutating first, then compare + assign
const width = this.$el ? this.$el.clientWidth : 720;
const newCols = Math.max(5, Math.min(30, Math.floor(width / 60)));
const newCols = this.calculateColumns();
if (newCols !== this.columns) {
this.columns = newCols;
this.fetchBuckets();
Expand All @@ -62,8 +60,7 @@ export default function timeline({ apiUrl, entityType, defaultView }) {
calculateColumns() {
const width = this.$el ? this.$el.clientWidth : 720;
const cols = Math.floor(width / 60);
this.columns = Math.max(5, Math.min(30, cols));
return this.columns;
return Math.max(5, Math.min(30, cols));
},

get rangeLabel() {
Expand Down
83 changes: 83 additions & 0 deletions src/components/timeline.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import timeline from './timeline.js';

// Mock ResizeObserver
global.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));

describe('timeline component', () => {
let component;
const mockApiUrl = '/v1/test/timeline';
const mockEntityType = 'test';
const mockDefaultView = '/test';

beforeEach(() => {
vi.clearAllMocks();
component = timeline({
apiUrl: mockApiUrl,
entityType: mockEntityType,
defaultView: mockDefaultView
});
// Mock $el
component.$el = { clientWidth: 1200 };
});

it('should calculate columns without mutating state in calculateColumns', () => {
component.columns = 10;
const result = component.calculateColumns();

expect(result).toBe(20); // 1200 / 60 = 20
expect(component.columns).toBe(10); // State should not have changed
});

it('should update columns in init', () => {
// Mock fetchBuckets
component.fetchBuckets = vi.fn();

component.init();
expect(component.columns).toBe(20);
expect(component.fetchBuckets).toHaveBeenCalled();
expect(global.ResizeObserver).toHaveBeenCalled();
});

it('should respect min/max column bounds', () => {
component.$el.clientWidth = 100;
expect(component.calculateColumns()).toBe(5); // Math.max(5, floor(100/60))

component.$el.clientWidth = 3000;
expect(component.calculateColumns()).toBe(30); // Math.min(30, floor(3000/60))
});

it('should update columns on resize if they change', () => {
component.fetchBuckets = vi.fn();
component.init();

const resizeCallback = global.ResizeObserver.mock.calls[0][0];

// Change width to trigger new column count
component.$el.clientWidth = 600; // should be 10 cols

resizeCallback();

expect(component.columns).toBe(10);
expect(component.fetchBuckets).toHaveBeenCalledTimes(2); // once in init, once on resize
});

it('should NOT update buckets on resize if columns stay same', () => {
component.fetchBuckets = vi.fn();
component.init();

const resizeCallback = global.ResizeObserver.mock.calls[0][0];

// Change width but keep cols same
component.$el.clientWidth = 1210; // still 20 cols

resizeCallback();

expect(component.columns).toBe(20);
expect(component.fetchBuckets).toHaveBeenCalledTimes(1); // only once in init
});
});
Loading