@@ -70,7 +70,8 @@ describe('_addMeasureSpans', () => {
7070 name : 'measure-1' ,
7171 duration : 10 ,
7272 startTime : 12 ,
73- } as PerformanceEntry ;
73+ detail : null ,
74+ } as PerformanceMeasure ;
7475
7576 const timeOrigin = 100 ;
7677 const startTime = 23 ;
@@ -106,7 +107,8 @@ describe('_addMeasureSpans', () => {
106107 name : 'measure-1' ,
107108 duration : 10 ,
108109 startTime : 12 ,
109- } as PerformanceEntry ;
110+ detail : null ,
111+ } as PerformanceMeasure ;
110112
111113 const timeOrigin = 100 ;
112114 const startTime = 23 ;
@@ -116,6 +118,165 @@ describe('_addMeasureSpans', () => {
116118
117119 expect ( spans ) . toHaveLength ( 0 ) ;
118120 } ) ;
121+
122+ it ( 'adds measure spans with primitive detail' , ( ) => {
123+ const spans : Span [ ] = [ ] ;
124+
125+ getClient ( ) ?. on ( 'spanEnd' , span => {
126+ spans . push ( span ) ;
127+ } ) ;
128+
129+ const entry = {
130+ entryType : 'measure' ,
131+ name : 'measure-1' ,
132+ duration : 10 ,
133+ startTime : 12 ,
134+ detail : 'test-detail' ,
135+ } as PerformanceMeasure ;
136+
137+ const timeOrigin = 100 ;
138+ const startTime = 23 ;
139+ const duration = 356 ;
140+
141+ _addMeasureSpans ( span , entry , startTime , duration , timeOrigin ) ;
142+
143+ expect ( spans ) . toHaveLength ( 1 ) ;
144+ expect ( spanToJSON ( spans [ 0 ] ! ) ) . toEqual (
145+ expect . objectContaining ( {
146+ description : 'measure-1' ,
147+ start_timestamp : timeOrigin + startTime ,
148+ timestamp : timeOrigin + startTime + duration ,
149+ op : 'measure' ,
150+ origin : 'auto.resource.browser.metrics' ,
151+ data : {
152+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'measure' ,
153+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.resource.browser.metrics' ,
154+ 'sentry.browser.measure.detail' : 'test-detail' ,
155+ } ,
156+ } ) ,
157+ ) ;
158+ } ) ;
159+
160+ it ( 'adds measure spans with object detail' , ( ) => {
161+ const spans : Span [ ] = [ ] ;
162+
163+ getClient ( ) ?. on ( 'spanEnd' , span => {
164+ spans . push ( span ) ;
165+ } ) ;
166+
167+ const detail = {
168+ component : 'Button' ,
169+ action : 'click' ,
170+ metadata : { id : 123 } ,
171+ } ;
172+
173+ const entry = {
174+ entryType : 'measure' ,
175+ name : 'measure-1' ,
176+ duration : 10 ,
177+ startTime : 12 ,
178+ detail,
179+ } as PerformanceMeasure ;
180+
181+ const timeOrigin = 100 ;
182+ const startTime = 23 ;
183+ const duration = 356 ;
184+
185+ _addMeasureSpans ( span , entry , startTime , duration , timeOrigin ) ;
186+
187+ expect ( spans ) . toHaveLength ( 1 ) ;
188+ expect ( spanToJSON ( spans [ 0 ] ! ) ) . toEqual (
189+ expect . objectContaining ( {
190+ description : 'measure-1' ,
191+ start_timestamp : timeOrigin + startTime ,
192+ timestamp : timeOrigin + startTime + duration ,
193+ op : 'measure' ,
194+ origin : 'auto.resource.browser.metrics' ,
195+ data : {
196+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'measure' ,
197+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.resource.browser.metrics' ,
198+ 'sentry.browser.measure.detail.component' : 'Button' ,
199+ 'sentry.browser.measure.detail.action' : 'click' ,
200+ 'sentry.browser.measure.detail.metadata' : JSON . stringify ( { id : 123 } ) ,
201+ } ,
202+ } ) ,
203+ ) ;
204+ } ) ;
205+
206+ it ( 'handles non-primitive detail values by stringifying them' , ( ) => {
207+ const spans : Span [ ] = [ ] ;
208+
209+ getClient ( ) ?. on ( 'spanEnd' , span => {
210+ spans . push ( span ) ;
211+ } ) ;
212+
213+ const detail = {
214+ component : 'Button' ,
215+ action : 'click' ,
216+ metadata : { id : 123 } ,
217+ callback : ( ) => { } ,
218+ } ;
219+
220+ const entry = {
221+ entryType : 'measure' ,
222+ name : 'measure-1' ,
223+ duration : 10 ,
224+ startTime : 12 ,
225+ detail,
226+ } as PerformanceMeasure ;
227+
228+ const timeOrigin = 100 ;
229+ const startTime = 23 ;
230+ const duration = 356 ;
231+
232+ _addMeasureSpans ( span , entry , startTime , duration , timeOrigin ) ;
233+
234+ expect ( spans ) . toHaveLength ( 1 ) ;
235+ const spanData = spanToJSON ( spans [ 0 ] ! ) . data ;
236+ expect ( spanData [ 'sentry.browser.measure.detail.component' ] ) . toBe ( 'Button' ) ;
237+ expect ( spanData [ 'sentry.browser.measure.detail.action' ] ) . toBe ( 'click' ) ;
238+ expect ( spanData [ 'sentry.browser.measure.detail.metadata' ] ) . toBe ( JSON . stringify ( { id : 123 } ) ) ;
239+ expect ( spanData [ 'sentry.browser.measure.detail.callback' ] ) . toBe ( JSON . stringify ( detail . callback ) ) ;
240+ } ) ;
241+
242+ it ( 'handles errors in object detail value stringification' , ( ) => {
243+ const spans : Span [ ] = [ ] ;
244+
245+ getClient ( ) ?. on ( 'spanEnd' , span => {
246+ spans . push ( span ) ;
247+ } ) ;
248+
249+ const circular : any = { } ;
250+ circular . self = circular ;
251+
252+ const detail = {
253+ component : 'Button' ,
254+ action : 'click' ,
255+ circular,
256+ } ;
257+
258+ const entry = {
259+ entryType : 'measure' ,
260+ name : 'measure-1' ,
261+ duration : 10 ,
262+ startTime : 12 ,
263+ detail,
264+ } as PerformanceMeasure ;
265+
266+ const timeOrigin = 100 ;
267+ const startTime = 23 ;
268+ const duration = 356 ;
269+
270+ // Should not throw
271+ _addMeasureSpans ( span , entry , startTime , duration , timeOrigin ) ;
272+
273+ expect ( spans ) . toHaveLength ( 1 ) ;
274+ const spanData = spanToJSON ( spans [ 0 ] ! ) . data ;
275+ expect ( spanData [ 'sentry.browser.measure.detail.component' ] ) . toBe ( 'Button' ) ;
276+ expect ( spanData [ 'sentry.browser.measure.detail.action' ] ) . toBe ( 'click' ) ;
277+ // The circular reference should be skipped
278+ expect ( spanData [ 'sentry.browser.measure.detail.circular' ] ) . toBeUndefined ( ) ;
279+ } ) ;
119280} ) ;
120281
121282describe ( '_addResourceSpans' , ( ) => {
@@ -464,7 +625,6 @@ describe('_addNavigationSpans', () => {
464625 transferSize : 14726 ,
465626 encodedBodySize : 14426 ,
466627 decodedBodySize : 67232 ,
467- responseStatus : 200 ,
468628 serverTiming : [ ] ,
469629 unloadEventStart : 0 ,
470630 unloadEventEnd : 0 ,
0 commit comments