Feature: Runtime Content Filter (#13007)
* Working version of Runtime Filter with banner behind Feature Flag * First attempt to add a cypress test * Adds simplified test * Cleanup global references * Add multi-platform e2e test * Use --runtime-display var with fallback to block * Update app/assets/javascripts/initializers/runtime.js Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * Add FeatureFlag stub to fix unrelated specs Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
parent
99af03f819
commit
f0b8754d4f
9 changed files with 260 additions and 1 deletions
|
|
@ -12,7 +12,7 @@
|
|||
initializeHeroBannerClose, initializeOnboardingTaskCard, initScrolling,
|
||||
nextPage:writable, fetching:writable, done:writable, adClicked:writable,
|
||||
initializePaymentPointers, initializeBroadcast, initializeDateHelpers,
|
||||
initializeColorPicker
|
||||
initializeColorPicker, Runtime
|
||||
*/
|
||||
|
||||
function callInitializers() {
|
||||
|
|
@ -79,6 +79,9 @@ function callInitializers() {
|
|||
if (!initScrolling.called) {
|
||||
initScrolling();
|
||||
}
|
||||
|
||||
// Initialize data-runtime context to the body data-attribute
|
||||
document.body.dataset.runtime = Runtime.currentContext();
|
||||
}
|
||||
|
||||
function initializePage() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,61 @@
|
|||
* if (Runtime.isNativeAndroid('podcastMessage')) { ... }
|
||||
*/
|
||||
class Runtime {
|
||||
/**
|
||||
* This function returns a string combining the current Medium and OS
|
||||
* that represents the current Context where the app is running.
|
||||
*
|
||||
* @returns {String} "Medium-OS", for example "Browser-Android"
|
||||
*/
|
||||
static currentContext() {
|
||||
return `${Runtime.currentMedium()}-${Runtime.currentOS()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a string that represents the current Medium where
|
||||
* the app is currently running. The currently supported mediums are Browser,
|
||||
* ForemWebView and PWA.
|
||||
*
|
||||
* @returns {String} One of the supported Mediums or 'Unsupported'
|
||||
*/
|
||||
static currentMedium() {
|
||||
const pwaButtons = document.getElementById('pwa-nav-buttons');
|
||||
if (/ForemWebView/i.test(navigator.userAgent)) {
|
||||
return 'ForemWebView';
|
||||
} else if (pwaButtons.classList.contains('pwa-nav-buttons--showing')) {
|
||||
return 'PWA';
|
||||
} else {
|
||||
return 'Browser';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a string that represents the current OS where the app
|
||||
* is currently running. The currently supported Operating Systems are
|
||||
* Windows, Linux, macOS, Android and iOS.
|
||||
*
|
||||
* @returns {String} One of the supported Operating Systems or 'Unsupported'
|
||||
*/
|
||||
static currentOS() {
|
||||
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];
|
||||
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];
|
||||
const iosPlatforms = ['iPhone', 'iPad', 'iPod'];
|
||||
|
||||
if (macosPlatforms.includes(window.navigator.platform)) {
|
||||
return 'macOS';
|
||||
} else if (iosPlatforms.includes(window.navigator.platform)) {
|
||||
return 'iOS';
|
||||
} else if (windowsPlatforms.includes(window.navigator.platform)) {
|
||||
return 'Windows';
|
||||
} else if (/Android/i.test(window.navigator.userAgent)) {
|
||||
return 'Android';
|
||||
} else if (/Linux/i.test(window.navigator.platform)) {
|
||||
return 'Linux';
|
||||
}
|
||||
|
||||
return 'Unsupported';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the device for iOS (webkit) native feature support
|
||||
*
|
||||
|
|
|
|||
|
|
@ -28,3 +28,6 @@
|
|||
@import 'chat';
|
||||
|
||||
@import 'email_subscriptions';
|
||||
|
||||
@import 'runtime';
|
||||
@import 'runtime-banner';
|
||||
|
|
|
|||
15
app/assets/stylesheets/runtime-banner.scss
Normal file
15
app/assets/stylesheets/runtime-banner.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.runtime-banner {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(65px + env(safe-area-inset-bottom));
|
||||
z-index: var(--z-sticky);
|
||||
|
||||
&__inner {
|
||||
background-color: #0b0c0c;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
96
app/assets/stylesheets/runtime.scss
Normal file
96
app/assets/stylesheets/runtime.scss
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// The classes in this file are all related to a Runtime Filter feature detailed
|
||||
// in RFC 27. A short summary of how they work can be summarized by:
|
||||
//
|
||||
// They all are invisible by default. Only after the `initializeRuntime()` code
|
||||
// adds the context to the data-runtime attribute to the body element is when
|
||||
// elements with these classes become visible.
|
||||
//
|
||||
// More thorough context can be found in: https://github.com/forem/rfcs/pull/27
|
||||
|
||||
// Elements with all of these classes are not visible by default
|
||||
.ForemWebView,
|
||||
.ForemWebView-iOS,
|
||||
.ForemWebView-Android,
|
||||
.PWA,
|
||||
.PWA-iOS,
|
||||
.PWA-Android,
|
||||
.PWA-Windows,
|
||||
.PWA-Linux,
|
||||
.PWA-macOS,
|
||||
.Browser,
|
||||
.Browser-iOS,
|
||||
.Browser-Android,
|
||||
.Browser-macOS,
|
||||
.Browser-Windows,
|
||||
.Browser-Linux {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Elements with class 'ForemWebView' should be visible on any attribute that
|
||||
// matches that Medium regardless of OS
|
||||
body[data-runtime='ForemWebView-iOS'] .ForemWebView,
|
||||
body[data-runtime='ForemWebView-Android'] .ForemWebView {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
||||
// Elements that need to match both 'ForemWebView' Medium and OS
|
||||
body[data-runtime='ForemWebView-iOS'] .ForemWebView-iOS {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='ForemWebView-Android'] .ForemWebView-Android {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
||||
// Elements with class 'Browser' should be visible on any attribute that
|
||||
// matches that Medium regardless of OS
|
||||
body[data-runtime='Browser-Android'] .Browser,
|
||||
body[data-runtime='Browser-iOS'] .Browser,
|
||||
body[data-runtime='Browser-Linux'] .Browser,
|
||||
body[data-runtime='Browser-Windows'] .Browser,
|
||||
body[data-runtime='Browser-macOS'] .Browser {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
||||
// Elements that need to match both 'Browser' Medium and OS
|
||||
body[data-runtime='Browser-Android'] .Browser-Android {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='Browser-iOS'] .Browser-iOS {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='Browser-Linux'] .Browser-Linux {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='Browser-Windows'] .Browser-Windows {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='Browser-macOS'] .Browser-macOS {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
||||
// Elements with class 'PWA' should be visible on any attribute that
|
||||
// matches that Medium regardless of OS
|
||||
body[data-runtime='PWA-Android'] .PWA,
|
||||
body[data-runtime='PWA-iOS'] .PWA,
|
||||
body[data-runtime='PWA-Linux'] .PWA,
|
||||
body[data-runtime='PWA-Windows'] .PWA,
|
||||
body[data-runtime='PWA-macOS'] .PWA {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
||||
// Elements that need to match both 'PWA' Medium and OS
|
||||
body[data-runtime='PWA-Android'] .PWA-Android {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='PWA-iOS'] .PWA-iOS {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='PWA-Linux'] .PWA-Linux {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='PWA-Windows'] .PWA-Windows {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
body[data-runtime='PWA-macOS'] .PWA-macOS {
|
||||
display: var(--runtime-display, block);
|
||||
}
|
||||
|
|
@ -106,6 +106,13 @@
|
|||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
<% if FeatureFlag.enabled?(:runtime_banner) %>
|
||||
<div class="Browser-iOS Browser-Android runtime-banner">
|
||||
<div class="runtime-banner__inner">
|
||||
<span>Open with the Forem app</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% unless internal_navigation? %>
|
||||
<% cache("footer-and-signup-modal-#{user_signed_in?}-#{ForemInstance.deployed_at}-#{SiteConfig.admin_action_taken_at&.rfc3339}", expires_in: 24.hours) do %>
|
||||
<%= render "layouts/footer" %>
|
||||
|
|
|
|||
78
cypress/integration/loggedOutFlows/runtimeContext.spec.js
Normal file
78
cypress/integration/loggedOutFlows/runtimeContext.spec.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
describe('Runtime Context', () => {
|
||||
beforeEach(() => {
|
||||
cy.testSetup();
|
||||
});
|
||||
|
||||
it('should detect the context and set the corresponding runtime attr', () => {
|
||||
const supportedPlatforms = [
|
||||
{
|
||||
// Windows 10 Chrome 89.0
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
platform: 'Win32',
|
||||
expectedContext: 'Browser-Windows',
|
||||
},
|
||||
{
|
||||
// Linux Firefox
|
||||
userAgent:
|
||||
'Mozilla/5.0 (X11; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
|
||||
platform: 'Linux x86_64',
|
||||
expectedContext: 'Browser-Linux',
|
||||
},
|
||||
{
|
||||
// macOS Big Sur 11.2.2 (Intel processor) Chrome 89.0
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
|
||||
platform: 'MacIntel',
|
||||
expectedContext: 'Browser-macOS',
|
||||
},
|
||||
{
|
||||
// macOS Big Sur (M1 processor) Safari
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15',
|
||||
platform: 'MacIntel',
|
||||
expectedContext: 'Browser-macOS',
|
||||
},
|
||||
{
|
||||
// Android 10 Chrome 88
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Linux; Android 10; SM-A217M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.181 Mobile Safari/537.36',
|
||||
platform: 'Linux armv8l',
|
||||
expectedContext: 'Browser-Android',
|
||||
},
|
||||
{
|
||||
// iOS 14.4.1 Safari
|
||||
userAgent:
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
|
||||
platform: 'iPhone',
|
||||
expectedContext: 'Browser-iOS',
|
||||
},
|
||||
{
|
||||
// ForemWebView (Forem iOS app)
|
||||
userAgent:
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 ForemWebView/1.0',
|
||||
platform: 'iPhone',
|
||||
expectedContext: 'ForemWebView-iOS',
|
||||
},
|
||||
];
|
||||
|
||||
supportedPlatforms.forEach((platform) => {
|
||||
// Visit the site
|
||||
cy.visit('/', {
|
||||
onBeforeLoad: (win) => {
|
||||
Object.defineProperty(win.navigator, 'userAgent', {
|
||||
value: platform.userAgent,
|
||||
});
|
||||
Object.defineProperty(win.navigator, 'platform', {
|
||||
value: platform.platform,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Check the body's data-runtime attribute
|
||||
cy.get('body')
|
||||
.should('have.attr', 'data-runtime')
|
||||
.and('equal', platform.expectedContext);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -115,6 +115,7 @@ RSpec.describe "Registrations", type: :request do
|
|||
context "with the creator_onboarding feature flag" do
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:creator_onboarding).and_return(true)
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:runtime_banner).and_return(false)
|
||||
allow(SiteConfig).to receive(:waiting_on_first_user).and_return(true)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ RSpec.describe "User edits their profile", type: :system do
|
|||
describe "editing admin created profile fields" do
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:profile_admin).and_return(true)
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:runtime_banner).and_return(false)
|
||||
Profile.refresh_attributes!
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue