Add optional client-side dismissal SKU for closable billboards (#20716)
* Add optional client-side dismissal SKU for closable billboards * Safe nav
This commit is contained in:
parent
ac4dcd9907
commit
f2968baadb
9 changed files with 108 additions and 34 deletions
|
|
@ -48,7 +48,7 @@ module Api
|
|||
|
||||
def permitted_params
|
||||
params.permit :approved, :body_markdown, :creator_id, :display_to,
|
||||
:name, :organization_id, :placement_area, :published,
|
||||
:name, :organization_id, :placement_area, :published, :dismissal_sku,
|
||||
:tag_list, :type_of, :exclude_article_ids, :weight, :requires_cookies,
|
||||
:audience_segment_type, :audience_segment_id, :priority, :special_behavior,
|
||||
:custom_display_label, :template, :render_mode, :preferred_article_ids,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,27 @@ describe('getBillboard', () => {
|
|||
<div class="js-billboard-container" data-async-url="/billboards/sidebar_left_2"></div>
|
||||
</div>
|
||||
`;
|
||||
// Mock localStorage
|
||||
const localStorageMock = (function() {
|
||||
let store = {};
|
||||
return {
|
||||
getItem: function(key) {
|
||||
return store[key] || null;
|
||||
},
|
||||
setItem: function(key, value) {
|
||||
store[key] = value.toString();
|
||||
},
|
||||
clear: function() {
|
||||
store = {};
|
||||
},
|
||||
removeItem: function(key) {
|
||||
delete store[key];
|
||||
}
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -66,7 +87,6 @@ describe('getBillboard', () => {
|
|||
});
|
||||
|
||||
test('should clone and re-insert script tags in fetched content', async () => {
|
||||
// Setup fetch to return a script tag
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
text: () =>
|
||||
|
|
@ -78,25 +98,17 @@ describe('getBillboard', () => {
|
|||
|
||||
await getBillboard();
|
||||
|
||||
// Find the new script tags in the document
|
||||
const scriptElements = document.querySelectorAll(
|
||||
'.js-billboard-container script',
|
||||
);
|
||||
|
||||
// Assert that the script tags are properly cloned and re-inserted
|
||||
expect(scriptElements.length).toBe(2); // You have two .js-billboard-container in your setup, so expect two script tags
|
||||
const scriptElements = document.querySelectorAll('.js-billboard-container script');
|
||||
expect(scriptElements.length).toBe(2);
|
||||
scriptElements.forEach((script) => {
|
||||
expect(script.type).toEqual('text/javascript'); // Should retain attributes
|
||||
expect(script.innerHTML).toEqual('console.log("test")'); // Should retain content
|
||||
expect(script.type).toEqual('text/javascript');
|
||||
expect(script.innerHTML).toEqual('console.log("test")');
|
||||
});
|
||||
});
|
||||
|
||||
test('should add current URL parameters to asyncUrl if bb_test_placement_area exists', async () => {
|
||||
// Mocking window.location.href
|
||||
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>
|
||||
|
|
@ -112,10 +124,37 @@ describe('getBillboard', () => {
|
|||
|
||||
await getBillboard();
|
||||
|
||||
// Check if the fetch function was called with the modified URL including the parameters
|
||||
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']));
|
||||
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
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');
|
||||
});
|
||||
|
||||
test('should display billboard content if there is no matching dismissal SKU', async () => {
|
||||
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>'),
|
||||
}),
|
||||
);
|
||||
|
||||
await getBillboard();
|
||||
|
||||
const billboardContent = document.querySelector('.js-billboard-container div');
|
||||
expect(billboardContent.closest('.js-billboard-container').style.display).toBe(''); // Not marked as display none
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -141,10 +180,7 @@ describe('executeBBScripts', () => {
|
|||
|
||||
test('should skip null or undefined script elements', () => {
|
||||
container.innerHTML = '<script>window.someGlobalVar = "executed";</script>';
|
||||
// Simulating an inconsistency in the returned HTMLCollection
|
||||
const spiedGetElementsByTagName = jest
|
||||
.spyOn(container, 'getElementsByTagName')
|
||||
.mockReturnValue([null, undefined]);
|
||||
const spiedGetElementsByTagName = jest.spyOn(container, 'getElementsByTagName').mockReturnValue([null, undefined]);
|
||||
|
||||
executeBBScripts(container);
|
||||
|
||||
|
|
@ -152,8 +188,7 @@ describe('executeBBScripts', () => {
|
|||
});
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -171,8 +206,7 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ async function generateBillboard(element) {
|
|||
}
|
||||
|
||||
if (cookieStatus === 'allowed') {
|
||||
asyncUrl += `${asyncUrl.includes('?') ? '&' : '?' }cookies_allowed=true`;
|
||||
asyncUrl += `${asyncUrl.includes('?') ? '&' : '?'}cookies_allowed=true`;
|
||||
}
|
||||
|
||||
if (asyncUrl) {
|
||||
|
|
@ -39,6 +39,15 @@ async function generateBillboard(element) {
|
|||
this.style.display = 'none';
|
||||
};
|
||||
});
|
||||
const dismissalSku =
|
||||
element.querySelector('.js-billboard')?.dataset.dismissalSku;
|
||||
if (localStorage && dismissalSku && dismissalSku.length > 0) {
|
||||
const skuArray =
|
||||
JSON.parse(localStorage.getItem('dismissal_skus_triggered')) || [];
|
||||
if (skuArray.includes(dismissalSku)) {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
executeBBScripts(element);
|
||||
implementSpecialBehavior(element);
|
||||
setupBillboardInteractivity();
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
<div class="js-billboard" style="display: block;" data-dismissal-sku="WHATUP">
|
||||
<button id="sponsorship-close-trigger-1"></button>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -35,4 +35,16 @@ describe('billboard close functionality', () => {
|
|||
const billboard = document.querySelector('.js-billboard');
|
||||
expect(billboard.style.display).toBe('none');
|
||||
});
|
||||
|
||||
it('adds dismissal sku to local storage when dismissing a billboard', () => {
|
||||
setupBillboardInteractivity();
|
||||
|
||||
// Simulate clicking the close button
|
||||
const closeButton = document.querySelector('#sponsorship-close-trigger-1');
|
||||
closeButton.click();
|
||||
|
||||
// Assert the dismissal sku is added to local storage
|
||||
const dismissalSkus = JSON.parse(localStorage.getItem('dismissal_skus_triggered'));
|
||||
expect(dismissalSkus).toEqual(['WHATUP']);
|
||||
});
|
||||
});
|
||||
|
|
@ -29,11 +29,11 @@ export function setupBillboardInteractivity() {
|
|||
if (sponsorshipCloseButtons.length) {
|
||||
sponsorshipCloseButtons.forEach((sponsorshipCloseButton) => {
|
||||
sponsorshipCloseButton.addEventListener('click', () => {
|
||||
sponsorshipCloseButton.closest('.js-billboard').style.display = 'none';
|
||||
dismissBillboard(sponsorshipCloseButton);
|
||||
});
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!event.target.closest('.js-billboard')) {
|
||||
sponsorshipCloseButton.closest('.js-billboard').style.display = 'none';
|
||||
dismissBillboard(sponsorshipCloseButton);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -51,3 +51,15 @@ function amendBillboardStyle(sponsorshipDropdownButton) {
|
|||
'revert';
|
||||
}
|
||||
}
|
||||
|
||||
function dismissBillboard(sponsorshipCloseButton) {
|
||||
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')) || [];
|
||||
if (!skuArray.includes(sku)) {
|
||||
skuArray.push(sku);
|
||||
localStorage.setItem('dismissal_skus_triggered', JSON.stringify(skuArray));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
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 %>">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class AddDismissalSkuToBillboards < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :display_ads, :dismissal_sku, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_02_27_181740) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_03_01_160047) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "citext"
|
||||
enable_extension "ltree"
|
||||
|
|
@ -479,6 +479,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_02_27_181740) do
|
|||
t.datetime "created_at", precision: nil, null: false
|
||||
t.integer "creator_id"
|
||||
t.string "custom_display_label"
|
||||
t.string "dismissal_sku"
|
||||
t.integer "display_to", default: 0, null: false
|
||||
t.integer "exclude_article_ids", default: [], array: true
|
||||
t.integer "impressions_count", default: 0
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ RSpec.describe "Api::V1::Billboards" do
|
|||
"impressions_count", "name", "organization_id",
|
||||
"placement_area", "processed_html", "published",
|
||||
"success_rate", "tag_list", "type_of", "updated_at",
|
||||
"creator_id", "exclude_article_ids",
|
||||
"creator_id", "exclude_article_ids", "dismissal_sku",
|
||||
"audience_segment_type", "audience_segment_id",
|
||||
"custom_display_label", "template", "render_mode", "preferred_article_ids",
|
||||
"priority", "weight", "target_geolocations", "requires_cookies", "special_behavior")
|
||||
|
|
@ -71,7 +71,7 @@ RSpec.describe "Api::V1::Billboards" do
|
|||
"impressions_count", "name", "organization_id",
|
||||
"placement_area", "processed_html", "published",
|
||||
"success_rate", "tag_list", "type_of", "updated_at",
|
||||
"creator_id", "exclude_article_ids",
|
||||
"creator_id", "exclude_article_ids", "dismissal_sku",
|
||||
"audience_segment_type", "audience_segment_id",
|
||||
"custom_display_label", "template", "render_mode", "preferred_article_ids",
|
||||
"priority", "weight", "target_geolocations", "requires_cookies", "special_behavior")
|
||||
|
|
@ -135,7 +135,7 @@ RSpec.describe "Api::V1::Billboards" do
|
|||
contain_exactly("approved", "body_markdown", "cached_tag_list",
|
||||
"clicks_count", "created_at", "display_to", "id",
|
||||
"impressions_count", "name", "organization_id",
|
||||
"placement_area", "processed_html", "published",
|
||||
"placement_area", "processed_html", "published", "dismissal_sku",
|
||||
"success_rate", "tag_list", "type_of", "updated_at",
|
||||
"creator_id", "exclude_article_ids", "requires_cookies",
|
||||
"audience_segment_type", "audience_segment_id", "special_behavior",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue