Skip to content

Commit bd9b08e

Browse files
hannojgV3RON
andauthored
change: channel client-logs through jest result console (#115)
## Description <!-- Provide a general summary of your changes --> When enabling client logs they were just streamed through the console making it hard to understand to which test they belong. With this change we add the logs to the jestResult.console. **Before** <img width="1395" height="438" alt="Screenshot 2026-05-12 at 11 18 20" src="https://github.com/user-attachments/assets/0aeb5d25-b2ce-4670-9a29-50a92095a791" /> **After** The logs show in the jest formatting as you might know it from other jest envs: <img width="1693" height="1054" alt="Screenshot 2026-05-12 at 11 20 36" src="https://github.com/user-attachments/assets/245a5b81-6be5-4311-84d8-fa7ea3c634a4" /> ## Related Issue <!--- This project only accepts pull requests related to open issues --> <!--- If suggesting a new feature or change, please discuss it in an issue first --> <!--- If fixing a bug, there should be an issue describing it with steps to reproduce --> <!--- Please link to the issue here: --> / ## Context <!-- Why this feature was implemented in this particular way? --> <!-- Is there anything reviewer needs to know before conducting code review? --> / ## Testing <!-- Please describe how you tested your changes --> Tested locally in playground app. --------- Co-authored-by: Szymon Chmal <szymon@chmal.it>
1 parent 771ac83 commit bd9b08e

9 files changed

Lines changed: 217 additions & 155 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
__default__: patch
3+
---
4+
5+
When `forwardClientLogs` is enabled, Harness continues attaching device `console` output to the active test result, while keeping the internal log-capture path simpler and easier to maintain.

packages/config/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ export const ConfigSchema = z
111111
.optional()
112112
.default(false)
113113
.describe(
114-
'Enable forwarding of console.log, console.warn, console.error, and other console method calls from the React Native app to the terminal. ' +
115-
'When enabled, all console output from your app will be displayed in the test runner terminal with styled level indicators (log, warn, error).'
114+
'Enable forwarding of console.log, console.warn, console.error, and other console method calls from the React Native app during the active test run. ' +
115+
"When enabled, app console output is attached to the active test result's console output."
116116
),
117117

118118
// Deprecated property - used for migration detection

packages/jest/src/__tests__/client-log-handler.test.ts

Lines changed: 117 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
22
import {
3+
createClientLogCollector,
34
formatClientLogMessage,
4-
formatClientLogLine,
5-
handleClientLogEvent,
6-
createClientLogListener,
5+
formatClientLogEntry,
76
type ClientLogEvent,
87
} from '../client-log-handler.js';
98

10-
// Mock the log function
11-
vi.mock('../logs.js', () => ({
12-
log: vi.fn(),
13-
}));
14-
15-
import { log } from '../logs.js';
16-
179
describe('client-log-handler', () => {
1810
beforeEach(() => {
1911
vi.clearAllMocks();
@@ -98,7 +90,11 @@ describe('client-log-handler', () => {
9890
});
9991

10092
it('should handle multiple substitutions', () => {
101-
const result = formatClientLogMessage(['Hello %s, you have %d messages', 'Alice', 5]);
93+
const result = formatClientLogMessage([
94+
'Hello %s, you have %d messages',
95+
'Alice',
96+
5,
97+
]);
10298
expect(result).toBe('Hello Alice, you have 5 messages');
10399
});
104100

@@ -120,22 +116,30 @@ describe('client-log-handler', () => {
120116
});
121117

122118
it('should append extra arguments after substitution', () => {
123-
const result = formatClientLogMessage(['Hello %s', 'world', 'extra', 'args']);
119+
const result = formatClientLogMessage([
120+
'Hello %s',
121+
'world',
122+
'extra',
123+
'args',
124+
]);
124125
expect(result).toBe('Hello world extra args');
125126
});
126127
});
127128
});
128129

129-
describe('formatClientLogLine', () => {
130+
describe('formatClientLogEntry', () => {
130131
it('should format log level event', () => {
131132
const event: ClientLogEvent = {
132133
type: 'client_log',
133134
level: 'log',
134135
data: ['Test message'],
135136
};
136-
const result = formatClientLogLine(event);
137-
// The result will contain ANSI codes for styling, so we check it contains the message
138-
expect(result).toContain('Test message');
137+
const result = formatClientLogEntry(event);
138+
expect(result).toEqual({
139+
message: 'Test message',
140+
origin: '',
141+
type: 'log',
142+
});
139143
});
140144

141145
it('should format error level event', () => {
@@ -144,8 +148,12 @@ describe('client-log-handler', () => {
144148
level: 'error',
145149
data: ['Error occurred'],
146150
};
147-
const result = formatClientLogLine(event);
148-
expect(result).toContain('Error occurred');
151+
const result = formatClientLogEntry(event);
152+
expect(result).toEqual({
153+
message: 'Error occurred',
154+
origin: '',
155+
type: 'error',
156+
});
149157
});
150158

151159
it('should format warn level event', () => {
@@ -154,8 +162,12 @@ describe('client-log-handler', () => {
154162
level: 'warn',
155163
data: ['Warning message'],
156164
};
157-
const result = formatClientLogLine(event);
158-
expect(result).toContain('Warning message');
165+
const result = formatClientLogEntry(event);
166+
expect(result).toEqual({
167+
message: 'Warning message',
168+
origin: '',
169+
type: 'warn',
170+
});
159171
});
160172

161173
it('should format info level event', () => {
@@ -164,8 +176,12 @@ describe('client-log-handler', () => {
164176
level: 'info',
165177
data: ['Info message'],
166178
};
167-
const result = formatClientLogLine(event);
168-
expect(result).toContain('Info message');
179+
const result = formatClientLogEntry(event);
180+
expect(result).toEqual({
181+
message: 'Info message',
182+
origin: '',
183+
type: 'info',
184+
});
169185
});
170186

171187
it('should format debug level event', () => {
@@ -174,8 +190,12 @@ describe('client-log-handler', () => {
174190
level: 'debug',
175191
data: ['Debug message'],
176192
};
177-
const result = formatClientLogLine(event);
178-
expect(result).toContain('Debug message');
193+
const result = formatClientLogEntry(event);
194+
expect(result).toEqual({
195+
message: 'Debug message',
196+
origin: '',
197+
type: 'debug',
198+
});
179199
});
180200

181201
it('should format trace level event', () => {
@@ -184,8 +204,12 @@ describe('client-log-handler', () => {
184204
level: 'trace',
185205
data: ['Trace message'],
186206
};
187-
const result = formatClientLogLine(event);
188-
expect(result).toContain('Trace message');
207+
const result = formatClientLogEntry(event);
208+
expect(result).toEqual({
209+
message: 'Trace message',
210+
origin: '',
211+
type: 'log',
212+
});
189213
});
190214

191215
it('should handle multiple data items', () => {
@@ -194,135 +218,147 @@ describe('client-log-handler', () => {
194218
level: 'log',
195219
data: ['User:', { id: 1, name: 'Test' }],
196220
};
197-
const result = formatClientLogLine(event);
198-
expect(result).toContain('User:');
199-
expect(result).toContain('id');
200-
expect(result).toContain('Test');
221+
const result = formatClientLogEntry(event);
222+
expect(result?.message).toContain('User:');
223+
expect(result?.message).toContain('id');
224+
expect(result?.message).toContain('Test');
201225
});
202226
});
203227

204-
describe('handleClientLogEvent', () => {
205-
it('should handle client_log events and call log', () => {
228+
describe('createClientLogCollector', () => {
229+
it('collects client_log events and flushes them', () => {
230+
const collector = createClientLogCollector();
206231
const event: ClientLogEvent = {
207232
type: 'client_log',
208233
level: 'log',
209234
data: ['Test message'],
210235
};
211236

212-
const result = handleClientLogEvent(event);
237+
collector.handleEvent(event);
213238

214-
expect(result).toBe(true);
215-
expect(log).toHaveBeenCalledTimes(1);
216-
expect(log).toHaveBeenCalledWith(expect.stringContaining('Test message'));
239+
expect(collector.flush()).toEqual([
240+
{
241+
message: 'Test message',
242+
origin: '',
243+
type: 'log',
244+
},
245+
]);
217246
});
218247

219-
it('should return false for non-client_log events', () => {
248+
it('returns an empty array for non-client_log events', () => {
249+
const collector = createClientLogCollector();
220250
// eslint-disable-next-line @typescript-eslint/no-explicit-any
221251
const event = { type: 'bundle_build_started' } as any;
222252

223-
const result = handleClientLogEvent(event);
253+
collector.handleEvent(event);
224254

225-
expect(result).toBe(false);
226-
expect(log).not.toHaveBeenCalled();
255+
expect(collector.flush()).toEqual([]);
227256
});
228257

229-
it('should handle error level logs', () => {
258+
it('collects error level logs', () => {
259+
const collector = createClientLogCollector();
230260
const event: ClientLogEvent = {
231261
type: 'client_log',
232262
level: 'error',
233263
data: ['Something went wrong'],
234264
};
235265

236-
handleClientLogEvent(event);
266+
collector.handleEvent(event);
237267

238-
expect(log).toHaveBeenCalledWith(
239-
expect.stringContaining('Something went wrong')
240-
);
268+
expect(collector.flush()).toEqual([
269+
{
270+
message: 'Something went wrong',
271+
origin: '',
272+
type: 'error',
273+
},
274+
]);
241275
});
242276

243-
it('should handle warn level logs', () => {
277+
it('collects warn level logs', () => {
278+
const collector = createClientLogCollector();
244279
const event: ClientLogEvent = {
245280
type: 'client_log',
246281
level: 'warn',
247282
data: ['Deprecation warning'],
248283
};
249284

250-
handleClientLogEvent(event);
285+
collector.handleEvent(event);
251286

252-
expect(log).toHaveBeenCalledWith(
253-
expect.stringContaining('Deprecation warning')
254-
);
287+
expect(collector.flush()).toEqual([
288+
{
289+
message: 'Deprecation warning',
290+
origin: '',
291+
type: 'warn',
292+
},
293+
]);
255294
});
256-
});
257295

258-
describe('createClientLogListener', () => {
259-
it('should create a listener function', () => {
260-
const listener = createClientLogListener();
261-
expect(typeof listener).toBe('function');
262-
});
296+
it('flush clears the buffer', () => {
297+
const collector = createClientLogCollector();
263298

264-
it('should handle client_log events when called', () => {
265-
const listener = createClientLogListener();
266-
const event: ClientLogEvent = {
299+
collector.handleEvent({
267300
type: 'client_log',
268301
level: 'info',
269302
data: ['Listener test'],
270-
};
271-
272-
listener(event);
303+
});
273304

274-
expect(log).toHaveBeenCalledWith(expect.stringContaining('Listener test'));
305+
expect(collector.flush()).toEqual([
306+
{
307+
message: 'Listener test',
308+
origin: '',
309+
type: 'info',
310+
},
311+
]);
312+
expect(collector.flush()).toEqual([]);
275313
});
276314

277-
it('should ignore non-client_log events', () => {
278-
const listener = createClientLogListener();
315+
it('ignores non-client_log events', () => {
316+
const collector = createClientLogCollector();
279317
// eslint-disable-next-line @typescript-eslint/no-explicit-any
280318
const event = { type: 'initialize_done' } as any;
281319

282-
listener(event);
320+
collector.handleEvent(event);
283321

284-
expect(log).not.toHaveBeenCalled();
322+
expect(collector.flush()).toEqual([]);
285323
});
286-
});
287324

288-
describe('group events', () => {
289-
it('should handle group events without logging', () => {
325+
it('ignores group events', () => {
326+
const collector = createClientLogCollector();
290327
const event: ClientLogEvent = {
291328
type: 'client_log',
292329
level: 'group',
293330
data: ['Group label'],
294331
};
295332

296-
const result = handleClientLogEvent(event);
333+
collector.handleEvent(event);
297334

298-
expect(result).toBe(true);
299-
expect(log).not.toHaveBeenCalled();
335+
expect(collector.flush()).toEqual([]);
300336
});
301337

302-
it('should handle groupCollapsed events without logging', () => {
338+
it('ignores groupCollapsed events', () => {
339+
const collector = createClientLogCollector();
303340
const event: ClientLogEvent = {
304341
type: 'client_log',
305342
level: 'groupCollapsed',
306343
data: ['Collapsed'],
307344
};
308345

309-
const result = handleClientLogEvent(event);
346+
collector.handleEvent(event);
310347

311-
expect(result).toBe(true);
312-
expect(log).not.toHaveBeenCalled();
348+
expect(collector.flush()).toEqual([]);
313349
});
314350

315-
it('should handle groupEnd events without logging', () => {
351+
it('ignores groupEnd events', () => {
352+
const collector = createClientLogCollector();
316353
const event: ClientLogEvent = {
317354
type: 'client_log',
318355
level: 'groupEnd',
319356
data: [],
320357
};
321358

322-
const result = handleClientLogEvent(event);
359+
collector.handleEvent(event);
323360

324-
expect(result).toBe(true);
325-
expect(log).not.toHaveBeenCalled();
361+
expect(collector.flush()).toEqual([]);
326362
});
327363
});
328364
});

0 commit comments

Comments
 (0)