Add experiment logs to the monitoring page
This commit is contained in:
@@ -11,8 +11,6 @@ const loggingStoreRef: { current: null | { setState: (state: Partial<LoggingSett
|
||||
type LoggingSettingsState = {
|
||||
showRelativeTime: boolean;
|
||||
setShowRelativeTime: (show: boolean) => void;
|
||||
scrollToBottom: boolean;
|
||||
setScrollToBottom: (scroll: boolean) => void;
|
||||
};
|
||||
|
||||
jest.mock("zustand", () => {
|
||||
@@ -59,8 +57,8 @@ type LoggingComponent = typeof import("../../../src/components/Logging/Logging.t
|
||||
let Logging: LoggingComponent;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!Element.prototype.scrollIntoView) {
|
||||
Object.defineProperty(Element.prototype, "scrollIntoView", {
|
||||
if (!Element.prototype.scrollTo) {
|
||||
Object.defineProperty(Element.prototype, "scrollTo", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function () {},
|
||||
@@ -84,7 +82,6 @@ afterEach(() => {
|
||||
function resetLoggingStore() {
|
||||
loggingStoreRef.current?.setState({
|
||||
showRelativeTime: false,
|
||||
scrollToBottom: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -151,7 +148,7 @@ describe("Logging component", () => {
|
||||
];
|
||||
mockUseLogs.mockReturnValue({filteredLogs: logs, distinctNames: new Set()});
|
||||
|
||||
const scrollSpy = jest.spyOn(Element.prototype, "scrollIntoView").mockImplementation(() => {});
|
||||
const scrollSpy = jest.spyOn(Element.prototype, "scrollTo").mockImplementation(() => {});
|
||||
const user = userEvent.setup();
|
||||
const view = render(<Logging/>);
|
||||
|
||||
@@ -175,7 +172,7 @@ describe("Logging component", () => {
|
||||
const logCell = makeCell({message: "Initial", firstRelativeCreated: 42});
|
||||
mockUseLogs.mockReturnValue({filteredLogs: [logCell], distinctNames: new Set()});
|
||||
|
||||
const scrollSpy = jest.spyOn(Element.prototype, "scrollIntoView").mockImplementation(() => {});
|
||||
const scrollSpy = jest.spyOn(Element.prototype, "scrollTo").mockImplementation(() => {});
|
||||
render(<Logging/>);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -209,7 +206,7 @@ describe("Logging component", () => {
|
||||
|
||||
const initialMap = firstProps.filterPredicates;
|
||||
expect(initialMap).toBeInstanceOf(Map);
|
||||
expect(initialMap.size).toBe(0);
|
||||
expect(initialMap.size).toBe(1); // Initially, only filter out experiment logs
|
||||
expect(mockUseLogs).toHaveBeenCalledWith(initialMap);
|
||||
|
||||
const updatedPredicate: LogFilterPredicate = {
|
||||
|
||||
@@ -1,2 +1,46 @@
|
||||
// Adds jest-dom matchers for React testing library
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
// Minimal browser API mocks for the test environment.
|
||||
// Fetch
|
||||
if (!globalThis.fetch) {
|
||||
globalThis.fetch = jest.fn(async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => [],
|
||||
text: async () => '',
|
||||
})) as unknown as typeof fetch;
|
||||
}
|
||||
|
||||
// EventSource
|
||||
if (!globalThis.EventSource) {
|
||||
class MockEventSource {
|
||||
url: string;
|
||||
readyState = 1;
|
||||
onmessage: ((event: MessageEvent) => void) | null = null;
|
||||
onerror: ((event: Event) => void) | null = null;
|
||||
onopen: ((event: Event) => void) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.readyState = 2;
|
||||
}
|
||||
|
||||
addEventListener(type: string, listener: (event: MessageEvent) => void) {
|
||||
if (type === 'message') {
|
||||
this.onmessage = listener;
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListener(type: string, listener: (event: MessageEvent) => void) {
|
||||
if (type === 'message' && this.onmessage === listener) {
|
||||
this.onmessage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.EventSource = MockEventSource as unknown as typeof EventSource;
|
||||
}
|
||||
|
||||
34
test/utils/capitalize.test.ts
Normal file
34
test/utils/capitalize.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import capitalize from "../../src/utils/capitalize.ts";
|
||||
|
||||
describe('capitalize', () => {
|
||||
it('capitalizes the first letter of a lowercase word', () => {
|
||||
expect(capitalize('hello')).toBe('Hello');
|
||||
});
|
||||
|
||||
it('keeps the first letter capitalized if already uppercase', () => {
|
||||
expect(capitalize('Hello')).toBe('Hello');
|
||||
});
|
||||
|
||||
it('handles single character strings', () => {
|
||||
expect(capitalize('a')).toBe('A');
|
||||
expect(capitalize('A')).toBe('A');
|
||||
});
|
||||
|
||||
it('returns empty string for empty input', () => {
|
||||
expect(capitalize('')).toBe('');
|
||||
});
|
||||
|
||||
it('only capitalizes the first letter, leaving the rest unchanged', () => {
|
||||
expect(capitalize('hELLO')).toBe('HELLO');
|
||||
expect(capitalize('hello world')).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('handles strings starting with numbers', () => {
|
||||
expect(capitalize('123abc')).toBe('123abc');
|
||||
});
|
||||
|
||||
it('handles strings starting with special characters', () => {
|
||||
expect(capitalize('!hello')).toBe('!hello');
|
||||
expect(capitalize(' hello')).toBe(' hello');
|
||||
});
|
||||
});
|
||||
77
test/utils/delayedResolve.test.ts
Normal file
77
test/utils/delayedResolve.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import delayedResolve from "../../src/utils/delayedResolve.ts";
|
||||
|
||||
describe('delayedResolve', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('returns the resolved value of the promise', async () => {
|
||||
const resultPromise = delayedResolve(Promise.resolve('hello'), 100);
|
||||
await jest.advanceTimersByTimeAsync(100);
|
||||
expect(await resultPromise).toBe('hello');
|
||||
});
|
||||
|
||||
it('waits at least minDelayMs before resolving', async () => {
|
||||
let resolved = false;
|
||||
const resultPromise = delayedResolve(Promise.resolve('fast'), 100);
|
||||
resultPromise.then(() => { resolved = true; });
|
||||
|
||||
await jest.advanceTimersByTimeAsync(50);
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
await jest.advanceTimersByTimeAsync(50);
|
||||
expect(resolved).toBe(true);
|
||||
});
|
||||
|
||||
it('resolves immediately after slow promise if it exceeds minDelayMs', async () => {
|
||||
let resolved = false;
|
||||
const slowPromise = new Promise<string>(resolve =>
|
||||
setTimeout(() => resolve('slow'), 150)
|
||||
);
|
||||
const resultPromise = delayedResolve(slowPromise, 50);
|
||||
resultPromise.then(() => { resolved = true; });
|
||||
|
||||
await jest.advanceTimersByTimeAsync(50);
|
||||
expect(resolved).toBe(false);
|
||||
|
||||
await jest.advanceTimersByTimeAsync(100);
|
||||
expect(resolved).toBe(true);
|
||||
expect(await resultPromise).toBe('slow');
|
||||
});
|
||||
|
||||
it('propagates rejections from the promise', async () => {
|
||||
const error = new Error('test error');
|
||||
const rejectedPromise = Promise.reject(error);
|
||||
|
||||
const resultPromise = delayedResolve(rejectedPromise, 100);
|
||||
const assertion = expect(resultPromise).rejects.toThrow('test error');
|
||||
|
||||
await jest.advanceTimersByTimeAsync(100);
|
||||
|
||||
await assertion;
|
||||
});
|
||||
|
||||
it('works with different value types', async () => {
|
||||
const test = async <T>(value: T) => {
|
||||
const resultPromise = delayedResolve(Promise.resolve(value), 10);
|
||||
await jest.advanceTimersByTimeAsync(10);
|
||||
return resultPromise;
|
||||
};
|
||||
|
||||
expect(await test(42)).toBe(42);
|
||||
expect(await test({ foo: 'bar' })).toEqual({ foo: 'bar' });
|
||||
expect(await test([1, 2, 3])).toEqual([1, 2, 3]);
|
||||
expect(await test(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('handles zero delay', async () => {
|
||||
const resultPromise = delayedResolve(Promise.resolve('instant'), 0);
|
||||
await jest.advanceTimersByTimeAsync(0);
|
||||
expect(await resultPromise).toBe('instant');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user