From b9c4ca8a7a54a3301d655edf3333a665c9c0d8e3 Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 12:24:46 -0400 Subject: [PATCH 1/7] Keep anchors when switching versions --- app/controllers/project-version.js | 121 ++++++++++-------- .../unit/controllers/project-version-test.js | 37 ++++++ 2 files changed, 107 insertions(+), 51 deletions(-) diff --git a/app/controllers/project-version.js b/app/controllers/project-version.js index 01cbedcc..ac2e84ac 100644 --- a/app/controllers/project-version.js +++ b/app/controllers/project-version.js @@ -141,61 +141,80 @@ export default class ProjectVersionController extends Controller { @action updateProject(project, ver /*, component */) { - let projectVersionID = ver.compactVersion; - let endingRoute; - switch (this.router.currentRouteName) { - case 'project-version.classes.class': { - let className = this._getEncodedNameForCurrentClass(); - endingRoute = `classes/${className}`; - break; - } - case 'project-version.modules.module': { - let moduleName = encodeURIComponent(this.moduleController.model.name); - endingRoute = `modules/${moduleName}`; - break; - } - case 'project-version.namespaces.namespace': { - let namespaceName = this.namespaceController.model.name; - endingRoute = `namespaces/${namespaceName}`; - break; - } - default: - endingRoute = ''; - break; - } - // if the user is navigating to/from api versions >= 2.16, take them - // to the home page instead of trying to translate the url - let shouldConvertPackages = this._shouldConvertPackages( - ver, - this.projectService.version, + this.router.transitionTo( + findEndingRoute({ + project, + targetVersion: ver.id, + currentVersion: this.projectService.version, + currentRouteName: this.router.currentRouteName, + classModelName: this.classController?.model?.get('name'), + moduleModelName: this.moduleController?.model?.name, + namespaceModelName: this.namespaceController?.model?.name, + currentAnchor: window.location.hash, + }), ); - let isEmberProject = project === 'ember'; - - if (!isEmberProject || !shouldConvertPackages) { - this.router.transitionTo( - `/${project}/${projectVersionID}/${endingRoute}`, - ); - } else { - this.router.transitionTo(`/${project}/${projectVersionID}`); - } } +} - _getEncodedNameForCurrentClass() { - // escape any reserved characters for url, like slashes - return encodeURIComponent(this.classController.model.get('name')); +export function findEndingRoute({ + project, + targetVersion, + currentVersion, + currentRouteName, + classModelName, + moduleModelName, + namespaceModelName, + currentAnchor, +}) { + let projectVersionID = getCompactVersion(targetVersion); + let endingRoute; + switch (currentRouteName) { + case 'project-version.classes.class': { + let className = encodeURIComponent(classModelName); + endingRoute = `classes/${className}`; + break; + } + case 'project-version.modules.module': { + let moduleName = encodeURIComponent(moduleModelName); + endingRoute = `modules/${moduleName}`; + break; + } + case 'project-version.namespaces.namespace': { + let namespaceName = namespaceModelName; + endingRoute = `namespaces/${namespaceName}`; + break; + } + default: + endingRoute = ''; + break; } - // Input some version info, returns a boolean based on - // whether the user is switching versions for a 2.16 docs release or later. - // The urls for pre-2.16 classes and later packages are quite different - _shouldConvertPackages(targetVer, previousVer) { - let targetVersion = getCompactVersion(targetVer.id); - let previousVersion = getCompactVersion(previousVer); - let previousComparison = semverCompare(previousVersion, '2.16'); - let targetComparison = semverCompare(targetVersion, '2.16'); - return ( - (previousComparison < 0 && targetComparison >= 0) || - (previousComparison >= 0 && targetComparison < 0) - ); + let isEmberProject = project === 'ember'; + + // if the user is navigating to/from api versions >= 2.16, take them + // to the home page instead of trying to translate the url + let shouldConvertPackages = _shouldConvertPackages( + targetVersion, + currentVersion, + ); + + if (!isEmberProject || !shouldConvertPackages) { + return `/${project}/${projectVersionID}/${endingRoute}${currentAnchor}`; + } else { + return `/${project}/${projectVersionID}`; } } + +// Input some version info, returns a boolean based on +// whether the user is switching versions for a 2.16 docs release or later. +// The urls for pre-2.16 classes and later packages are quite different +function _shouldConvertPackages(targetVer, previousVer) { + let targetVersion = getCompactVersion(targetVer); + let previousVersion = getCompactVersion(previousVer); + let previousComparison = semverCompare(previousVersion, '2.16'); + let targetComparison = semverCompare(targetVersion, '2.16'); + return ( + (previousComparison < 0 && targetComparison >= 0) || + (previousComparison >= 0 && targetComparison < 0) + ); +} diff --git a/tests/unit/controllers/project-version-test.js b/tests/unit/controllers/project-version-test.js index 0795b761..2007b449 100644 --- a/tests/unit/controllers/project-version-test.js +++ b/tests/unit/controllers/project-version-test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; /* eslint-disable ember/no-restricted-resolver-tests */ import { setupTest } from 'ember-qunit'; +import { findEndingRoute } from 'ember-api-docs/controllers/project-version'; const moduleIds = [ 'ember-2.10.0-ember', @@ -45,4 +46,40 @@ module('Unit | Controller | project version', function (hooks) { let moduleNames = controller.getModuleRelationships('ember-2.10.1'); assert.deepEqual(moduleNames, expectedModuleNames); }); + + module('findEndingRoute', function () { + test('Maintains anchors', function (assert) { + let endingRoute = findEndingRoute({ + project: 'ember', + targetVersion: '6.4.0', + currentVersion: '6.5.0', + currentRouteName: 'project-version.classes.class', + classModelName: 'Component', + moduleModelName: null, + namespaceModelName: null, + currentAnchor: '#didInsertElement', + }); + + assert.strictEqual( + endingRoute, + '/ember/6.4/classes/Component#didInsertElement', + ); + + endingRoute = findEndingRoute({ + project: 'ember', + targetVersion: '6.4.0', + currentVersion: '6.5.0', + currentRouteName: 'project-version.modules.module', + classModelName: null, + moduleModelName: '@ember/application', + namespaceModelName: null, + currentAnchor: '#classes', + }); + + assert.strictEqual( + endingRoute, + '/ember/6.4/modules/%40ember%2Fapplication#classes', + ); + }); + }); }); From b4380a3d4c753b7a1fa1bd54094b7df387b9daa3 Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 13:56:21 -0400 Subject: [PATCH 2/7] Go to the version index when changing ember-data versions across the 4.0 boundary 4.0 is somewhat arbitrarily chosen as a spot where ember-data is substantially different organization before and after. --- app/controllers/project-version.js | 36 +++++++----- .../unit/controllers/project-version-test.js | 56 +++++++++++++++++++ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/app/controllers/project-version.js b/app/controllers/project-version.js index ac2e84ac..db5eb783 100644 --- a/app/controllers/project-version.js +++ b/app/controllers/project-version.js @@ -189,30 +189,36 @@ export function findEndingRoute({ break; } - let isEmberProject = project === 'ember'; - - // if the user is navigating to/from api versions >= 2.16, take them + // if the user is navigating to/from api versions Ember >= 2.16 or Ember Data >= 4.0, take them // to the home page instead of trying to translate the url - let shouldConvertPackages = _shouldConvertPackages( + if (shouldGoToVersionIndex(project, targetVersion, currentVersion)) { + return `/${project}/${projectVersionID}`; + } else { + return `/${project}/${projectVersionID}/${endingRoute}${currentAnchor}`; + } +} + +function shouldGoToVersionIndex(project, targetVersion, currentVersion) { + let boundaryVersion; + if (project === 'ember') { + boundaryVersion = '2.16'; + } else if (project === 'ember-data') { + boundaryVersion = '4.0'; + } + return isCrossingVersionBoundary( targetVersion, currentVersion, + boundaryVersion, ); - - if (!isEmberProject || !shouldConvertPackages) { - return `/${project}/${projectVersionID}/${endingRoute}${currentAnchor}`; - } else { - return `/${project}/${projectVersionID}`; - } } // Input some version info, returns a boolean based on -// whether the user is switching versions for a 2.16 docs release or later. -// The urls for pre-2.16 classes and later packages are quite different -function _shouldConvertPackages(targetVer, previousVer) { +// whether the user is switching versions for a release or later. +function isCrossingVersionBoundary(targetVer, previousVer, boundaryVersion) { let targetVersion = getCompactVersion(targetVer); let previousVersion = getCompactVersion(previousVer); - let previousComparison = semverCompare(previousVersion, '2.16'); - let targetComparison = semverCompare(targetVersion, '2.16'); + let previousComparison = semverCompare(previousVersion, boundaryVersion); + let targetComparison = semverCompare(targetVersion, boundaryVersion); return ( (previousComparison < 0 && targetComparison >= 0) || (previousComparison >= 0 && targetComparison < 0) diff --git a/tests/unit/controllers/project-version-test.js b/tests/unit/controllers/project-version-test.js index 2007b449..83973b09 100644 --- a/tests/unit/controllers/project-version-test.js +++ b/tests/unit/controllers/project-version-test.js @@ -81,5 +81,61 @@ module('Unit | Controller | project version', function (hooks) { '/ember/6.4/modules/%40ember%2Fapplication#classes', ); }); + + test('For ember project, it goes to version root when crossing 2.16 boundary', function (assert) { + let endingRoute = findEndingRoute({ + project: 'ember', + targetVersion: '2.15.0', + currentVersion: '2.16.0', + currentRouteName: 'project-version.classes.class', + classModelName: 'Component', + moduleModelName: null, + namespaceModelName: null, + currentAnchor: '#didInsertElement', + }); + + assert.strictEqual(endingRoute, '/ember/2.15'); + + endingRoute = findEndingRoute({ + project: 'ember', + targetVersion: '2.16.0', + currentVersion: '2.15.0', + currentRouteName: 'project-version.classes.class', + classModelName: 'Component', + moduleModelName: null, + namespaceModelName: null, + currentAnchor: '#didInsertElement', + }); + + assert.strictEqual(endingRoute, '/ember/2.16'); + }); + + test('For ember-data project, it goes to version root when crossing 4.0 boundary', function (assert) { + let endingRoute = findEndingRoute({ + project: 'ember-data', + targetVersion: '3.28.0', + currentVersion: '4.0.0', + currentRouteName: 'project-version.classes.class', + classModelName: 'Adapter', + moduleModelName: null, + namespaceModelName: null, + currentAnchor: '', + }); + + assert.strictEqual(endingRoute, '/ember-data/3.28'); + + endingRoute = findEndingRoute({ + project: 'ember-data', + targetVersion: '4.0.0', + currentVersion: '3.28.0', + currentRouteName: 'project-version.classes.class', + classModelName: 'DS.Adapter', + moduleModelName: null, + namespaceModelName: null, + currentAnchor: '', + }); + + assert.strictEqual(endingRoute, '/ember-data/4.0'); + }); }); }); From 572b4abaf5ae8cf5bcfa212aaf1cb2184616a9cf Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 15:04:07 -0400 Subject: [PATCH 3/7] Display a more helpful error when switching versions and a module or class is not found in the target version --- app/assets/styles.css | 1 + app/routes/project-version/classes/class.js | 20 +++++++++++- app/templates/error.hbs | 12 +++++-- tests/acceptance/switch-versions-test.js | 36 +++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/app/assets/styles.css b/app/assets/styles.css index 48bd2ae4..9af0470d 100644 --- a/app/assets/styles.css +++ b/app/assets/styles.css @@ -177,6 +177,7 @@ section.event { .whoops { display: flex; + flex-direction: column; justify-content: center; align-items: center; padding: var(--spacing-6); diff --git a/app/routes/project-version/classes/class.js b/app/routes/project-version/classes/class.js index 58ce7fad..93841db9 100644 --- a/app/routes/project-version/classes/class.js +++ b/app/routes/project-version/classes/class.js @@ -59,8 +59,26 @@ export default class ClassRoute extends Route { }); } - redirect(model) { + redirect(model, transition) { if (model.isError) { + // Transitioning to the same route, probably only changing version + // Could explicitly check by comparing transition.to and transition.from + if (transition.to.name === transition?.from?.name) { + const projectVersionRouteInfo = transition.to.find(function (item) { + return item.params?.project_version; + }); + const attemptedVersion = + projectVersionRouteInfo.params?.project_version; + const attemptedProject = projectVersionRouteInfo.params?.project; + let error = new Error( + `We could not find ${transition.to.localName} ${transition.to.params[transition.to.paramNames[0]]} in v${attemptedVersion} of ${attemptedProject}.`, + ); + error.status = 404; + error.attemptedProject = attemptedProject; + error.attemptedVersion = attemptedVersion; + throw error; + } + let error = new Error( 'Error retrieving model in routes/project-version/classes/class', ); diff --git a/app/templates/error.hbs b/app/templates/error.hbs index 0a9f9d3f..40bfb234 100644 --- a/app/templates/error.hbs +++ b/app/templates/error.hbs @@ -1,12 +1,20 @@
{{#if (eq this.model.status 404)}}

Ack! 404 friend, you're in the wrong place

-
+ {{#if this.model.attemptedVersion}} +

+ {{this.model.message}} +

+

+ Modules, classes, and functions sometimes move around or are renamed across versions. + Try the v{{this.model.attemptedVersion}} API docs index. +

+ {{else}}

This page wasn't found. Please try the API docs page. If you expected something else to be here, please file a ticket.

-
+ {{/if}} {{else}}

Whoops! Something went wrong. diff --git a/tests/acceptance/switch-versions-test.js b/tests/acceptance/switch-versions-test.js index 74134f1b..70462c26 100644 --- a/tests/acceptance/switch-versions-test.js +++ b/tests/acceptance/switch-versions-test.js @@ -279,4 +279,40 @@ module('Acceptance | version navigation', function (hooks) { 'navigated to v1.13 class', ); }); + + test('switching to a version that is missing a module or class offers a link to the API index for that version', async function (assert) { + const versionIndexLinkSelector = '[data-test-version-index-link]'; + await visit('/ember/6.4/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache'); + assert.strictEqual( + currentURL(), + '/ember/6.4/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache', + ); + + await selectChoose('.ember-power-select-trigger', '3.10'); + + assert + .dom() + .includesText( + 'We could not find module @glimmer/tracking/primitives/cache in v3.10 of ember.', + ); + + assert + .dom(versionIndexLinkSelector) + .includesText('v3.10') + .hasAttribute('href', '/ember/3.10'); + + await visit('/ember/3.0/classes/Ember.Debug'); + assert.strictEqual(currentURL(), '/ember/3.0/classes/Ember.Debug'); + + await selectChoose('.ember-power-select-trigger', '4.0'); + + assert + .dom() + .includesText('We could not find class Ember.Debug in v4.0 of ember.'); + + assert + .dom(versionIndexLinkSelector) + .includesText('v4.0') + .hasAttribute('href', '/ember/4.0'); + }); }); From e4a2b5a294b2c6c3e06e82f2535bfb468e75a20d Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 15:28:47 -0400 Subject: [PATCH 4/7] Enable maintain location when switching versions while on a function --- app/controllers/project-version.js | 35 +++---------------- tests/acceptance/switch-versions-test.js | 15 ++++++++ .../unit/controllers/project-version-test.js | 30 ++++------------ 3 files changed, 25 insertions(+), 55 deletions(-) diff --git a/app/controllers/project-version.js b/app/controllers/project-version.js index db5eb783..5a7807e2 100644 --- a/app/controllers/project-version.js +++ b/app/controllers/project-version.js @@ -141,15 +141,13 @@ export default class ProjectVersionController extends Controller { @action updateProject(project, ver /*, component */) { + const currentURL = this.router.currentURL; this.router.transitionTo( findEndingRoute({ project, targetVersion: ver.id, currentVersion: this.projectService.version, - currentRouteName: this.router.currentRouteName, - classModelName: this.classController?.model?.get('name'), - moduleModelName: this.moduleController?.model?.name, - namespaceModelName: this.namespaceController?.model?.name, + currentURL, currentAnchor: window.location.hash, }), ); @@ -160,41 +158,16 @@ export function findEndingRoute({ project, targetVersion, currentVersion, - currentRouteName, - classModelName, - moduleModelName, - namespaceModelName, + currentURL, currentAnchor, }) { let projectVersionID = getCompactVersion(targetVersion); - let endingRoute; - switch (currentRouteName) { - case 'project-version.classes.class': { - let className = encodeURIComponent(classModelName); - endingRoute = `classes/${className}`; - break; - } - case 'project-version.modules.module': { - let moduleName = encodeURIComponent(moduleModelName); - endingRoute = `modules/${moduleName}`; - break; - } - case 'project-version.namespaces.namespace': { - let namespaceName = namespaceModelName; - endingRoute = `namespaces/${namespaceName}`; - break; - } - default: - endingRoute = ''; - break; - } - // if the user is navigating to/from api versions Ember >= 2.16 or Ember Data >= 4.0, take them // to the home page instead of trying to translate the url if (shouldGoToVersionIndex(project, targetVersion, currentVersion)) { return `/${project}/${projectVersionID}`; } else { - return `/${project}/${projectVersionID}/${endingRoute}${currentAnchor}`; + return `${currentURL.replace(getCompactVersion(currentVersion), projectVersionID)}${currentAnchor}`; } } diff --git a/tests/acceptance/switch-versions-test.js b/tests/acceptance/switch-versions-test.js index 70462c26..eff1169b 100644 --- a/tests/acceptance/switch-versions-test.js +++ b/tests/acceptance/switch-versions-test.js @@ -165,6 +165,21 @@ module('Acceptance | version navigation', function (hooks) { ); }); + test('switching between versions on a function works', async function (assert) { + await visit('/ember/6.5/functions/@ember%2Fdebug/debug'); + assert.strictEqual( + currentURL(), + '/ember/6.5/functions/@ember%2Fdebug/debug', + ); + + await selectChoose('.ember-power-select-trigger', '6.4'); + + assert.strictEqual( + currentURL(), + '/ember/6.4/functions/@ember%2Fdebug/debug', + ); + }); + test('switching versions works if class name includes slashes', async function (assert) { await visit('/ember/3.4/classes/@ember%2Fobject%2Fcomputed'); assert.equal( diff --git a/tests/unit/controllers/project-version-test.js b/tests/unit/controllers/project-version-test.js index 83973b09..4d725840 100644 --- a/tests/unit/controllers/project-version-test.js +++ b/tests/unit/controllers/project-version-test.js @@ -53,10 +53,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '6.4.0', currentVersion: '6.5.0', - currentRouteName: 'project-version.classes.class', - classModelName: 'Component', - moduleModelName: null, - namespaceModelName: null, + currentURL: '/ember/6.5/classes/Component', currentAnchor: '#didInsertElement', }); @@ -69,10 +66,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '6.4.0', currentVersion: '6.5.0', - currentRouteName: 'project-version.modules.module', - classModelName: null, - moduleModelName: '@ember/application', - namespaceModelName: null, + currentURL: '/ember/6.5/modules/%40ember%2Fapplication', currentAnchor: '#classes', }); @@ -87,10 +81,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '2.15.0', currentVersion: '2.16.0', - currentRouteName: 'project-version.classes.class', - classModelName: 'Component', - moduleModelName: null, - namespaceModelName: null, + currentURL: '/ember/2.16/classes/Component', currentAnchor: '#didInsertElement', }); @@ -100,10 +91,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '2.16.0', currentVersion: '2.15.0', - currentRouteName: 'project-version.classes.class', - classModelName: 'Component', - moduleModelName: null, - namespaceModelName: null, + currentURL: '/ember/2.15/classes/Component', currentAnchor: '#didInsertElement', }); @@ -115,10 +103,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember-data', targetVersion: '3.28.0', currentVersion: '4.0.0', - currentRouteName: 'project-version.classes.class', - classModelName: 'Adapter', - moduleModelName: null, - namespaceModelName: null, + currentURL: '/ember-data/4.0/classes/Adapter', currentAnchor: '', }); @@ -128,10 +113,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember-data', targetVersion: '4.0.0', currentVersion: '3.28.0', - currentRouteName: 'project-version.classes.class', - classModelName: 'DS.Adapter', - moduleModelName: null, - namespaceModelName: null, + currentURL: '/ember-data/3.28/classes/DS.Adapter', currentAnchor: '', }); From 3083aeb74914d675fc4f3a3399c28f453d7a7f32 Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 17:21:32 -0400 Subject: [PATCH 5/7] Test and support switching versions from 'release' --- app/controllers/project-version.js | 4 +++- tests/acceptance/switch-versions-test.js | 17 +++++++++++++++++ tests/unit/controllers/project-version-test.js | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/controllers/project-version.js b/app/controllers/project-version.js index 5a7807e2..f46f93db 100644 --- a/app/controllers/project-version.js +++ b/app/controllers/project-version.js @@ -147,6 +147,7 @@ export default class ProjectVersionController extends Controller { project, targetVersion: ver.id, currentVersion: this.projectService.version, + currentUrlVersion: this.projectService.getUrlVersion(), currentURL, currentAnchor: window.location.hash, }), @@ -158,6 +159,7 @@ export function findEndingRoute({ project, targetVersion, currentVersion, + currentUrlVersion, currentURL, currentAnchor, }) { @@ -167,7 +169,7 @@ export function findEndingRoute({ if (shouldGoToVersionIndex(project, targetVersion, currentVersion)) { return `/${project}/${projectVersionID}`; } else { - return `${currentURL.replace(getCompactVersion(currentVersion), projectVersionID)}${currentAnchor}`; + return `${currentURL.replace(currentUrlVersion, projectVersionID)}${currentAnchor}`; } } diff --git a/tests/acceptance/switch-versions-test.js b/tests/acceptance/switch-versions-test.js index eff1169b..7fe12db5 100644 --- a/tests/acceptance/switch-versions-test.js +++ b/tests/acceptance/switch-versions-test.js @@ -38,6 +38,23 @@ module('Acceptance | version navigation', function (hooks) { ); }); + test('switching versions from release', async function (assert) { + await visit('/ember/release/modules/@glimmer%2Ftracking'); + + assert.equal( + currentURL(), + '/ember/release/modules/@glimmer%2Ftracking', + 'navigated to release', + ); + await selectChoose('.ember-power-select-trigger', '6.4'); + + assert.equal( + currentURL(), + '/ember/6.4/modules/@glimmer%2Ftracking', + 'navigated to v6.4 class', + ); + }); + test('switching namespace versions less than 2.16 should retain namespace page', async function (assert) { await visit('/ember/2.7/namespaces/Ember'); await waitForSettled(); diff --git a/tests/unit/controllers/project-version-test.js b/tests/unit/controllers/project-version-test.js index 4d725840..3ed03ef0 100644 --- a/tests/unit/controllers/project-version-test.js +++ b/tests/unit/controllers/project-version-test.js @@ -53,6 +53,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '6.4.0', currentVersion: '6.5.0', + currentUrlVersion: '6.5', currentURL: '/ember/6.5/classes/Component', currentAnchor: '#didInsertElement', }); @@ -66,6 +67,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '6.4.0', currentVersion: '6.5.0', + currentUrlVersion: '6.5', currentURL: '/ember/6.5/modules/%40ember%2Fapplication', currentAnchor: '#classes', }); @@ -81,6 +83,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '2.15.0', currentVersion: '2.16.0', + currentUrlVersion: '2.16', currentURL: '/ember/2.16/classes/Component', currentAnchor: '#didInsertElement', }); @@ -91,6 +94,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember', targetVersion: '2.16.0', currentVersion: '2.15.0', + currentUrlVersion: '2.15', currentURL: '/ember/2.15/classes/Component', currentAnchor: '#didInsertElement', }); @@ -103,6 +107,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember-data', targetVersion: '3.28.0', currentVersion: '4.0.0', + currentUrlVersion: '4.0', currentURL: '/ember-data/4.0/classes/Adapter', currentAnchor: '', }); @@ -113,6 +118,7 @@ module('Unit | Controller | project version', function (hooks) { project: 'ember-data', targetVersion: '4.0.0', currentVersion: '3.28.0', + currentUrlVersion: '3.28', currentURL: '/ember-data/3.28/classes/DS.Adapter', currentAnchor: '', }); From b407b79a0838964fbf81a72878ce3d91865fa0f0 Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Thu, 11 Sep 2025 17:22:06 -0400 Subject: [PATCH 6/7] Provide a useful message when switching versions from a function and the function does not exist in the target version --- .../project-version/functions/function.js | 24 ++++++++++++---- tests/acceptance/switch-versions-test.js | 28 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/app/routes/project-version/functions/function.js b/app/routes/project-version/functions/function.js index 3da43198..412f697d 100644 --- a/app/routes/project-version/functions/function.js +++ b/app/routes/project-version/functions/function.js @@ -17,7 +17,7 @@ export default class FunctionRoute extends Route { return model?.fn?.name; } - async model(params) { + async model(params, transition) { const pVParams = this.paramsFor('project-version'); const { project, project_version: compactVersion } = pVParams; @@ -38,10 +38,24 @@ export default class FunctionRoute extends Route { `${project}-${projectVersion}-${className}`.toLowerCase(), ); } catch (e) { - fnModule = await this.store.find( - 'namespace', - `${project}-${projectVersion}-${className}`.toLowerCase(), - ); + try { + fnModule = await this.store.find( + 'namespace', + `${project}-${projectVersion}-${className}`.toLowerCase(), + ); + } catch (e2) { + if (transition.to.name === transition?.from?.name) { + let error = new Error( + `We could not find function ${className}/${functionName} in v${compactVersion} of ${project}.`, + ); + error.status = 404; + error.attemptedProject = project; + error.attemptedVersion = compactVersion; + throw error; + } else { + throw e2; + } + } } return { diff --git a/tests/acceptance/switch-versions-test.js b/tests/acceptance/switch-versions-test.js index 7fe12db5..74537075 100644 --- a/tests/acceptance/switch-versions-test.js +++ b/tests/acceptance/switch-versions-test.js @@ -11,6 +11,8 @@ async function waitForSettled() { await settled(); } +const versionIndexLinkSelector = '[data-test-version-index-link]'; + module('Acceptance | version navigation', function (hooks) { setupApplicationTest(hooks); @@ -312,8 +314,7 @@ module('Acceptance | version navigation', function (hooks) { ); }); - test('switching to a version that is missing a module or class offers a link to the API index for that version', async function (assert) { - const versionIndexLinkSelector = '[data-test-version-index-link]'; + test('switching to a version that is missing a module offers a link to the API index for that version', async function (assert) { await visit('/ember/6.4/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache'); assert.strictEqual( currentURL(), @@ -332,7 +333,9 @@ module('Acceptance | version navigation', function (hooks) { .dom(versionIndexLinkSelector) .includesText('v3.10') .hasAttribute('href', '/ember/3.10'); + }); + test('switching to a version that is missing a class offers a link to the API index for that version', async function (assert) { await visit('/ember/3.0/classes/Ember.Debug'); assert.strictEqual(currentURL(), '/ember/3.0/classes/Ember.Debug'); @@ -347,4 +350,25 @@ module('Acceptance | version navigation', function (hooks) { .includesText('v4.0') .hasAttribute('href', '/ember/4.0'); }); + + test('switching to a version that is missing a function offers a link to the API index for that version', async function (assert) { + await visit('/ember/3.28/functions/@glimmer%2Ftracking/tracked'); + assert.strictEqual( + currentURL(), + '/ember/3.28/functions/@glimmer%2Ftracking/tracked', + ); + + await selectChoose('.ember-power-select-trigger', '3.12'); + + assert + .dom() + .includesText( + 'We could not find function @glimmer/tracking/tracked in v3.12 of ember.', + ); + + assert + .dom(versionIndexLinkSelector) + .includesText('v3.12') + .hasAttribute('href', '/ember/3.12'); + }); }); From a51eecedc18874ece51c607f2964d90999debd92 Mon Sep 17 00:00:00 2001 From: Katie Gengler Date: Fri, 12 Sep 2025 00:26:01 -0400 Subject: [PATCH 7/7] Injected controllers are no longer needed --- app/controllers/project-version.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/controllers/project-version.js b/app/controllers/project-version.js index f46f93db..f4543865 100644 --- a/app/controllers/project-version.js +++ b/app/controllers/project-version.js @@ -2,7 +2,7 @@ import { action, computed, set } from '@ember/object'; import { inject as service } from '@ember/service'; import { readOnly, alias } from '@ember/object/computed'; -import Controller, { inject as controller } from '@ember/controller'; +import Controller from '@ember/controller'; import { A } from '@ember/array'; import values from 'lodash.values'; import groupBy from 'lodash.groupby'; @@ -25,10 +25,6 @@ export default class ProjectVersionController extends Controller { @service router; @service('project') projectService; - @controller('project-version.classes.class') classController; - @controller('project-version.modules.module') moduleController; - @controller('project-version.namespaces.namespace') namespaceController; - @alias('filterData.sideNav.showPrivate') showPrivateClasses;