diff --git a/client/src/controllers/UpgradeController.test.js b/client/src/controllers/UpgradeController.test.js index b0cc709c88..9297197b3d 100644 --- a/client/src/controllers/UpgradeController.test.js +++ b/client/src/controllers/UpgradeController.test.js @@ -112,6 +112,181 @@ describe('UpgradeController', () => { expect(document.getElementById('panel').hidden).toBe(true); }); + it('should not show the message if the version has been dismissed before', async () => { + const data = { + version: '6.2.2', + url: 'https://docs.wagtail.org/latest/url', + minorUrl: 'https://docs.wagtail.org/latest-minor/url', + lts: { + version: '5.2.6', + url: 'https://docs.wagtail.org/lts/url', + minorUrl: 'https://docs.wagtail.org/lts-minor/url', + }, + }; + + fetch.mockResponseSuccessJSON(JSON.stringify(data)); + + expect(global.fetch).not.toHaveBeenCalled(); + const panel = document.getElementById('panel'); + const dismissButton = document.createElement('button'); + dismissButton.setAttribute('data-w-upgrade-target', 'dismiss'); + panel.appendChild(dismissButton); + + // Last dismissed version is 6.2.2 (the same as the latest version) + dismissButton.setAttribute('data-w-dismissible-value-param', '6.2.2'); + + // start application + application = Application.start(); + application.register('w-upgrade', UpgradeController); + + // trigger next browser render cycle + await Promise.resolve(); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://releases.wagtail.org/mock.txt', + { referrerPolicy: 'strict-origin-when-cross-origin' }, + ); + + expect(document.getElementById('panel').hidden).toBe(true); + + await new Promise(requestAnimationFrame); + + // should remove the hidden class on success + expect(document.getElementById('panel').hidden).toBe(true); + }); + + it('should show the message if the last dismissed version is not the latest', async () => { + const data = { + version: '6.2.3', + url: 'https://docs.wagtail.org/latest/url', + minorUrl: 'https://docs.wagtail.org/latest-minor/url', + lts: { + version: '5.2.9', // latest LTS version + url: 'https://docs.wagtail.org/lts/url', + minorUrl: 'https://docs.wagtail.org/lts-minor/url', + }, + }; + + fetch.mockResponseSuccessJSON(JSON.stringify(data)); + + expect(global.fetch).not.toHaveBeenCalled(); + const panel = document.getElementById('panel'); + const dismissButton = document.createElement('button'); + dismissButton.setAttribute('data-w-upgrade-target', 'dismiss'); + panel.appendChild(dismissButton); + + // Simulate the case where we only care about LTS versions + panel.setAttribute('data-w-upgrade-lts-only-value', 'true'); + // Last dismissed version is 5.2.6 + dismissButton.setAttribute('data-w-dismissible-value-param', '5.2.6'); + // Current installed version is 4.1.2 + panel.setAttribute('data-w-upgrade-current-version-value', '4.1.2'); + + // start application + application = Application.start(); + application.register('w-upgrade', UpgradeController); + + // trigger next browser render cycle + await Promise.resolve(); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://releases.wagtail.org/mock.txt', + { referrerPolicy: 'strict-origin-when-cross-origin' }, + ); + + expect(document.getElementById('panel').hidden).toBe(true); + + await new Promise(requestAnimationFrame); + + // should remove the hidden class on success + expect(document.getElementById('panel').hidden).toBe(false); + + // should update the latest version number in the text + // and include the (LTS) label since we only care about the LTS versions + expect(document.getElementById('latest-version').textContent).toBe( + `${data.lts.version} (LTS)`, + ); + + // should add the link using the minorUrl, + // since the actual installed version is way behind, i.e. 4.1.2 vs 5.2.9, + // even though the last dismissed version is 5.2.6 + expect(document.getElementById('link').getAttribute('href')).toEqual( + data.lts.minorUrl, + ); + + // Should update the dismissible value param to the latest LTS version, + // so this version is not shown again if the user dismisses it + expect( + document + .querySelector('[data-w-upgrade-target="dismiss"]') + .getAttribute('data-w-dismissible-value-param'), + ).toEqual('5.2.9'); + }); + + it('should use the latest URL if the currently installed version is at the same minor version', async () => { + const data = { + version: '6.2.6', + url: 'https://docs.wagtail.org/latest/url', + minorUrl: 'https://docs.wagtail.org/latest-minor/url', + lts: { + version: '5.2.6', + url: 'https://docs.wagtail.org/lts/url', + minorUrl: 'https://docs.wagtail.org/lts-minor/url', + }, + }; + + fetch.mockResponseSuccessJSON(JSON.stringify(data)); + + expect(global.fetch).not.toHaveBeenCalled(); + const panel = document.getElementById('panel'); + const dismissButton = document.createElement('button'); + dismissButton.setAttribute('data-w-upgrade-target', 'dismiss'); + panel.appendChild(dismissButton); + + // Last dismissed version is 6.2.2 + dismissButton.setAttribute('data-w-dismissible-value-param', '6.2.2'); + // Current installed version is 6.2.3 + panel.setAttribute('data-w-upgrade-current-version-value', '6.2.3'); + + // start application + application = Application.start(); + application.register('w-upgrade', UpgradeController); + + // trigger next browser render cycle + await Promise.resolve(); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://releases.wagtail.org/mock.txt', + { referrerPolicy: 'strict-origin-when-cross-origin' }, + ); + + expect(document.getElementById('panel').hidden).toBe(true); + + await new Promise(requestAnimationFrame); + + // should remove the hidden class on success + expect(document.getElementById('panel').hidden).toBe(false); + + // should update the latest version number in the text + expect(document.getElementById('latest-version').textContent).toBe( + data.version, + ); + + // should add the latest version link using the latest version, + // since the actual installed version is not far behind (6.2.3 vs 6.2.6) + expect(document.getElementById('link').getAttribute('href')).toEqual( + data.url, + ); + + // Should update the dismissible value param to the latest version, + // so this version is not shown again if the user dismisses it + expect( + document + .querySelector('[data-w-upgrade-target="dismiss"]') + .getAttribute('data-w-dismissible-value-param'), + ).toEqual('6.2.6'); + }); + it('should throw an error if the fetch fails', async () => { // Spy on console.error to verify that it is called with the expected error message jest.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/client/src/controllers/UpgradeController.ts b/client/src/controllers/UpgradeController.ts index 85e343945f..487dd0640a 100644 --- a/client/src/controllers/UpgradeController.ts +++ b/client/src/controllers/UpgradeController.ts @@ -30,7 +30,7 @@ interface LatestVersionData extends VersionData { * } */ export class UpgradeController extends Controller { - static targets = ['latestVersion', 'link']; + static targets = ['latestVersion', 'link', 'dismiss']; static values = { currentVersion: String, ltsOnly: { default: false, type: Boolean }, @@ -39,9 +39,11 @@ export class UpgradeController extends Controller { declare readonly hasLatestVersionTarget: boolean; declare readonly hasLinkTarget: boolean; + declare readonly hasDismissTarget: boolean; declare currentVersionValue: string; declare latestVersionTarget: HTMLElement; declare linkTarget: HTMLElement; + declare dismissTarget: HTMLElement; declare ltsOnlyValue: any; declare urlValue: string; @@ -49,6 +51,19 @@ export class UpgradeController extends Controller { this.checkVersion(); } + /** + * The version number that the user has acknowledged. + * + * Use the last dismissed version if it exists, or the current version otherwise. + */ + get knownVersion() { + return new VersionNumber( + (this.hasDismissTarget && + this.dismissTarget.getAttribute('data-w-dismissible-value-param')) || + this.currentVersionValue, + ); + } + checkVersion() { const releasesUrl = this.urlValue; const currentVersion = new VersionNumber(this.currentVersionValue); @@ -75,10 +90,15 @@ export class UpgradeController extends Controller { const latestVersion = new VersionNumber(data.version); const versionDelta = currentVersion.howMuchBehind(latestVersion); - let releaseNotesUrl: string; - if (!versionDelta) { + // Check with the last dismissed version if it exists, so we don't + // show the notification again if the user has already dismissed it. + if (!this.knownVersion.howMuchBehind(latestVersion)) { return; } + + // But use the actual installed version to check whether we want to + // link to the feature release notes or the patch release notes. + let releaseNotesUrl: string; if ( versionDelta === VersionDeltaType.MAJOR || versionDelta === VersionDeltaType.MINOR @@ -98,6 +118,14 @@ export class UpgradeController extends Controller { if (this.hasLinkTarget) { this.linkTarget.setAttribute('href', releaseNotesUrl || ''); } + + if (this.hasDismissTarget) { + this.dismissTarget.setAttribute( + 'data-w-dismissible-value-param', + data.version, + ); + } + this.element.hidden = false; }) .catch((err) => {