Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/support/grant-suggestions-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ const OPPORTUNITY_STRATEGIES = {
return idA.localeCompare(idB);
},
},
// Decorative-image suggestions (data.recommendations[].isDecorative) are excluded
// from grant selection entirely, since they need no alt text fix and shouldn't
// consume one of the customer's limited PLG token slots. Remaining suggestions
// fall back to the default sort (rank ascending, then id ascending).
'alt-text': {
groupFn: (suggestions) => suggestions
.filter((s) => {
const data = typeof s?.getData === 'function' ? s.getData() : s?.data;
const recommendations = data?.recommendations ?? [];
return !recommendations.some((r) => r?.isDecorative === true);
})
.map((s) => createGroup([s])),
},
};

/**
Expand Down
61 changes: 61 additions & 0 deletions test/support/grant-suggestions-handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,67 @@ describe('grant-suggestions-handler', () => {
const ids = groups.flatMap((g) => g.items.map((s) => s.getId()));
expect(ids).to.include.members(['id-1', 'id-2']);
});

it('alt-text: excludes suggestions with a decorative recommendation', () => {
const mk = (id, rank, isDecorative) => ({
getId: () => id,
getRank: () => rank,
getData: () => ({ recommendations: [{ isDecorative }] }),
});
const s1 = mk('id-1', 1, false);
const s2 = mk('id-2', 2, true);
const groups = getTopSuggestions([s1, s2], 'alt-text');
expect(groups).to.have.lengthOf(1);
expect(groups[0].items[0]).to.equal(s1);
});

it('alt-text: excludes a suggestion when any of its recommendations is decorative', () => {
const s1 = {
getId: () => 'id-1',
getRank: () => 1,
getData: () => ({ recommendations: [{ isDecorative: false }, { isDecorative: true }] }),
};
const groups = getTopSuggestions([s1], 'alt-text');
expect(groups).to.have.lengthOf(0);
});

it('alt-text: keeps suggestions with no decorative recommendations', () => {
const s1 = { getId: () => 'id-1', getRank: () => 1, getData: () => ({ recommendations: [{ isDecorative: false }] }) };
const s2 = { getId: () => 'id-2', getRank: () => 2, getData: () => ({ recommendations: [{ isDecorative: false }] }) };
const groups = getTopSuggestions([s1, s2], 'alt-text');
expect(groups).to.have.lengthOf(2);
});

it('alt-text: treats missing or empty recommendations as not decorative', () => {
const s1 = { getId: () => 'id-1', getRank: () => 1, getData: () => ({ recommendations: [] }) };
const s2 = { getId: () => 'id-2', getRank: () => 2, getData: () => ({}) };
const groups = getTopSuggestions([s1, s2], 'alt-text');
expect(groups).to.have.lengthOf(2);
});

it('alt-text: works with plain object data (no getData())', () => {
const s1 = { id: 'id-1', rank: 1, data: { recommendations: [{ isDecorative: true }] } };
const s2 = { id: 'id-2', rank: 2, data: { recommendations: [{ isDecorative: false }] } };
const groups = getTopSuggestions([s1, s2], 'alt-text');
expect(groups).to.have.lengthOf(1);
expect(groups[0].items[0]).to.equal(s2);
});

it('alt-text: sorts kept suggestions by rank ascending then id (default sort)', () => {
const mk = (id, rank) => ({
getId: () => id,
getRank: () => rank,
getData: () => ({ recommendations: [{ isDecorative: false }] }),
});
const s1 = mk('id-b', 10);
const s2 = mk('id-a', 5);
const s3 = mk('id-c', 10);
const groups = getTopSuggestions([s1, s2, s3], 'alt-text');
expect(groups).to.have.lengthOf(3);
expect(groups[0].items[0]).to.equal(s2); // rank 5 first
expect(groups[1].items[0]).to.equal(s1); // rank 10, id-b before id-c
expect(groups[2].items[0]).to.equal(s3); // rank 10, id-c
});
});

describe('grantSuggestionsForOpportunity', () => {
Expand Down
Loading