Skip to content

Commit 12f3007

Browse files
authored
fix(ember): Make implementation field optional (hash routes) (#18564)
Related PR: #3195 Closes #18543
1 parent 3fda84d commit 12f3007

File tree

3 files changed

+106
-4
lines changed

3 files changed

+106
-4
lines changed

packages/ember/addon/instance-initializers/sentry-performance.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ function getTransitionInformation(
8484
};
8585
}
8686

87-
function getLocationURL(location: EmberRouterMain['location']): string {
87+
// Only exported for testing
88+
export function _getLocationURL(location: EmberRouterMain['location']): string {
8889
if (!location?.getURL || !location?.formatURL) {
8990
return '';
9091
}
9192
const url = location.formatURL(location.getURL());
9293

93-
if (location.implementation === 'hash') {
94+
// `implementation` is optional in Ember's predefined location types, so we also check if the URL starts with '#'.
95+
if (location.implementation === 'hash' || url.startsWith('#')) {
9496
return `${location.rootURL}${url}`;
9597
}
9698
return url;
@@ -110,7 +112,7 @@ export function _instrumentEmberRouter(
110112

111113
// Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred.
112114
const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {};
113-
const url = getLocationURL(location);
115+
const url = _getLocationURL(location);
114116

115117
const client = getClient<BrowserClient>();
116118

packages/ember/addon/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface EmberRouterMain {
2929
location: {
3030
getURL?: () => string;
3131
formatURL?: (url: string) => string;
32-
implementation: string;
32+
implementation?: string;
3333
rootURL: string;
3434
};
3535
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type { EmberRouterMain } from '@sentry/ember/addon/types';
2+
import { _getLocationURL } from '@sentry/ember/instance-initializers/sentry-performance';
3+
import { setupTest } from 'ember-qunit';
4+
import { module, test } from 'qunit';
5+
import type { SentryTestContext } from '../helpers/setup-sentry';
6+
import { setupSentryTest } from '../helpers/setup-sentry';
7+
8+
module('Unit | Utility | instrument-router-location', function (hooks) {
9+
setupTest(hooks);
10+
setupSentryTest(hooks);
11+
12+
test('getLocationURL handles hash location without implementation field', function (this: SentryTestContext, assert) {
13+
// This simulates the default Ember HashLocation which doesn't include the implementation field
14+
const mockLocation: EmberRouterMain['location'] = {
15+
getURL: () => '#/test-route',
16+
formatURL: (url: string) => url,
17+
rootURL: '/',
18+
};
19+
20+
const result = _getLocationURL(mockLocation);
21+
assert.strictEqual(result, '/#/test-route', 'Should prepend rootURL to hash URL when implementation is not set');
22+
});
23+
24+
test('_getLocationURL handles hash location with implementation field', function (this: SentryTestContext, assert) {
25+
// This simulates a custom HashLocation with explicit implementation field
26+
const mockLocation: EmberRouterMain['location'] = {
27+
getURL: () => '#/test-route',
28+
formatURL: (url: string) => url,
29+
implementation: 'hash',
30+
rootURL: '/',
31+
};
32+
33+
const result = _getLocationURL(mockLocation);
34+
assert.strictEqual(result, '/#/test-route', 'Should prepend rootURL to hash URL when implementation is hash');
35+
});
36+
37+
test('_getLocationURL handles history location', function (this: SentryTestContext, assert) {
38+
// This simulates a history location
39+
const mockLocation: EmberRouterMain['location'] = {
40+
getURL: () => '/test-route',
41+
formatURL: (url: string) => url,
42+
implementation: 'history',
43+
rootURL: '/',
44+
};
45+
46+
const result = _getLocationURL(mockLocation);
47+
assert.strictEqual(result, '/test-route', 'Should return URL as-is for non-hash locations');
48+
});
49+
50+
test('_getLocationURL handles none location type', function (this: SentryTestContext, assert) {
51+
// This simulates a 'none' location (often used in tests)
52+
const mockLocation: EmberRouterMain['location'] = {
53+
getURL: () => '',
54+
formatURL: (url: string) => url,
55+
implementation: 'none',
56+
rootURL: '/',
57+
};
58+
59+
const result = _getLocationURL(mockLocation);
60+
assert.strictEqual(result, '', 'Should return empty string when URL is empty');
61+
});
62+
63+
test('_getLocationURL handles custom rootURL for hash location', function (this: SentryTestContext, assert) {
64+
// Test with non-root rootURL
65+
const mockLocation: EmberRouterMain['location'] = {
66+
getURL: () => '#/test-route',
67+
formatURL: (url: string) => url,
68+
rootURL: '/my-app/',
69+
};
70+
71+
const result = _getLocationURL(mockLocation);
72+
assert.strictEqual(
73+
result,
74+
'/my-app/#/test-route',
75+
'Should prepend custom rootURL to hash URL when implementation is not set',
76+
);
77+
});
78+
79+
test('_getLocationURL handles location without getURL method', function (this: SentryTestContext, assert) {
80+
// This simulates an incomplete location object
81+
const mockLocation: EmberRouterMain['location'] = {
82+
formatURL: (url: string) => url,
83+
rootURL: '/',
84+
};
85+
86+
const result = _getLocationURL(mockLocation);
87+
assert.strictEqual(result, '', 'Should return empty string when getURL is not available');
88+
});
89+
90+
test('_getLocationURL handles location without formatURL method', function (this: SentryTestContext, assert) {
91+
// This simulates an incomplete location object
92+
const mockLocation: EmberRouterMain['location'] = {
93+
getURL: () => '#/test-route',
94+
rootURL: '/',
95+
};
96+
97+
const result = _getLocationURL(mockLocation);
98+
assert.strictEqual(result, '', 'Should return empty string when formatURL is not available');
99+
});
100+
});

0 commit comments

Comments
 (0)