Skip to content

Commit

Permalink
Allow feed billboards to be dismissable if dismissal_sku is present (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
benhalpern authored Mar 6, 2024
1 parent e817378 commit fad28a1
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 42 deletions.
67 changes: 46 additions & 21 deletions app/javascript/__tests__/billboard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ describe('getBillboard', () => {
</div>
`;
// Mock localStorage
const localStorageMock = (function() {
const localStorageMock = (function () {
let store = {};
return {
getItem: function(key) {
getItem (key) {
return store[key] || null;
},
setItem: function(key, value) {
setItem (key, value) {
store[key] = value.toString();
},
clear: function() {
clear () {
store = {};
},
removeItem: function(key) {
removeItem (key) {
delete store[key];
}
},
};
})();
Object.defineProperty(window, 'localStorage', {
Expand Down Expand Up @@ -98,7 +98,9 @@ describe('getBillboard', () => {

await getBillboard();

const scriptElements = document.querySelectorAll('.js-billboard-container script');
const scriptElements = document.querySelectorAll(
'.js-billboard-container script',
);
expect(scriptElements.length).toBe(2);
scriptElements.forEach((script) => {
expect(script.type).toEqual('text/javascript');
Expand All @@ -108,7 +110,9 @@ describe('getBillboard', () => {

test('should add current URL parameters to asyncUrl if bb_test_placement_area exists', async () => {
delete window.location;
window.location = new URL('http://example.com?bb_test_placement_area=post_sidebar&bb_test_id=1');
window.location = new URL(
'http://example.com?bb_test_placement_area=post_sidebar&bb_test_id=1',
);

document.body.innerHTML = `
<div>
Expand All @@ -124,37 +128,54 @@ describe('getBillboard', () => {

await getBillboard();

expect(global.fetch).toHaveBeenCalledWith('/billboards/post_sidebar?bb_test_placement_area=post_sidebar&bb_test_id=1');
expect(global.fetch).toHaveBeenCalledWith(
'/billboards/post_sidebar?bb_test_placement_area=post_sidebar&bb_test_id=1',
);
});

test('should display none if dismissal SKU matches', async () => {
window.localStorage.setItem('dismissal_skus_triggered', JSON.stringify(['sku123']));
test('should have null content if dismissal SKU matches', async () => {
window.localStorage.setItem(
'dismissal_skus_triggered',
JSON.stringify(['sku123']),
);

global.fetch = jest.fn(() =>
Promise.resolve({
text: () => Promise.resolve('<div class="js-billboard" data-dismissal-sku="sku123">Billboard Content</div>'),
text: () =>
Promise.resolve(
'<div class="js-billboard" data-dismissal-sku="sku123">Billboard Content</div>',
),
}),
);

await getBillboard();

const billboardContent = document.querySelector('.js-billboard-container div');
expect(billboardContent.closest('.js-billboard-container').style.display).toBe('none');
expect(document.querySelector('.js-billboard-container div')).toBe(null);
});

test('should display billboard content if there is no matching dismissal SKU', async () => {
window.localStorage.setItem('dismissal_skus_triggered', JSON.stringify(['sku999']));
window.localStorage.setItem(
'dismissal_skus_triggered',
JSON.stringify(['sku999']),
);

global.fetch = jest.fn(() =>
Promise.resolve({
text: () => Promise.resolve('<div class="js-billboard" data-dismissal-sku="sku123">Billboard Content</div>'),
text: () =>
Promise.resolve(
'<div class="js-billboard" data-dismissal-sku="sku123">Billboard Content</div>',
),
}),
);

await getBillboard();

const billboardContent = document.querySelector('.js-billboard-container div');
expect(billboardContent.closest('.js-billboard-container').style.display).toBe(''); // Not marked as display none
const billboardContent = document.querySelector(
'.js-billboard-container div',
);
expect(
billboardContent.closest('.js-billboard-container').style.display,
).toBe(''); // Not marked as display none
});
});

Expand All @@ -180,15 +201,18 @@ describe('executeBBScripts', () => {

test('should skip null or undefined script elements', () => {
container.innerHTML = '<script>window.someGlobalVar = "executed";</script>';
const spiedGetElementsByTagName = jest.spyOn(container, 'getElementsByTagName').mockReturnValue([null, undefined]);
const spiedGetElementsByTagName = jest
.spyOn(container, 'getElementsByTagName')
.mockReturnValue([null, undefined]);

executeBBScripts(container);

expect(spiedGetElementsByTagName).toBeCalled();
});

test('should copy attributes of original script element', () => {
container.innerHTML = '<script type="text/javascript" async>window.someGlobalVar = "executed";</script>';
container.innerHTML =
'<script type="text/javascript" async>window.someGlobalVar = "executed";</script>';

executeBBScripts(container);

Expand All @@ -206,7 +230,8 @@ describe('executeBBScripts', () => {
});

test('should insert the new script element at the same position as the original', () => {
container.innerHTML = '<div></div><script>window.someGlobalVar = "executed";</script><div></div>';
container.innerHTML =
'<div></div><script>window.someGlobalVar = "executed";</script><div></div>';

executeBBScripts(container);

Expand Down
34 changes: 31 additions & 3 deletions app/javascript/articles/Feed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,15 +232,27 @@ export const Feed = ({ timeFrame, renderFeed, afterRender }) => {
feedSecondBillboard,
feedThirdBillboard,
) {
if (organizedFeedItems.length >= 9 && feedThirdBillboard) {
if (
organizedFeedItems.length >= 9 &&
feedThirdBillboard &&
!isDismissed(feedThirdBillboard)
) {
organizedFeedItems.splice(7, 0, feedThirdBillboard);
}

if (organizedFeedItems.length >= 3 && feedSecondBillboard) {
if (
organizedFeedItems.length >= 3 &&
feedSecondBillboard &&
!isDismissed(feedSecondBillboard)
) {
organizedFeedItems.splice(2, 0, feedSecondBillboard);
}

if (organizedFeedItems.length >= 0 && feedFirstBillboard) {
if (
organizedFeedItems.length >= 0 &&
feedFirstBillboard &&
!isDismissed(feedFirstBillboard)
) {
organizedFeedItems.splice(0, 0, feedFirstBillboard);
}

Expand All @@ -257,6 +269,22 @@ export const Feed = ({ timeFrame, renderFeed, afterRender }) => {
return result.value.headers?.get('content-type')?.includes('text/html');
}

function isDismissed(bb) {
const parser = new DOMParser();
const doc = parser.parseFromString(bb, 'text/html');
const element = doc.querySelector('.crayons-story');
const dismissalSku = element?.dataset?.dismissalSku;
if (localStorage && dismissalSku && dismissalSku.length > 0) {
const skuArray =
JSON.parse(localStorage.getItem('dismissal_skus_triggered')) || [];
if (skuArray.includes(dismissalSku)) {
return true;
}
} else {
return false;
}
}

// /**
// * Retrieves the podcasts for the feed from the user data and the `followed-podcasts`
// * div item.
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packs/billboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async function generateBillboard(element) {
JSON.parse(localStorage.getItem('dismissal_skus_triggered')) || [];
if (skuArray.includes(dismissalSku)) {
element.style.display = 'none';
element.innerHTML = '';
}
}
executeBBScripts(element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('billboard close functionality', () => {
// Setup a simple DOM structure that includes only the elements needed for the close functionality
document.body.innerHTML = `
<div class="another-element"></div>
<div class="js-billboard" style="display: block;" data-dismissal-sku="WHATUP">
<div class="js-billboard popover-billboard" style="display: block;" data-dismissal-sku="WHATUP">
<button id="sponsorship-close-trigger-1"></button>
</div>
`;
Expand Down Expand Up @@ -44,7 +44,9 @@ describe('billboard close functionality', () => {
closeButton.click();

// Assert the dismissal sku is added to local storage
const dismissalSkus = JSON.parse(localStorage.getItem('dismissal_skus_triggered'));
const dismissalSkus = JSON.parse(
localStorage.getItem('dismissal_skus_triggered'),
);
expect(dismissalSkus).toEqual(['WHATUP']);
});
});
});
36 changes: 23 additions & 13 deletions app/javascript/utilities/billboardInteractivity.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ export function setupBillboardInteractivity() {
sponsorshipDropdownButton.dataset.initialized = 'true';
}

const popoverParent = sponsorshipDropdownButton.closest('.popover-billboard');
if (popoverParent &&
sponsorshipDropdownButton.getBoundingClientRect().top > window.innerHeight / 2) {
const popoverParent =
sponsorshipDropdownButton.closest('.popover-billboard');
if (
popoverParent &&
sponsorshipDropdownButton.getBoundingClientRect().top >
window.innerHeight / 2
) {
popoverParent.classList.add('popover-billboard--menuopenupwards');
}
});
Expand All @@ -37,14 +41,15 @@ export function setupBillboardInteractivity() {
sponsorshipCloseButton.addEventListener('click', () => {
dismissBillboard(sponsorshipCloseButton);
});
document.addEventListener('click', (event) => {
if (!event.target.closest('.js-billboard')) {
dismissBillboard(sponsorshipCloseButton);
}
});
if (sponsorshipCloseButton.closest('.popover-billboard')) {
document.addEventListener('click', (event) => {
if (!event.target.closest('.js-billboard')) {
dismissBillboard(sponsorshipCloseButton);
}
});
}
});
}

}

/**
Expand All @@ -59,13 +64,18 @@ function amendBillboardStyle(sponsorshipDropdownButton) {
}

function dismissBillboard(sponsorshipCloseButton) {
const sku = sponsorshipCloseButton.closest('.js-billboard').dataset.dismissalSku;
const sku =
sponsorshipCloseButton.closest('.js-billboard').dataset.dismissalSku;
sponsorshipCloseButton.closest('.js-billboard').style.display = 'none';
if (localStorage && sku && sku.length > 0) {
const skuArray = JSON.parse(localStorage.getItem('dismissal_skus_triggered')) || [];
const skuArray =
JSON.parse(localStorage.getItem('dismissal_skus_triggered')) || [];
if (!skuArray.includes(sku)) {
skuArray.push(sku);
localStorage.setItem('dismissal_skus_triggered', JSON.stringify(skuArray));
localStorage.setItem(
'dismissal_skus_triggered',
JSON.stringify(skuArray),
);
}
}
}
}
18 changes: 16 additions & 2 deletions app/views/shared/_billboard.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@
data-category-click="<%= BillboardEvent::CATEGORY_CLICK %>"
data-category-impression="<%= BillboardEvent::CATEGORY_IMPRESSION %>"
data-context-type="<%= data_context_type %>"
data-dismissal-sku="<%= billboard.dismissal_sku %>"
data-special="<%= billboard.special_behavior %>"
data-article-id="<%= @article&.id %>"
data-type-of="<%= billboard.type_of %>">
<div class="crayons-story__body">
<div class="crayons-story__top">
<div class="crayons-story__top flex">
<%= render partial: "shared/billboard_header", locals: { billboard: billboard } %>
<% if billboard.dismissal_sku.present? %>
<button id="sponsorship-close-trigger-<%= billboard.id %>" aria-controls="sponsorship-close-<%= billboard.id %>"
class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon">
<%= crayons_icon_tag("x", class: "pointer-events-none", title: t("billboard.menu.close")) %>
</button>
<% end %>
</div>
<div class="crayons-story__indention-billboard">
<div class="text-styles text-styles--billboard">
Expand All @@ -35,10 +42,17 @@
data-category-click="<%= BillboardEvent::CATEGORY_CLICK %>"
data-category-impression="<%= BillboardEvent::CATEGORY_IMPRESSION %>"
data-context-type="<%= data_context_type %>"
data-dismissal-sku="<%= billboard.dismissal_sku %>"
data-special="<%= billboard.special_behavior %>"
data-article-id="<%= @article&.id %>"
data-type-of="<%= billboard.type_of %>">
<%= render partial: "shared/billboard_header", locals: { billboard: billboard } %>
<div class="flex">
<%= render partial: "shared/billboard_header", locals: { billboard: billboard } %>
<button id="sponsorship-close-trigger-<%= billboard.id %>" aria-controls="sponsorship-close-<%= billboard.id %>"
class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon">
<%= crayons_icon_tag("x", class: "pointer-events-none", title: t("billboard.menu.close")) %>
</button>
</div>
<div class="p-1 text-styles text-styles--billboard">
<%= billboard.processed_html.html_safe %>
</div>
Expand Down
14 changes: 14 additions & 0 deletions spec/requests/billboards_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ def create_billboard(**options)
end
end

context "when the placement area is feed_first" do
it "includes sponsorship-close-trigger when there is a dismissal_sku" do
billboard = create_billboard(placement_area: "feed_first", dismissal_sku: "DISMISS_ME")
get billboard_path(placement_area: "feed_first")
expect(response.body).to include "sponsorship-close-trigger-#{billboard.id}"
end

it "does not include sponsorship-close-trigger when there is no dismissal_sku" do
billboard = create_billboard(placement_area: "feed_first")
get billboard_path(placement_area: "feed_first")
expect(response.body).not_to include "sponsorship-close-trigger-#{billboard.id}"
end
end

context "when the placement area is post_sidebar" do
it "does not contain close button" do
billboard = create_billboard(placement_area: "post_sidebar")
Expand Down

0 comments on commit fad28a1

Please sign in to comment.