diff --git a/ghost/admin/app/components/koenig-lexical-editor.js b/ghost/admin/app/components/koenig-lexical-editor.js index af4a4e7214..0e4979b232 100644 --- a/ghost/admin/app/components/koenig-lexical-editor.js +++ b/ghost/admin/app/components/koenig-lexical-editor.js @@ -83,6 +83,28 @@ export function decoratePostSearchResult(item, settings) { } } +/** + * Fetches the URLs of all active offers + * @returns {Promise<{label: string, value: string}[]>} + */ +export async function offerUrls() { + let offers = []; + + try { + offers = await this.fetchOffersTask.perform(); + } catch (e) { + // No-op: if offers are not available (e.g. missing permissions), return an empty array + return []; + } + + return offers.map((offer) => { + return { + label: `Offer — ${offer.name}`, + value: this.config.getSiteUrl(offer.code) + }; + }); +} + class ErrorHandler extends React.Component { state = { hasError: false @@ -273,18 +295,6 @@ export default class KoenigLexicalEditor extends Component { }; const fetchAutocompleteLinks = async () => { - let offers = []; - try { - offers = await this.fetchOffersTask.perform(); - } catch (e) { - // Do not throw cancellation errors - if (didCancel(e)) { - return; - } - - throw e; - } - const defaults = [ {label: 'Homepage', value: window.location.origin + '/'}, {label: 'Free signup', value: '#/portal/signup/free'} @@ -329,12 +339,7 @@ export default class KoenigLexicalEditor extends Component { return []; }; - const offersLinks = offers.toArray().map((offer) => { - return { - label: `Offer - ${offer.name}`, - value: this.config.getSiteUrl(offer.code) - }; - }); + const offersLinks = await offerUrls.call(this); return [...defaults, ...memberLinks(), ...donationLink(), ...recommendationLink(), ...offersLinks]; }; diff --git a/ghost/admin/tests/unit/components/koenig-lexical-editor-test.js b/ghost/admin/tests/unit/components/koenig-lexical-editor-test.js index 034ea84425..74e9525fff 100644 --- a/ghost/admin/tests/unit/components/koenig-lexical-editor-test.js +++ b/ghost/admin/tests/unit/components/koenig-lexical-editor-test.js @@ -1,4 +1,5 @@ -import {decoratePostSearchResult} from 'ghost-admin/components/koenig-lexical-editor'; +import sinon from 'sinon'; +import {decoratePostSearchResult, offerUrls} from 'ghost-admin/components/koenig-lexical-editor'; import {describe, it} from 'mocha'; import {expect} from 'chai'; @@ -67,4 +68,59 @@ describe('Unit: Component: koenig-lexical-editor', function () { expect(result.metaIconTitle).to.be.undefined; }); }); + + describe('offersUrls', function () { + let context; + let performStub; + + beforeEach(function () { + context = { + fetchOffersTask: { + perform: () => {} + }, + config: { + getSiteUrl: code => `https://example.com?offer=${code}` + } + }; + + performStub = sinon.stub(context.fetchOffersTask, 'perform'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('returns an empty array if fetching offers gives no result', async function () { + performStub.resolves([]); + + const results = await offerUrls.call(context); + + expect(performStub.callCount).to.equal(1); + expect(results).to.deep.equal([]); + }); + + it('returns an empty array if fetching offers fails', async function () { + performStub.rejects(new Error('Failed to fetch offers')); + + const results = await offerUrls.call(context); + + expect(performStub.callCount).to.equal(1); + expect(results).to.deep.equal([]); + }); + + it(('returns an array of offers urls if fetching offers is successful'), async function () { + performStub.resolves([ + {name: 'Yellow Thursday', code: 'yellow-thursday'}, + {name: 'Green Friday', code: 'green-friday'} + ]); + + const results = await offerUrls.call(context); + + expect(performStub.callCount).to.equal(1); + expect(results).to.deep.equal([ + {label: 'Offer — Yellow Thursday', value: 'https://example.com?offer=yellow-thursday'}, + {label: 'Offer — Green Friday', value: 'https://example.com?offer=green-friday'} + ]); + }); + }); });