diff --git a/.env.test.example b/.env.test.example index a0634e6d..8b1e1493 100644 --- a/.env.test.example +++ b/.env.test.example @@ -5,3 +5,6 @@ VITE_ABLY_ENV=local # A key when testing using a local realtime setup VITE_ABLY_API_KEY=your-test-api-key + +# Whether to log during tests, valid values are "silent", "error", "warn", "info", "debug", "trace" +VITE_TEST_LOG_LEVEL=silent diff --git a/src/Chat.ts b/src/Chat.ts index 695d8748..dabd7cbd 100644 --- a/src/Chat.ts +++ b/src/Chat.ts @@ -1,9 +1,10 @@ import * as Ably from 'ably'; -import { ClientOptions } from './config.js'; +import { ClientOptions, DefaultClientOptions } from './config.js'; +import { makeLogger } from './logger.js'; import { RealtimeWithOptions } from './realtimeextensions.js'; import { DefaultRooms, Rooms } from './Rooms.js'; -import { AGENT_STRING } from './version.js'; +import { AGENT_STRING, VERSION } from './version.js'; /** * This is the core client for Ably chat. It provides access to chat rooms. @@ -26,8 +27,12 @@ export class ChatClient { */ constructor(realtime: Ably.Realtime, clientOptions?: ClientOptions) { this._realtime = realtime; - this._rooms = new DefaultRooms(realtime, clientOptions); + clientOptions = clientOptions ?? DefaultClientOptions; + const logger = makeLogger(clientOptions); + + this._rooms = new DefaultRooms(realtime, clientOptions, logger); this.setAgent(); + logger.trace(`ably chat client version ${VERSION}; initialised`); } /** diff --git a/src/ChatApi.ts b/src/ChatApi.ts index d64ab768..862b0ac9 100644 --- a/src/ChatApi.ts +++ b/src/ChatApi.ts @@ -3,6 +3,7 @@ import * as Ably from 'ably'; import { Message } from './Message.js'; import { OccupancyEvent } from './Occupancy.js'; import { PaginatedResult } from './query.js'; +import { Logger } from './logger.js'; export interface GetMessagesQueryParams { start?: number; @@ -25,9 +26,11 @@ interface CreateMessageRequest { */ export class ChatApi { private readonly realtime: Ably.Realtime; + private readonly _logger: Logger; - constructor(realtime: Ably.Realtime) { + constructor(realtime: Ably.Realtime, logger: Logger) { this.realtime = realtime; + this._logger = logger; } async getMessages(roomId: string, params: GetMessagesQueryParams): Promise> { diff --git a/src/Messages.ts b/src/Messages.ts index cfaf9347..48d7d136 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -2,6 +2,7 @@ import * as Ably from 'ably'; import { ChatApi } from './ChatApi.js'; import { MessageEvents } from './events.js'; +import { Logger } from './logger.js'; import { DefaultMessage, Message } from './Message.js'; import { PaginatedResult } from './query.js'; import { SubscriptionManager } from './SubscriptionManager.js'; @@ -143,14 +144,16 @@ export class DefaultMessages extends EventEmitter implements M private readonly _managedChannel: SubscriptionManager; private readonly _chatApi: ChatApi; private readonly _clientId: string; + private readonly _logger: Logger; private _internalListener: Ably.messageCallback | undefined; - constructor(roomId: string, managedChannel: SubscriptionManager, chatApi: ChatApi, clientId: string) { + constructor(roomId: string, managedChannel: SubscriptionManager, chatApi: ChatApi, clientId: string, logger: Logger) { super(); this._roomId = roomId; this._managedChannel = managedChannel; this._chatApi = chatApi; this._clientId = clientId; + this._logger = logger; } /** diff --git a/src/Occupancy.ts b/src/Occupancy.ts index acb7953d..b354cacb 100644 --- a/src/Occupancy.ts +++ b/src/Occupancy.ts @@ -1,6 +1,7 @@ import * as Ably from 'ably'; import { ChatApi } from './ChatApi.js'; +import { Logger } from './logger.js'; import { SubscriptionManager } from './SubscriptionManager.js'; import EventEmitter from './utils/EventEmitter.js'; @@ -76,12 +77,14 @@ export class DefaultOccupancy extends EventEmitter implement private readonly _managedChannel: SubscriptionManager; private readonly _chatApi: ChatApi; private _internalListener: any; + private _logger: Logger; - constructor(roomId: string, managedChannel: SubscriptionManager, chatApi: ChatApi) { + constructor(roomId: string, managedChannel: SubscriptionManager, chatApi: ChatApi, logger: Logger) { super(); this.roomId = roomId; this._managedChannel = managedChannel; this._chatApi = chatApi; + this._logger = logger; } /** diff --git a/src/Presence.ts b/src/Presence.ts index 988e2cf1..ab07331c 100644 --- a/src/Presence.ts +++ b/src/Presence.ts @@ -1,6 +1,7 @@ import * as Ably from 'ably'; import { PresenceEvents } from './events.js'; +import { Logger } from './logger.js'; import { SubscriptionManager } from './SubscriptionManager.js'; import EventEmitter from './utils/EventEmitter.js'; @@ -172,6 +173,7 @@ export interface Presence { export class DefaultPresence extends EventEmitter implements Presence { private readonly subscriptionManager: SubscriptionManager; private readonly clientId: string; + private readonly _logger: Logger; /** * Constructor for Presence @@ -180,10 +182,11 @@ export class DefaultPresence extends EventEmitter implements * @param {string} clientId - The client ID, attached to presences messages as an identifier of the sender. * A channel can have multiple connections using the same clientId. */ - constructor(subscriptionManager: SubscriptionManager, clientId: string) { + constructor(subscriptionManager: SubscriptionManager, clientId: string, logger: Logger) { super(); this.subscriptionManager = subscriptionManager; this.clientId = clientId; + this._logger = logger; } /** diff --git a/src/Room.ts b/src/Room.ts index 02a7c0ff..cfb3465b 100644 --- a/src/Room.ts +++ b/src/Room.ts @@ -2,6 +2,7 @@ import * as Ably from 'ably'; import { ChatApi } from './ChatApi.js'; import { ClientOptions } from './config.js'; +import { Logger } from './logger.js'; import { DefaultMessages, Messages } from './Messages.js'; import { DefaultOccupancy, Occupancy } from './Occupancy.js'; import { DefaultPresence, Presence } from './Presence.js'; @@ -64,6 +65,7 @@ export class DefaultRoom implements Room { private readonly _presence: Presence; private readonly _reactions: RoomReactions; private readonly _occupancy: Occupancy; + private readonly _logger: Logger; /** * Constructs a new Room instance. @@ -73,7 +75,7 @@ export class DefaultRoom implements Room { * @param chatApi An instance of the ChatApi. * @param clientOptions The client options from the chat instance. */ - constructor(roomId: string, realtime: Ably.Realtime, chatApi: ChatApi, clientOptions: ClientOptions) { + constructor(roomId: string, realtime: Ably.Realtime, chatApi: ChatApi, clientOptions: ClientOptions, logger: Logger) { this._roomId = roomId; this.chatApi = chatApi; const messagesChannelName = `${this._roomId}::$chat::$chatMessages`; @@ -81,20 +83,22 @@ export class DefaultRoom implements Room { const subscriptionManager = new DefaultSubscriptionManager( realtime.channels.get(messagesChannelName, DEFAULT_CHANNEL_OPTIONS), ); - this._messages = new DefaultMessages(roomId, subscriptionManager, this.chatApi, realtime.auth.clientId); - this._presence = new DefaultPresence(subscriptionManager, realtime.auth.clientId); + this._messages = new DefaultMessages(roomId, subscriptionManager, this.chatApi, realtime.auth.clientId, logger); + this._presence = new DefaultPresence(subscriptionManager, realtime.auth.clientId, logger); this._typingIndicators = new DefaultTypingIndicator( roomId, realtime, realtime.auth.clientId, clientOptions.typingTimeoutMs, + logger, ); const reactionsManagedChannel = new DefaultSubscriptionManager( realtime.channels.get(`${this._roomId}::$chat::$reactions`, DEFAULT_CHANNEL_OPTIONS), ); - this._reactions = new DefaultRoomReactions(roomId, reactionsManagedChannel, realtime.auth.clientId); - this._occupancy = new DefaultOccupancy(roomId, subscriptionManager, this.chatApi); + this._reactions = new DefaultRoomReactions(roomId, reactionsManagedChannel, realtime.auth.clientId, logger); + this._occupancy = new DefaultOccupancy(roomId, subscriptionManager, this.chatApi, logger); + this._logger = logger; } /** diff --git a/src/RoomReactions.ts b/src/RoomReactions.ts index cd441ec3..97e0bd99 100644 --- a/src/RoomReactions.ts +++ b/src/RoomReactions.ts @@ -1,6 +1,7 @@ import * as Ably from 'ably'; import { RoomReactionEvents } from './events.js'; +import { Logger } from './logger.js'; import { SubscriptionManager } from './SubscriptionManager.js'; import EventEmitter from './utils/EventEmitter.js'; @@ -98,12 +99,14 @@ export class DefaultRoomReactions extends EventEmitter im private readonly roomId: string; private readonly _managedChannel: SubscriptionManager; private readonly clientId: string; + private readonly _logger: Logger; - constructor(roomId: string, managedChannel: SubscriptionManager, clientId: string) { + constructor(roomId: string, managedChannel: SubscriptionManager, clientId: string, logger: Logger) { super(); this.roomId = roomId; this._managedChannel = managedChannel; this.clientId = clientId; + this._logger = logger; } /** diff --git a/src/Rooms.ts b/src/Rooms.ts index 19df8a43..94ec9f2f 100644 --- a/src/Rooms.ts +++ b/src/Rooms.ts @@ -1,7 +1,8 @@ import * as Ably from 'ably'; import { ChatApi } from './ChatApi.js'; -import { ClientOptions, DefaultClientOptions } from './config.js'; +import { ClientOptions } from './config.js'; +import { Logger } from './logger.js'; import { DefaultRoom, Room } from './Room.js'; /** @@ -45,6 +46,7 @@ export class DefaultRooms implements Rooms { private readonly chatApi: ChatApi; private readonly _clientOptions: ClientOptions; private rooms: Record = {}; + private _logger: Logger; /** * Constructs a new Rooms instance. @@ -52,10 +54,11 @@ export class DefaultRooms implements Rooms { * @param realtime An instance of the Ably Realtime client. * @param clientOptions The client options from the chat instance. */ - constructor(realtime: Ably.Realtime, clientOptions?: ClientOptions) { + constructor(realtime: Ably.Realtime, clientOptions: ClientOptions, logger: Logger) { this.realtime = realtime; - this.chatApi = new ChatApi(realtime); - this._clientOptions = clientOptions ? clientOptions : DefaultClientOptions; + this.chatApi = new ChatApi(realtime, logger); + this._clientOptions = clientOptions; + this._logger = logger; } /** @@ -64,7 +67,7 @@ export class DefaultRooms implements Rooms { get(roomId: string): Room { if (this.rooms[roomId]) return this.rooms[roomId]; - const room = new DefaultRoom(roomId, this.realtime, this.chatApi, this._clientOptions); + const room = new DefaultRoom(roomId, this.realtime, this.chatApi, this._clientOptions, this._logger); this.rooms[roomId] = room; return room; diff --git a/src/TypingIndicator.ts b/src/TypingIndicator.ts index 98936309..3a110aae 100644 --- a/src/TypingIndicator.ts +++ b/src/TypingIndicator.ts @@ -1,6 +1,7 @@ import * as Ably from 'ably'; import { TypingIndicatorEvents } from './events.js'; +import { Logger } from './logger.js'; import { DefaultSubscriptionManager, SubscriptionManager } from './SubscriptionManager.js'; import EventEmitter from './utils/EventEmitter.js'; import { DEFAULT_CHANNEL_OPTIONS } from './version.js'; @@ -110,6 +111,7 @@ export class DefaultTypingIndicator extends EventEmitter; private readonly _typingIndicatorsChannelName: string; private readonly _managedChannel: SubscriptionManager; + private readonly _logger: Logger; // Timeout for typing indicator private readonly _typingTimeoutMs: number; @@ -122,7 +124,7 @@ export class DefaultTypingIndicator extends EventEmitter + new DefaultRoom(randomRoomId(), context.realtime, context.chatApi, { typingTimeoutMs: 1000 }, makeTestLogger()); + describe('Messages', () => { beforeEach((context) => { context.realtime = new Ably.Realtime({ clientId: 'clientId', key: 'key' }); - context.chatApi = new ChatApi(context.realtime); context.channelLevelListeners = new Map, string[]>(); + context.chatApi = new ChatApi(context.realtime, makeTestLogger()); const channel = context.realtime.channels.get('roomId'); vi.spyOn(channel, 'subscribe').mockImplementation( @@ -66,7 +71,7 @@ describe('Messages', () => { createdAt: timestamp, }); - const room = new DefaultRoom('coffee-room-chat', realtime, chatApi, { typingTimeoutMs: 300 }); + const room = new DefaultRoom('coffee-room-chat', realtime, chatApi, { typingTimeoutMs: 300 }, makeTestLogger()); const messagePromise = room.messages.send('hello there'); const message = await messagePromise; @@ -87,9 +92,7 @@ describe('Messages', () => { it('subscribing to messages should work live', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe((rawMsg) => { const message = rawMsg.message; @@ -100,7 +103,7 @@ describe('Messages', () => { content: 'may the fourth be with you', clientId: 'yoda', createdAt: publishTimestamp, - roomId: roomId, + roomId: room.roomId, }), ); } catch (err) { @@ -128,10 +131,9 @@ describe('Messages', () => { }); it('attach its internal listener according to subscriptions', async (context) => { - const { realtime, chatApi, channelLevelListeners } = context; + const { channelLevelListeners } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); const listener1 = () => {}; const listener2 = () => {}; @@ -158,9 +160,7 @@ describe('Messages', () => { it('should raise an error if no data provided with incoming message', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without data')); @@ -187,9 +187,7 @@ describe('Messages', () => { it('should raise an error if no clientId provided with incoming message', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without clientId')); @@ -218,9 +216,7 @@ describe('Messages', () => { it('should raise an error if no extras provided with incoming message', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without extras')); @@ -247,9 +243,7 @@ describe('Messages', () => { it('should raise an error if no timeserial provided with incoming message', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without clientId')); @@ -277,9 +271,7 @@ describe('Messages', () => { it('should raise an error if no content in incoming message', (context) => new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without content')); @@ -306,9 +298,7 @@ describe('Messages', () => { it('should raise an error if no timestamp provided with incoming message', (context) => new Promise((done, reject) => { - const { chatApi, realtime } = context; - const roomId = randomRoomId(); - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 300 }); + const room = makeRoom(context); room.messages .subscribe(() => { reject(new Error('should not have received message without timestamp')); diff --git a/test/Occupany.test.ts b/test/Occupany.test.ts index c71c404e..71742726 100644 --- a/test/Occupany.test.ts +++ b/test/Occupany.test.ts @@ -5,7 +5,7 @@ import { ChatApi } from '../src/ChatApi.js'; import { OccupancyEvent } from '../src/Occupancy.js'; import { DefaultRoom } from '../src/Room.js'; import { randomRoomId } from './helper/identifier.js'; -import { ablyRealtimeClient } from './helper/realtimeClient.js'; +import { makeTestLogger } from './helper/logger.js'; interface TestContext { realtime: Ably.Realtime; @@ -18,10 +18,14 @@ interface TestContext { vi.mock('ably'); +// Helper function to create a room +const makeRoom = (context: TestContext) => + new DefaultRoom(context.roomId, context.realtime, context.chatApi, { typingTimeoutMs: 1000 }, makeTestLogger()); + describe('Occupancy', () => { beforeEach((context) => { context.realtime = new Ably.Realtime({ clientId: 'clientId', key: 'key' }); - context.chatApi = new ChatApi(context.realtime); + context.chatApi = new ChatApi(context.realtime, makeTestLogger()); context.roomId = randomRoomId(); context.channelLevelListeners = new Map, string[]>(); context.currentChannelOptions = {}; @@ -69,9 +73,9 @@ describe('Occupancy', () => { }); it('registers its internal listener as subscriptions change', async (context) => { - const { realtime, chatApi, roomId, channelLevelListeners } = context; + const { channelLevelListeners } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); const listener1 = () => {}; const listener2 = () => {}; @@ -96,9 +100,7 @@ describe('Occupancy', () => { }); it('enables channel occupancy as subscriptions change', async (context) => { - const { realtime, chatApi, roomId } = context; - - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); const listener1 = () => {}; const listener2 = () => {}; @@ -121,8 +123,7 @@ describe('Occupancy', () => { it('receives occupancy updates', async (context) => new Promise((done, reject) => { - const { realtime, chatApi, roomId } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); room.occupancy .subscribe((event: OccupancyEvent) => { try { @@ -153,8 +154,7 @@ describe('Occupancy', () => { it('receives occupancy updates zero values', async (context) => new Promise((done, reject) => { - const { realtime, chatApi, roomId } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); room.occupancy .subscribe((event: OccupancyEvent) => { try { @@ -185,9 +185,7 @@ describe('Occupancy', () => { it('raises an error if no connections in occupancy data', async (context) => new Promise((done, reject) => { - ablyRealtimeClient(); - const { realtime, chatApi, roomId } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); room.occupancy .subscribe(() => { reject('should not have received occupancy event without connections'); @@ -213,9 +211,7 @@ describe('Occupancy', () => { it('raises an error if no presenceMembers in occupancy data', async (context) => new Promise((done, reject) => { - ablyRealtimeClient(); - const { realtime, chatApi, roomId } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); room.occupancy .subscribe(() => { reject('should not have received occupancy event without presenceMembers'); @@ -241,9 +237,7 @@ describe('Occupancy', () => { it('raises an error if no metrics in occupancy data', async (context) => new Promise((done, reject) => { - ablyRealtimeClient(); - const { realtime, chatApi, roomId } = context; - const room = new DefaultRoom(roomId, realtime, chatApi, { typingTimeoutMs: 1000 }); + const room = makeRoom(context); room.occupancy .subscribe(() => { reject('should not have received occupancy event without metrics'); diff --git a/test/Presence.integration.test.ts b/test/Presence.integration.test.ts index d558a568..f55348d6 100644 --- a/test/Presence.integration.test.ts +++ b/test/Presence.integration.test.ts @@ -3,11 +3,13 @@ import * as Ably from 'ably'; import { PresenceAction, Realtime } from 'ably'; import { beforeEach, describe, expect, it } from 'vitest'; +import { DefaultClientOptions } from '../src/config.js'; import { PresenceEvents } from '../src/events.js'; import { PresenceData, PresenceEvent } from '../src/Presence.js'; import { Room } from '../src/Room.js'; import { DefaultRooms, Rooms } from '../src/Rooms.js'; import { randomRoomId } from './helper/identifier.js'; +import { makeTestLogger } from './helper/logger.js'; import { ablyRealtimeClient } from './helper/realtimeClient.js'; // Define the test context interface @@ -23,7 +25,7 @@ describe('UserPresence', { timeout: 10000 }, () => { beforeEach(async (context) => { context.realtime = ablyRealtimeClient(); const roomId = randomRoomId(); - context.chat = new DefaultRooms(context.realtime); + context.chat = new DefaultRooms(context.realtime, DefaultClientOptions, makeTestLogger()); context.defaultTestClientId = context.realtime.auth.clientId; context.chatRoom = context.chat.get(roomId); }); diff --git a/test/RoomReactions.test.ts b/test/RoomReactions.test.ts index fee3779a..efa4fadb 100644 --- a/test/RoomReactions.test.ts +++ b/test/RoomReactions.test.ts @@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ChatApi } from '../src/ChatApi.js'; import { DefaultClientOptions } from '../src/config.js'; import { DefaultRoom } from '../src/Room.js'; +import { makeTestLogger } from './helper/logger.js'; interface TestContext { realtime: Ably.Realtime; @@ -20,7 +21,7 @@ describe('Reactions', () => { const clientId = 'd.vader'; context.realtime = new Ably.Realtime({ clientId: clientId, key: 'key' }); - context.chatApi = new ChatApi(context.realtime); + context.chatApi = new ChatApi(context.realtime, makeTestLogger()); context.publishTimestamp = new Date(); context.setPublishTimestamp = (date: Date) => { @@ -66,7 +67,7 @@ describe('Reactions', () => { new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); const { chatApi, realtime } = context; - const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions); + const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions, makeTestLogger()); room.reactions .subscribe((reaction) => { @@ -103,7 +104,7 @@ describe('Reactions', () => { new Promise((done, reject) => { const publishTimestamp = new Date().getTime(); const { chatApi, realtime } = context; - const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions); + const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions, makeTestLogger()); room.reactions .subscribe((reaction) => { @@ -141,7 +142,7 @@ describe('Reactions', () => { it('should be able to send a reaction and see it back on the realtime channel', (context) => new Promise((done, reject) => { const { chatApi, realtime } = context; - const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions); + const room = new DefaultRoom('abcd', realtime, chatApi, DefaultClientOptions, makeTestLogger()); room.reactions .subscribe((reaction) => { diff --git a/test/helper/logger.ts b/test/helper/logger.ts new file mode 100644 index 00000000..ce221d1f --- /dev/null +++ b/test/helper/logger.ts @@ -0,0 +1,6 @@ +import { LogLevel, Logger, makeLogger } from '../../src/logger.js'; + +export const makeTestLogger = (): Logger => { + const level = (process.env.VITE_TEST_LOG_LEVEL as LogLevel) ?? LogLevel.silent; + return makeLogger({ logLevel: level, typingTimeoutMs: 1000 }); +};