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:
Fernando Valverde 2021-03-25 12:04:45 -06:00 committed by GitHub
parent 99af03f819
commit f0b8754d4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 260 additions and 1 deletions

View file

@ -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() {

View file

@ -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
*

View file

@ -28,3 +28,6 @@
@import 'chat';
@import 'email_subscriptions';
@import 'runtime';
@import 'runtime-banner';

View 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;
}
}

View 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);
}

View file

@ -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" %>

View 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);
});
});
});

View file

@ -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

View file

@ -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