mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 13:19:19 +08:00
Previously, a site in read only or staff only mode still offered anonymous visitors every way in: the header "Sign Up" button, the signup call to action at the end of a topic, the create account link on the login page, and the server-rendered header — including the one on the read only error page itself. Account creation is blocked in both modes, so all of them were dead ends. The banner made it worse by describing what members lose (replying, likes) rather than what an anonymous visitor is actually unable to do. This change gates the JS `canSignUp` getter and its Ruby twin `can_sign_up?` on read only state, which covers every signup entry point at once. `canSignUp` had to drop its `@computed` decorator to do so: with no dependent keys it cached permanently, so the new term would have been evaluated once at boot and then frozen — leaving the button visible if read only mode started later, and hidden long after it ended. The "Log In" button deliberately stays, because staff can still log in during staff only mode. Anonymous visitors now get banner copy naming signup and login instead, a refused login says only staff can log in rather than claiming login is disabled outright, and the email and code login forms show that inline instead of a generic "an error occurred" dialog. It also fixes a pre-existing blank page. Both `/login` and `/signup` aborted the transition when read only, which on a direct URL load left the application template unrendered — and with it the dialog holder, so the explanation never appeared either. They now redirect home when there is no route to stay on, and keep aborting when there is. Meta ref: /t/408703
1420 lines
50 KiB
Ruby
Vendored
1420 lines
50 KiB
Ruby
Vendored
# coding: utf-8
|
|
# frozen_string_literal: true
|
|
|
|
RSpec.describe ApplicationHelper do
|
|
describe "#discourse_pageview_tracking_meta_tags" do
|
|
it "includes beacon tracking meta tags for anonymous users when dashboard_improvements is enabled" do
|
|
SiteSetting.dashboard_improvements = true
|
|
helper.stubs(:current_user).returns(nil)
|
|
|
|
tags = helper.discourse_pageview_tracking_meta_tags
|
|
|
|
expect(tags).to include('name="discourse-track-view-session-id"')
|
|
expect(tags).to include('name="discourse-beacon-pageview-enabled"')
|
|
end
|
|
|
|
it "omits beacon tracking meta tags when dashboard_improvements is disabled" do
|
|
SiteSetting.dashboard_improvements = false
|
|
|
|
tags = helper.discourse_pageview_tracking_meta_tags
|
|
|
|
expect(tags).to include('name="discourse-track-view-session-id"')
|
|
expect(tags).not_to include('name="discourse-beacon-pageview-enabled"')
|
|
end
|
|
|
|
it "includes beacon tracking meta tags for browser pageview event triggers" do
|
|
SiteSetting.dashboard_improvements = true
|
|
SiteSetting.persist_browser_pageview_events = false
|
|
SiteSetting.trigger_browser_pageview_events = true
|
|
|
|
tags = helper.discourse_pageview_tracking_meta_tags
|
|
|
|
expect(tags).to include('name="discourse-track-view-session-id"')
|
|
expect(tags).to include('name="discourse-beacon-pageview-enabled"')
|
|
end
|
|
|
|
it "includes the engagement tracking meta tag when pageview events are persisted" do
|
|
SiteSetting.persist_browser_pageview_events = true
|
|
|
|
tags = helper.discourse_pageview_tracking_meta_tags
|
|
|
|
expect(tags).to include('name="discourse-engagement-tracking-enabled"')
|
|
end
|
|
|
|
it "omits the engagement tracking meta tag in a trigger-only config so the browser does not send /srv/se beacons the server rejects" do
|
|
SiteSetting.dashboard_improvements = true
|
|
SiteSetting.persist_browser_pageview_events = false
|
|
SiteSetting.trigger_browser_pageview_events = true
|
|
|
|
tags = helper.discourse_pageview_tracking_meta_tags
|
|
|
|
expect(tags).to include('name="discourse-beacon-pageview-enabled"')
|
|
expect(tags).not_to include('name="discourse-engagement-tracking-enabled"')
|
|
end
|
|
end
|
|
|
|
describe "preload_script" do
|
|
def script_tag(url, entrypoint, nonce)
|
|
<<~HTML
|
|
<script defer src="#{url}" data-discourse-entrypoint="#{entrypoint}" nonce="#{nonce}"></script>
|
|
HTML
|
|
end
|
|
|
|
it "does not send crawler content to logged on users" do
|
|
controller.stubs(:use_crawler_layout?).returns(false)
|
|
helper.stubs(:current_user).returns(Fabricate(:user))
|
|
|
|
helper.request.user_agent = "Firefox"
|
|
expect(helper.include_crawler_content?).to eq(false)
|
|
end
|
|
|
|
it "sends crawler content to logged on users who wants to print" do
|
|
helper.stubs(:current_user).returns(Fabricate(:user))
|
|
controller.stubs(:use_crawler_layout?).returns(false)
|
|
helper.stubs(:params).returns(print: true)
|
|
|
|
expect(helper.include_crawler_content?).to eq(true)
|
|
end
|
|
|
|
it "sends crawler content to logged on users with a crawler user agent" do
|
|
helper.stubs(:current_user).returns(Fabricate(:user))
|
|
controller.stubs(:use_crawler_layout?).returns(true)
|
|
|
|
expect(helper.include_crawler_content?).to eq(true)
|
|
end
|
|
|
|
it "sends crawler content to old mobiles" do
|
|
controller.stubs(:use_crawler_layout?).returns(false)
|
|
|
|
helper.request.user_agent =
|
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25"
|
|
|
|
expect(helper.include_crawler_content?).to eq(true)
|
|
end
|
|
|
|
it "does not send crawler content to new mobiles" do
|
|
controller.stubs(:use_crawler_layout?).returns(false)
|
|
|
|
helper.request.user_agent =
|
|
"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Mobile Safari/537.36 (compatible"
|
|
|
|
expect(helper.include_crawler_content?).to eq(false)
|
|
end
|
|
|
|
context "with s3 CDN" do
|
|
before do
|
|
global_setting :s3_bucket, "test_bucket"
|
|
global_setting :s3_region, "ap-australia"
|
|
global_setting :s3_access_key_id, "123"
|
|
global_setting :s3_secret_access_key, "123"
|
|
global_setting :s3_cdn_url, "https://s3cdn.com"
|
|
|
|
# Backend RSpec tests might be run without real manifest/assets
|
|
EmberAssets.stubs(:script_chunks).returns(
|
|
{ "discourse" => ["js/discourse-20n62q6s.digested"] },
|
|
)
|
|
end
|
|
|
|
it "deals correctly with subfolder" do
|
|
set_subfolder "/community"
|
|
expect(helper.preload_script("discourse")).to include(
|
|
%r{https://s3cdn.com/assets/js/discourse-\w{8}.digested.js},
|
|
)
|
|
end
|
|
|
|
it "replaces cdn URLs with s3 cdn subfolder paths" do
|
|
global_setting :s3_cdn_url, "https://s3cdn.com/s3_subpath"
|
|
set_cdn_url "https://awesome.com"
|
|
set_subfolder "/community"
|
|
expect(helper.preload_script("discourse")).to include(
|
|
%r{https://s3cdn.com/s3_subpath/assets/js/discourse-\w{8}.digested.js},
|
|
)
|
|
end
|
|
|
|
it "returns magic brotli mangling for brotli requests" do
|
|
helper.request.env["HTTP_ACCEPT_ENCODING"] = "br"
|
|
link = helper.preload_script("discourse")
|
|
|
|
expect(link).to include(%r{https://s3cdn.com/assets/br/discourse-\w{8}.digested.js})
|
|
end
|
|
|
|
it "gives s3 cdn if asset host is not set" do
|
|
link = helper.preload_script("discourse")
|
|
|
|
expect(link).to include(%r{https://s3cdn.com/assets/js/discourse-\w{8}.digested.js})
|
|
end
|
|
|
|
it "can fall back to gzip compression" do
|
|
helper.request.env["HTTP_ACCEPT_ENCODING"] = "gzip"
|
|
link = helper.preload_script("discourse")
|
|
expect(link).to include(%r{https://s3cdn.com/assets/gz/discourse-\w{8}.digested.js})
|
|
end
|
|
|
|
it "gives s3 cdn even if asset host is set" do
|
|
set_cdn_url "https://awesome.com"
|
|
link = helper.preload_script("discourse")
|
|
|
|
expect(link).to include(%r{https://s3cdn.com/assets/js/discourse-\w{8}.digested.js})
|
|
end
|
|
|
|
it "uses separate asset CDN if configured" do
|
|
global_setting :s3_asset_cdn_url, "https://s3-asset-cdn.example.com"
|
|
expect(helper.preload_script("discourse")).to include(
|
|
%r{https://s3-asset-cdn.example.com/assets/js/discourse-\w{8}.digested.js},
|
|
)
|
|
end
|
|
end
|
|
|
|
it "includes extra attrs when provided" do
|
|
result =
|
|
helper.preload_script(
|
|
"plugins/my-plugin",
|
|
attrs: {
|
|
"data-plugin-name": "my-plugin",
|
|
"data-preinstalled": "true",
|
|
"data-official": "true",
|
|
},
|
|
)
|
|
expect(result).to include('data-plugin-name="my-plugin"')
|
|
expect(result).to include('data-preinstalled="true"')
|
|
expect(result).to include('data-official="true"')
|
|
end
|
|
|
|
it "does not include extra attrs when none are provided" do
|
|
result = helper.preload_script("discourse")
|
|
expect(result).not_to include("data-plugin-name")
|
|
expect(result).not_to include("data-preinstalled")
|
|
expect(result).not_to include("data-official")
|
|
end
|
|
|
|
it "escapes attr values" do
|
|
result =
|
|
helper.preload_script("plugins/test", attrs: { "data-plugin-name": "<script>xss</script>" })
|
|
expect(result).not_to include("<script>xss</script>")
|
|
expect(result).to include("<script>xss</script>")
|
|
end
|
|
|
|
it "renders preinstalled and official as false for non-preinstalled plugins" do
|
|
result =
|
|
helper.preload_script(
|
|
"plugins/my-plugin",
|
|
attrs: {
|
|
"data-plugin-name": "my-plugin",
|
|
"data-preinstalled": "false",
|
|
"data-official": "false",
|
|
},
|
|
)
|
|
expect(result).to include('data-preinstalled="false"')
|
|
expect(result).to include('data-official="false"')
|
|
end
|
|
end
|
|
|
|
describe "add_resource_preload_list" do
|
|
it "adds resources to the preload list" do
|
|
add_resource_preload_list("/assets/discourse.js", "script")
|
|
add_resource_preload_list("/assets/discourse.css", "style")
|
|
|
|
expect(controller.instance_variable_get(:@asset_preload_links).size).to eq(2)
|
|
end
|
|
|
|
it "adds resources to the preload list when preload_script is called" do
|
|
helper.preload_script("discourse")
|
|
|
|
expect(controller.instance_variable_get(:@asset_preload_links).size).to eq(1)
|
|
end
|
|
|
|
it "adds resources to the preload list when discourse_stylesheet_link_tag is called" do
|
|
helper.discourse_stylesheet_link_tag(:common)
|
|
|
|
expect(controller.instance_variable_get(:@asset_preload_links).size).to eq(1)
|
|
end
|
|
|
|
it "adds resources as the correct type" do
|
|
helper.discourse_stylesheet_link_tag(:common)
|
|
helper.preload_script("discourse")
|
|
|
|
expect(controller.instance_variable_get(:@asset_preload_links)[0]).to match(/as="style"/)
|
|
expect(controller.instance_variable_get(:@asset_preload_links)[1]).to match(/as="script"/)
|
|
end
|
|
end
|
|
|
|
describe "escape_unicode" do
|
|
it "encodes tags" do
|
|
expect(helper.escape_unicode("<tag>")).to eq("\u003ctag>")
|
|
end
|
|
it "survives junk text" do
|
|
expect(helper.escape_unicode("hello \xc3\x28 world")).to match(/hello.*world/)
|
|
end
|
|
end
|
|
|
|
describe "render_sitelinks_search_tag" do
|
|
context "for non-subfolder install" do
|
|
context "when on homepage" do
|
|
it "will return sitelinks search tag" do
|
|
helper.stubs(:current_page?).returns(false)
|
|
helper.stubs(:current_page?).with("/").returns(true)
|
|
|
|
sitelinks_search_tag =
|
|
JSON.parse(
|
|
helper
|
|
.render_sitelinks_search_tag
|
|
.gsub('<script type="application/ld+json">', "")
|
|
.gsub("</script>", ""),
|
|
)
|
|
|
|
expect(sitelinks_search_tag["@type"]).to eq("WebSite")
|
|
expect(sitelinks_search_tag["potentialAction"]["@type"]).to eq("SearchAction")
|
|
expect(sitelinks_search_tag["name"]).to eq(SiteSetting.title)
|
|
expect(sitelinks_search_tag["url"]).to eq(Discourse.base_url)
|
|
end
|
|
end
|
|
context "when not on homepage" do
|
|
it "will not return sitelinks search tag" do
|
|
helper.stubs(:current_page?).returns(true)
|
|
helper.stubs(:current_page?).with("/").returns(false)
|
|
helper.stubs(:current_page?).with(Discourse.base_path).returns(false)
|
|
expect(helper.render_sitelinks_search_tag).to be_nil
|
|
end
|
|
end
|
|
end
|
|
context "for subfolder install" do
|
|
context "when on homepage" do
|
|
it "will return sitelinks search tag" do
|
|
Discourse.stubs(:base_path).returns("/subfolder-base-path/")
|
|
helper.stubs(:current_page?).returns(false)
|
|
helper.stubs(:current_page?).with(Discourse.base_path).returns(true)
|
|
expect(helper.render_sitelinks_search_tag).to include('"@type":"SearchAction"')
|
|
expect(helper.render_sitelinks_search_tag).to include("subfolder-base-path")
|
|
end
|
|
end
|
|
context "when not on homepage" do
|
|
it "will not return sitelinks search tag" do
|
|
Discourse.stubs(:base_path).returns("/subfolder-base-path/")
|
|
helper.stubs(:current_page?).returns(true)
|
|
helper.stubs(:current_page?).with("/").returns(false)
|
|
helper.stubs(:current_page?).with(Discourse.base_path).returns(false)
|
|
expect(helper.render_sitelinks_search_tag).to be_nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "application_logo_url" do
|
|
context "when a dark color scheme is active" do
|
|
before do
|
|
dark_theme =
|
|
Theme.create(
|
|
name: "Dark",
|
|
user_id: Discourse::SYSTEM_USER_ID,
|
|
color_scheme_id:
|
|
ColorScheme.find_by(base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Dark"]).id,
|
|
)
|
|
helper.request.env[:resolved_theme_id] = dark_theme.id
|
|
end
|
|
|
|
context "when on desktop" do
|
|
before { helper.stubs(:mobile_device?).returns(false) }
|
|
|
|
context "when logo_dark is not set" do
|
|
it "will return site_logo_url instead" do
|
|
expect(helper.application_logo_url).to eq(SiteSetting.site_logo_url)
|
|
end
|
|
end
|
|
|
|
context "when logo_dark is set" do
|
|
before { SiteSetting.logo_dark = Fabricate(:upload, url: "/images/logo-dark.png") }
|
|
|
|
it "will return site_logo_dark_url" do
|
|
expect(helper.application_logo_url).to eq(SiteSetting.site_logo_dark_url)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when on mobile" do
|
|
before { helper.stubs(:mobile_device?).returns(true) }
|
|
|
|
context "when mobile_logo_dark is not set" do
|
|
it "will return site_mobile_logo_url instead" do
|
|
expect(helper.application_logo_url).to eq(SiteSetting.site_mobile_logo_url)
|
|
end
|
|
end
|
|
|
|
context "when mobile_logo_dark is set" do
|
|
before do
|
|
SiteSetting.mobile_logo_dark = Fabricate(:upload, url: "/images/mobile-logo-dark.png")
|
|
end
|
|
|
|
it "will return site_mobile_logo_dark_url" do
|
|
expect(helper.application_logo_url).to eq(SiteSetting.site_mobile_logo_dark_url)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "application_logo_dark_url" do
|
|
context "when dark theme is not present" do
|
|
context "when dark logo is not present" do
|
|
it "should return nothing" do
|
|
expect(helper.application_logo_dark_url.present?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when dark theme is present" do
|
|
before do
|
|
_dark_theme =
|
|
Theme.create(
|
|
name: "Dark",
|
|
user_id: Discourse::SYSTEM_USER_ID,
|
|
color_scheme_id:
|
|
ColorScheme.find_by(base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Dark"]).id,
|
|
)
|
|
end
|
|
|
|
context "when dark logo is not present" do
|
|
it "should return nothing" do
|
|
expect(helper.application_logo_dark_url.present?).to eq(false)
|
|
end
|
|
end
|
|
|
|
context "when dark logo is present" do
|
|
before { SiteSetting.logo_dark = Fabricate(:upload, url: "/images/logo-dark.png") }
|
|
|
|
it "should return correct url" do
|
|
expect(helper.application_logo_dark_url).to eq(SiteSetting.site_logo_dark_url)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when dark theme is present and selected" do
|
|
before do
|
|
dark_theme =
|
|
Theme.create(
|
|
name: "Dark",
|
|
user_id: Discourse::SYSTEM_USER_ID,
|
|
color_scheme_id:
|
|
ColorScheme.find_by(base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Dark"]).id,
|
|
)
|
|
helper.request.env[:resolved_theme_id] = dark_theme.id
|
|
SiteSetting.logo_dark = Fabricate(:upload, url: "/images/logo-dark.png")
|
|
end
|
|
|
|
it "should return nothing" do
|
|
expect(helper.application_logo_url).to eq(SiteSetting.site_logo_dark_url)
|
|
expect(helper.application_logo_dark_url.present?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#mobile_device?" do
|
|
it "is false if user agent is not mobile" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
|
|
)
|
|
expect(helper.mobile_device?).to be_falsey
|
|
end
|
|
|
|
it "is true for iPhone" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13D15 Safari/601.1",
|
|
)
|
|
expect(helper.mobile_device?).to eq(true)
|
|
end
|
|
|
|
it "is true for Android Samsung Galaxy" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36",
|
|
)
|
|
expect(helper.mobile_device?).to eq(true)
|
|
end
|
|
|
|
it "is true for Android Google Nexus 5X" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5X Build/MDB08I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.43 Mobile Safari/537.36",
|
|
)
|
|
expect(helper.mobile_device?).to eq(true)
|
|
end
|
|
|
|
it "is false for iPad" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B14 3 Safari/601.1",
|
|
)
|
|
expect(helper.mobile_device?).to eq(false)
|
|
end
|
|
|
|
it "is false for Nexus 10 tablet" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 10 Build/LMY49G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.91 Safari/537.36",
|
|
)
|
|
expect(helper.mobile_device?).to be_falsey
|
|
end
|
|
|
|
it "is false for Nexus 7 tablet" do
|
|
controller
|
|
.request
|
|
.stubs(:user_agent)
|
|
.returns(
|
|
"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MMB29Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.91 Safari/537.36",
|
|
)
|
|
expect(helper.mobile_device?).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe "#html_classes" do
|
|
fab!(:user)
|
|
|
|
it "includes 'rtl' when the I18n.locale is rtl" do
|
|
I18n.stubs(:locale).returns(:he)
|
|
expect(helper.html_classes.split(" ")).to include("rtl")
|
|
end
|
|
|
|
it "returns an empty string when the I18n.locale is not rtl" do
|
|
I18n.stubs(:locale).returns(:zh_TW)
|
|
expect(helper.html_classes.split(" ")).not_to include("rtl")
|
|
end
|
|
|
|
describe "text size" do
|
|
context "with a user option" do
|
|
before do
|
|
user.user_option.text_size = "larger"
|
|
user.user_option.save!
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
end
|
|
|
|
it "ignores invalid text sizes" do
|
|
helper.request.cookies["text_size"] = "invalid"
|
|
expect(helper.html_classes.split(" ")).to include("text-size-larger")
|
|
end
|
|
|
|
it "ignores missing text size" do
|
|
helper.request.cookies["text_size"] = nil
|
|
expect(helper.html_classes.split(" ")).to include("text-size-larger")
|
|
end
|
|
|
|
it "ignores cookies with lower sequence" do
|
|
user.user_option.update!(text_size_seq: 2)
|
|
|
|
helper.request.cookies["text_size"] = "normal|1"
|
|
expect(helper.html_classes.split(" ")).to include("text-size-larger")
|
|
end
|
|
|
|
it "prioritises the cookie specified text size" do
|
|
user.user_option.update!(text_size_seq: 2)
|
|
|
|
helper.request.cookies["text_size"] = "largest|4"
|
|
expect(helper.html_classes.split(" ")).to include("text-size-largest")
|
|
end
|
|
|
|
it "includes the user specified text size" do
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
expect(helper.html_classes.split(" ")).to include("text-size-larger")
|
|
end
|
|
end
|
|
|
|
it "falls back to the default text size for anon" do
|
|
expect(helper.html_classes.split(" ")).to include("text-size-normal")
|
|
SiteSetting.default_text_size = "largest"
|
|
expect(helper.html_classes.split(" ")).to include("text-size-largest")
|
|
end
|
|
end
|
|
|
|
it "includes 'anon' for anonymous users and excludes when logged in" do
|
|
expect(helper.html_classes.split(" ")).to include("anon")
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
expect(helper.html_classes.split(" ")).not_to include("anon")
|
|
end
|
|
end
|
|
|
|
describe "gsub_emoji_to_unicode" do
|
|
it "converts all emoji to unicode" do
|
|
expect(
|
|
helper.gsub_emoji_to_unicode("Boat Talk: my :sailboat: boat: why is it so slow? :snail:"),
|
|
).to eq("Boat Talk: my ⛵ boat: why is it so slow? 🐌")
|
|
end
|
|
end
|
|
|
|
describe "preloaded_json" do
|
|
fab!(:user)
|
|
|
|
it "returns empty JSON if preloader is not initialized" do
|
|
@application_layout_preloader = nil
|
|
expect(helper.preloaded_json).to eq("{}")
|
|
end
|
|
|
|
it "escapes and strips invalid unicode and strips in json body" do
|
|
@application_layout_preloader =
|
|
ApplicationLayoutPreloader.new(
|
|
guardian: Guardian.new(user),
|
|
theme_id: nil,
|
|
theme_target: nil,
|
|
login_method: nil,
|
|
)
|
|
|
|
@application_layout_preloader.store_preloaded("test", %{["</script><script> \x80"]})
|
|
expect(helper.preloaded_json).to include(
|
|
%{"test":"[\\"\\u003c\\\\/script\\u003e\\u003cscript\\u003e \uFFFD\\"]"},
|
|
)
|
|
expect(helper.preloaded_json).not_to include("</script>")
|
|
end
|
|
end
|
|
|
|
describe "client_side_setup_data" do
|
|
context "when Rails.env.development? is true" do
|
|
before { Rails.env.stubs(:development?).returns(true) }
|
|
|
|
it "returns the correct service worker url" do
|
|
expect(helper.client_side_setup_data[:service_worker_url]).to eq("service-worker.js")
|
|
end
|
|
|
|
it "returns the svg_icon_list in the setup data" do
|
|
expect(helper.client_side_setup_data[:svg_icon_list]).not_to eq(nil)
|
|
end
|
|
|
|
it "does not return debug_preloaded_app_data without the env var" do
|
|
expect(helper.client_side_setup_data.key?(:debug_preloaded_app_data)).to eq(false)
|
|
end
|
|
|
|
context "if the DEBUG_PRELOADED_APP_DATA env var is provided" do
|
|
before { ENV["DEBUG_PRELOADED_APP_DATA"] = "true" }
|
|
|
|
it "returns that key as true" do
|
|
expect(helper.client_side_setup_data[:debug_preloaded_app_data]).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "crawlable_meta_data" do
|
|
it "escapes the description exactly once" do
|
|
result =
|
|
helper.crawlable_meta_data(description: %(Tom & O'Reilly "><script>alert(1)</script>))
|
|
|
|
expect(result).to include(
|
|
%(<meta property="og:description" content="Tom & O'Reilly "><script>alert(1)</script>" />),
|
|
)
|
|
expect(result).not_to include("<script>")
|
|
end
|
|
|
|
it "Supports ASCII URLs with odd chars" do
|
|
result =
|
|
helper.crawlable_meta_data(
|
|
url: (+"http://localhost/ión").force_encoding("ASCII-8BIT").freeze,
|
|
)
|
|
|
|
expect(result).to include("ión")
|
|
end
|
|
|
|
context "with opengraph image" do
|
|
it "returns the correct image" do
|
|
SiteSetting.opengraph_image = Fabricate(:upload, url: "/images/og-image.png")
|
|
|
|
SiteSetting.x_summary_large_image = Fabricate(:upload, url: "/images/twitter.png")
|
|
|
|
SiteSetting.large_icon = Fabricate(:upload, url: "/images/large_icon.png")
|
|
|
|
SiteSetting.apple_touch_icon =
|
|
Fabricate(:upload, url: "/images/default-apple-touch-icon.png")
|
|
|
|
SiteSetting.logo = Fabricate(:upload, url: "/images/d-logo-sketch.png")
|
|
|
|
expect(helper.crawlable_meta_data(image: "some-image.png")).to include("some-image.png")
|
|
|
|
expect(helper.crawlable_meta_data).to include(SiteSetting.site_opengraph_image_url)
|
|
|
|
SiteSetting.opengraph_image = ""
|
|
|
|
expect(helper.crawlable_meta_data).to include(SiteSetting.site_x_summary_large_image_url)
|
|
|
|
SiteSetting.x_summary_large_image = ""
|
|
|
|
expect(helper.crawlable_meta_data).to include(SiteSetting.site_large_icon_url)
|
|
|
|
SiteSetting.large_icon = ""
|
|
SiteSetting.logo_small = ""
|
|
|
|
expect(helper.crawlable_meta_data).to include(SiteSetting.site_logo_url)
|
|
|
|
SiteSetting.logo = ""
|
|
|
|
expect(helper.crawlable_meta_data).to include(
|
|
Upload.find(SiteIconManager::SKETCH_LOGO_ID).url,
|
|
)
|
|
end
|
|
|
|
it "does not allow SVG images for twitter:image, falls back to site logo or nothing if site logo is SVG too" do
|
|
SiteSetting.logo = Fabricate(:upload, url: "/images/d-logo-sketch.png")
|
|
SiteSetting.opengraph_image = Fabricate(:upload, url: "/images/og-image.png")
|
|
|
|
expect(helper.crawlable_meta_data).to include(<<~HTML)
|
|
<meta name=\"twitter:image\" content=\"#{SiteSetting.site_opengraph_image_url}\" />
|
|
HTML
|
|
|
|
SiteSetting.opengraph_image = Fabricate(:upload, url: "/images/og-image.svg")
|
|
|
|
expect(helper.crawlable_meta_data).to include(<<~HTML)
|
|
<meta name=\"twitter:image\" content=\"#{SiteSetting.site_logo_url}\" />
|
|
HTML
|
|
|
|
SiteSetting.x_summary_large_image = Fabricate(:upload, url: "/images/twitter.png")
|
|
|
|
expect(helper.crawlable_meta_data).to include(<<~HTML)
|
|
<meta name=\"twitter:image\" content=\"#{SiteSetting.site_x_summary_large_image_url}\" />
|
|
HTML
|
|
|
|
SiteSetting.x_summary_large_image = Fabricate(:upload, url: "/images/twitter.svg")
|
|
|
|
expect(helper.crawlable_meta_data).to include(<<~HTML)
|
|
<meta name=\"twitter:image\" content=\"#{SiteSetting.site_logo_url}\" />
|
|
HTML
|
|
|
|
SiteSetting.logo = Fabricate(:upload, url: "/images/d-logo-sketch.svg")
|
|
|
|
expect(helper.crawlable_meta_data).not_to include("twitter:image")
|
|
end
|
|
end
|
|
|
|
context "with opengraph image dimensions" do
|
|
it "includes og:image:width and og:image:height when provided" do
|
|
result =
|
|
helper.crawlable_meta_data(image: "some-image.png", image_width: 1024, image_height: 768)
|
|
expect(result).to include('<meta property="og:image:width" content="1024" />')
|
|
expect(result).to include('<meta property="og:image:height" content="768" />')
|
|
end
|
|
|
|
it "does not include og:image dimensions when not provided" do
|
|
result = helper.crawlable_meta_data(image: "some-image.png")
|
|
expect(result).not_to include("og:image:width")
|
|
expect(result).not_to include("og:image:height")
|
|
end
|
|
end
|
|
|
|
context "with opengraph image type" do
|
|
it "includes og:image:type when provided" do
|
|
result = helper.crawlable_meta_data(image: "some-image.png", image_type: "image/png")
|
|
expect(result).to include('<meta property="og:image:type" content="image/png" />')
|
|
end
|
|
|
|
it "does not include og:image:type when not provided" do
|
|
result = helper.crawlable_meta_data(image: "some-image.png")
|
|
expect(result).not_to include("og:image:type")
|
|
end
|
|
end
|
|
|
|
context "with breadcrumbs" do
|
|
subject(:metadata) { helper.crawlable_meta_data(breadcrumbs: breadcrumbs) }
|
|
|
|
let(:breadcrumbs) do
|
|
[{ name: "section1", color: "ff0000" }, { name: "section2", color: "0000ff" }]
|
|
end
|
|
let(:tags) { <<~HTML.strip }
|
|
<meta property="og:article:section" content="section1" />
|
|
<meta property="og:article:section:color" content="ff0000" />
|
|
<meta property="og:article:section" content="section2" />
|
|
<meta property="og:article:section:color" content="0000ff" />
|
|
HTML
|
|
|
|
it "generates section and color tags" do
|
|
expect(metadata).to include tags
|
|
end
|
|
end
|
|
|
|
context "with tags" do
|
|
subject(:metadata) { helper.crawlable_meta_data(tags: tags) }
|
|
|
|
let(:tags) { %w[tag1 tag2] }
|
|
let(:output_tags) { <<~HTML.strip }
|
|
<meta property="og:article:tag" content="tag1" />
|
|
<meta property="og:article:tag" content="tag2" />
|
|
HTML
|
|
|
|
it "generates tag tags" do
|
|
expect(metadata).to include output_tags
|
|
end
|
|
end
|
|
|
|
context "with custom site name" do
|
|
before { SiteSetting.title = "Default Site Title" }
|
|
|
|
it "uses the provided site name in og:site_name" do
|
|
custom_site_name = "Custom Site Name"
|
|
result = helper.crawlable_meta_data(site_name: custom_site_name)
|
|
|
|
expect(result).to include(
|
|
"<meta property=\"og:site_name\" content=\"#{custom_site_name}\" />",
|
|
)
|
|
end
|
|
|
|
it "falls back to the default site title if no custom site name is provided" do
|
|
result = helper.crawlable_meta_data
|
|
|
|
expect(result).to include(
|
|
"<meta property=\"og:site_name\" content=\"#{SiteSetting.title}\" />",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with reading time and likes" do
|
|
it "uses translated strings for reading time and likes" do
|
|
result = helper.crawlable_meta_data(read_time: 5, like_count: 10)
|
|
|
|
expect(result).to include(
|
|
"<meta name=\"twitter:label1\" value=\"#{I18n.t("reading_time")}\" />",
|
|
)
|
|
expect(result).to include(
|
|
"<meta name=\"twitter:data1\" value=\"#{I18n.t("reading_time_minutes", count: 5)}\" />",
|
|
)
|
|
expect(result).to include("<meta name=\"twitter:label2\" value=\"#{I18n.t("likes")}\" />")
|
|
expect(result).to include(
|
|
"<meta name=\"twitter:data2\" value=\"#{I18n.t("likes_count", count: 10)}\" />",
|
|
)
|
|
end
|
|
|
|
it "handles singular reading time correctly" do
|
|
result = helper.crawlable_meta_data(read_time: 1, like_count: 1)
|
|
|
|
expect(result).to include("1 min 🕑")
|
|
expect(result).not_to include("1 mins")
|
|
end
|
|
|
|
it "does not include twitter card labels when read_time or like_count is missing" do
|
|
result = helper.crawlable_meta_data(read_time: 5)
|
|
expect(result).not_to include("twitter:label1")
|
|
|
|
result = helper.crawlable_meta_data(like_count: 10)
|
|
expect(result).not_to include("twitter:label1")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#title_content" do
|
|
it "returns the correct title" do
|
|
SiteSetting.title = "Test Title"
|
|
result = helper.title_content
|
|
|
|
expect(result).to include("Test Title")
|
|
end
|
|
|
|
it "accepts a content argument" do
|
|
helper.stubs(:content_for?).with(:title).returns(true)
|
|
helper.stubs(:content_for).with(:title).returns("Custom Title")
|
|
|
|
result = helper.title_content
|
|
|
|
expect(result).to include("Custom Title")
|
|
end
|
|
end
|
|
|
|
describe "#description_content" do
|
|
it "returns the correct description" do
|
|
SiteSetting.site_description = "Test Description"
|
|
result = helper.description_content
|
|
|
|
expect(result).to include("Test Description")
|
|
end
|
|
|
|
it "accepts a content argument" do
|
|
@description_meta = "Custom Description"
|
|
|
|
result = helper.description_content
|
|
|
|
expect(result).to include("Custom Description")
|
|
end
|
|
end
|
|
|
|
describe "when a plugin registers the :meta_data_content modifier" do
|
|
let!(:plugin) { Plugin::Instance.new }
|
|
let!(:modifier) { :meta_data_content }
|
|
let!(:block) do
|
|
Proc.new do |content, property, opts|
|
|
next "modified by plugin" if property == :description
|
|
next "BIG TITLE" if property == :title
|
|
content
|
|
end
|
|
end
|
|
|
|
before { DiscoursePluginRegistry.register_modifier(plugin, modifier, &block) }
|
|
after { DiscoursePluginRegistry.unregister_modifier(plugin, modifier, &block) }
|
|
|
|
it "allows the plugin to modify the meta tags" do
|
|
result =
|
|
helper.crawlable_meta_data(
|
|
description: "This is a test description",
|
|
title: "to be overridden",
|
|
)
|
|
|
|
expect(result).to include(
|
|
"<meta property=\"og:description\" content=\"modified by plugin\" />",
|
|
)
|
|
expect(result).to include("<meta property=\"og:title\" content=\"BIG TITLE\" />")
|
|
end
|
|
|
|
it "modifies the title tag" do
|
|
title = helper.title_content
|
|
|
|
expect(title).to include("BIG TITLE")
|
|
end
|
|
|
|
it "modifies the description tag" do
|
|
description = helper.description_content
|
|
|
|
expect(description).to include("modified by plugin")
|
|
end
|
|
|
|
it "does not modify the `title` SiteSetting" do
|
|
SiteSetting.title = "Test Title"
|
|
result = helper.title_content
|
|
|
|
expect(result).to include("BIG TITLE")
|
|
expect(SiteSetting.title).to eq("Test Title")
|
|
end
|
|
|
|
it "does not modify the `site_description` SiteSetting" do
|
|
SiteSetting.site_description = "Test Description"
|
|
result = helper.description_content
|
|
|
|
expect(result).to include("modified by plugin")
|
|
expect(SiteSetting.site_description).to eq("Test Description")
|
|
end
|
|
end
|
|
|
|
describe "discourse_color_scheme_stylesheets" do
|
|
fab!(:user)
|
|
|
|
it "returns a stylesheet link tag by default" do
|
|
cs_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(cs_stylesheets).to include("stylesheets/color_definitions")
|
|
end
|
|
|
|
it "returns two color scheme link tags when dark mode is enabled" do
|
|
Theme.find_default.update!(dark_color_scheme_id: ColorScheme.where(name: "Dark").pick(:id))
|
|
cs_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
|
|
expect(cs_stylesheets).to include("(prefers-color-scheme: dark)")
|
|
expect(cs_stylesheets.scan("stylesheets/color_definitions").size).to eq(2)
|
|
end
|
|
|
|
it "handles a missing dark color scheme gracefully" do
|
|
scheme = ColorScheme.create!(name: "pyramid")
|
|
Theme.find_default.update!(dark_color_scheme_id: scheme.id)
|
|
scheme.destroy!
|
|
cs_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
|
|
expect(cs_stylesheets).to include("stylesheets/color_definitions")
|
|
expect(cs_stylesheets).not_to include("(prefers-color-scheme: dark)")
|
|
end
|
|
|
|
context "with custom light scheme" do
|
|
before do
|
|
@new_cs = Fabricate(:color_scheme, name: "Flamboyant", user_selectable: true)
|
|
user.user_option.color_scheme_id = @new_cs.id
|
|
user.user_option.save!
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
end
|
|
|
|
it "returns color scheme from user option value" do
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).to include("color_definitions_flamboyant")
|
|
end
|
|
|
|
it "falls back to base scheme when the scheme is no longer user selectable" do
|
|
@new_cs.update!(user_selectable: false)
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).not_to include("color_definitions_flamboyant")
|
|
expect(color_stylesheets).to include("color_definitions_light-default")
|
|
end
|
|
|
|
it "keeps a non-user-selectable scheme that is the theme's own color scheme" do
|
|
@new_cs.update!(user_selectable: false)
|
|
Theme.find_default.update!(color_scheme_id: @new_cs.id)
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).to include("color_definitions_flamboyant")
|
|
end
|
|
|
|
it "returns color scheme from cookie value" do
|
|
cs = ColorScheme.where(name: "Dark").first
|
|
helper.request.cookies["color_scheme_id"] = cs.id
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
|
|
expect(color_stylesheets).to include("color_definitions_dark")
|
|
expect(color_stylesheets).not_to include("color_definitions_flamboyant")
|
|
end
|
|
|
|
it "falls back to base scheme with invalid cookie value" do
|
|
helper.request.cookies["color_scheme_id"] = -50
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).not_to include("color_definitions_flamboyant")
|
|
expect(color_stylesheets).to include("color_definitions_light-default")
|
|
end
|
|
end
|
|
|
|
context "with dark scheme with user option and/or cookies" do
|
|
before do
|
|
user.user_option.interface_color_mode = UserOption::LIGHT_MODE
|
|
user.user_option.save!
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
@new_cs = Fabricate(:color_scheme, name: "Custom Color Scheme", user_selectable: true)
|
|
|
|
Theme.find_default.update!(dark_color_scheme_id: ColorScheme.where(name: "Dark").pick(:id))
|
|
end
|
|
|
|
it "returns no dark scheme stylesheet when user has disabled that option" do
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
|
|
expect(color_stylesheets).to include("stylesheets/color_definitions")
|
|
expect(color_stylesheets).not_to include("(prefers-color-scheme: dark)")
|
|
end
|
|
|
|
it "returns user-selected dark color scheme stylesheet" do
|
|
user.user_option.update!(
|
|
dark_scheme_id: @new_cs.id,
|
|
interface_color_mode: UserOption::AUTO_MODE,
|
|
)
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).to include("(prefers-color-scheme: dark)")
|
|
expect(color_stylesheets).to include("custom-color-scheme")
|
|
end
|
|
|
|
it "respects cookie value over user option for dark color scheme" do
|
|
user.user_option.update!(interface_color_mode: UserOption::AUTO_MODE)
|
|
helper.request.cookies["dark_scheme_id"] = @new_cs.id
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).to include("(prefers-color-scheme: dark)")
|
|
expect(color_stylesheets).to include("custom-color-scheme")
|
|
end
|
|
|
|
it "returns no dark scheme with invalid cookie value" do
|
|
helper.request.cookies["dark_scheme_id"] = -10
|
|
|
|
color_stylesheets = helper.discourse_color_scheme_stylesheets
|
|
expect(color_stylesheets).not_to include("(prefers-color-scheme: dark)")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "dark_color_scheme?" do
|
|
it "returns false for the base color scheme" do
|
|
expect(helper.dark_color_scheme?).to eq(false)
|
|
end
|
|
|
|
it "works correctly for a dark scheme" do
|
|
dark_theme =
|
|
Theme.create(
|
|
name: "Dark",
|
|
user_id: Discourse::SYSTEM_USER_ID,
|
|
color_scheme_id:
|
|
ColorScheme.find_by(base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Dark"]).id,
|
|
)
|
|
helper.request.env[:resolved_theme_id] = dark_theme.id
|
|
|
|
expect(helper.dark_color_scheme?).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe "html_lang" do
|
|
fab!(:user)
|
|
|
|
before do
|
|
I18n.locale = :de
|
|
SiteSetting.default_locale = :fr
|
|
end
|
|
|
|
it "returns default locale if no request" do
|
|
helper.request = nil
|
|
expect(helper.html_lang).to eq(SiteSetting.default_locale)
|
|
end
|
|
|
|
it "returns current user locale if request" do
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user
|
|
expect(helper.html_lang).to eq(I18n.locale.to_s)
|
|
end
|
|
end
|
|
|
|
describe "#discourse_theme_color_meta_tags" do
|
|
before do
|
|
light = Fabricate(:color_scheme, user_selectable: true)
|
|
light.color_scheme_colors << ColorSchemeColor.new(name: "header_background", hex: "abcdef")
|
|
light.save!
|
|
helper.request.cookies["color_scheme_id"] = light.id
|
|
|
|
dark = Fabricate(:color_scheme, user_selectable: true)
|
|
dark.color_scheme_colors << ColorSchemeColor.new(name: "header_background", hex: "defabc")
|
|
dark.save!
|
|
helper.request.cookies["dark_scheme_id"] = dark.id
|
|
end
|
|
|
|
it "renders theme-color meta for the light scheme with media=(prefers-color-scheme: light) and another one for the dark scheme with media=(prefers-color-scheme: dark)" do
|
|
expect(helper.discourse_theme_color_meta_tags).to eq(<<~HTML)
|
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#abcdef">
|
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#defabc">
|
|
HTML
|
|
end
|
|
|
|
it "doesn't render theme-color meta tag for the dark scheme if none is set" do
|
|
Theme.find_default.update!(dark_color_scheme_id: -1)
|
|
helper.request.cookies.delete("dark_scheme_id")
|
|
|
|
expect(helper.discourse_theme_color_meta_tags).to eq(<<~HTML)
|
|
<meta name="theme-color" media="all" content="#abcdef">
|
|
HTML
|
|
end
|
|
end
|
|
|
|
describe "#discourse_color_scheme_meta_tag" do
|
|
fab!(:color_scheme)
|
|
|
|
before { Theme.find_default.update!(dark_color_scheme_id: -1) }
|
|
|
|
it "renders a 'light' color-scheme if no dark scheme is set and the current scheme is light" do
|
|
ColorSchemeRevisor.revise(
|
|
color_scheme,
|
|
{ colors: [{ name: "primary", hex: "333333" }, { name: "secondary", hex: "DDDDDD" }] },
|
|
)
|
|
|
|
helper.request.cookies["color_scheme_id"] = color_scheme.id
|
|
|
|
expect(helper.discourse_color_scheme_meta_tag).to eq(<<~HTML)
|
|
<meta name="color-scheme" content="light">
|
|
HTML
|
|
end
|
|
|
|
it "renders a 'dark' color-scheme if no dark scheme is set and the default scheme is dark" do
|
|
ColorSchemeRevisor.revise(
|
|
color_scheme,
|
|
{ colors: [{ name: "primary", hex: "F8F8F8" }, { name: "secondary", hex: "232323" }] },
|
|
)
|
|
@scheme_id = color_scheme.id
|
|
|
|
expect(helper.discourse_color_scheme_meta_tag).to eq(<<~HTML)
|
|
<meta name="color-scheme" content="dark">
|
|
HTML
|
|
end
|
|
|
|
it "renders a 'light dark' color-scheme if a dark scheme is set" do
|
|
dark = Fabricate(:color_scheme, user_selectable: true)
|
|
dark.save!
|
|
helper.request.cookies["dark_scheme_id"] = dark.id
|
|
|
|
expect(helper.discourse_color_scheme_meta_tag).to eq(<<~HTML)
|
|
<meta name="color-scheme" content="light dark">
|
|
HTML
|
|
end
|
|
end
|
|
|
|
describe "#dark_scheme_id" do
|
|
fab!(:dark_scheme) { Fabricate(:color_scheme, user_selectable: true) }
|
|
fab!(:light_scheme) { Fabricate(:color_scheme, user_selectable: true) }
|
|
|
|
before do
|
|
helper.request.cookies["color_scheme_id"] = light_scheme.id
|
|
helper.request.cookies["dark_scheme_id"] = dark_scheme.id
|
|
end
|
|
|
|
it "returns the value set in the dark_scheme_id cookie" do
|
|
expect(helper.dark_scheme_id).to eq(dark_scheme.id)
|
|
end
|
|
end
|
|
|
|
describe "#forced_light_mode?" do
|
|
fab!(:user)
|
|
|
|
context "when the user preference in the database is set to light" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::LIGHT_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_light_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_light_mode?).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "when the user preference in the database is set to dark" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::DARK_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_light_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
end
|
|
|
|
context "when the user preference in the database is set to auto" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::AUTO_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_light_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_light_mode?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#forced_dark_mode?" do
|
|
fab!(:user)
|
|
|
|
context "when the user preference in the database is set to light" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::LIGHT_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_dark_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
end
|
|
|
|
context "when the user preference in the database is set to dark" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::DARK_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_dark_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_dark_mode?).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "when the user preference in the database is set to auto" do
|
|
before do
|
|
user.user_option.update!(interface_color_mode: UserOption::AUTO_MODE)
|
|
helper.stubs(:current_user).returns(user)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `light`" do
|
|
helper.request.cookies["forced_color_mode"] = "light"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns true if the forced_color_mode cookie is set to `dark`" do
|
|
helper.request.cookies["forced_color_mode"] = "dark"
|
|
expect(helper.forced_dark_mode?).to eq(true)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is set to `auto`" do
|
|
helper.request.cookies["forced_color_mode"] = "auto"
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
|
|
it "returns false if the forced_color_mode cookie is not set" do
|
|
helper.request.cookies["forced_color_mode"] = nil
|
|
expect(helper.forced_dark_mode?).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#crawler_topic_container_schema" do
|
|
fab!(:topic)
|
|
|
|
it "returns DiscussionForumPosting schema attributes by default" do
|
|
result = helper.crawler_topic_container_schema(topic)
|
|
expect(result).to include("itemscope")
|
|
expect(result).to include('itemtype="http://schema.org/DiscussionForumPosting"')
|
|
end
|
|
end
|
|
|
|
describe "#crawler_post_schema" do
|
|
fab!(:topic)
|
|
fab!(:first_post) { Fabricate(:post, topic: topic) }
|
|
fab!(:reply) { Fabricate(:post, topic: topic) }
|
|
|
|
it "returns empty string for the first post" do
|
|
expect(helper.crawler_post_schema(first_post, topic)).to eq("")
|
|
end
|
|
|
|
it "returns Comment schema attributes for reply posts" do
|
|
result = helper.crawler_post_schema(reply, topic)
|
|
expect(result).to include('itemprop="comment"')
|
|
expect(result).to include("itemscope")
|
|
expect(result).to include('itemtype="http://schema.org/Comment"')
|
|
end
|
|
end
|
|
|
|
describe "#shared_session_key" do
|
|
fab!(:user)
|
|
|
|
before { SiteSetting.long_polling_base_url = "https://mb.example.com/" }
|
|
|
|
context "when the request carries an auth token" do
|
|
let(:auth_token) { UserAuthToken.generate!(user_id: user.id) }
|
|
|
|
before do
|
|
helper.stubs(:current_user).returns(user)
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::USER_TOKEN_KEY] = auth_token
|
|
end
|
|
|
|
it "binds the stored value to the auth token" do
|
|
key = helper.shared_session_key
|
|
expect(
|
|
Discourse.redis.get(Auth::DefaultCurrentUserProvider.shared_session_redis_key(key)),
|
|
).to eq(auth_token.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "when the auth token does not belong to the current user" do
|
|
fab!(:other_user, :user)
|
|
|
|
before do
|
|
helper.stubs(:current_user).returns(user)
|
|
helper.request.env[
|
|
Auth::DefaultCurrentUserProvider::USER_TOKEN_KEY
|
|
] = UserAuthToken.generate!(user_id: other_user.id)
|
|
end
|
|
|
|
it "returns no shared session key" do
|
|
expect(helper.shared_session_key).to eq(nil)
|
|
end
|
|
end
|
|
|
|
context "when the auth token is impersonating the current user" do
|
|
fab!(:admin)
|
|
|
|
let(:auth_token) do
|
|
UserAuthToken
|
|
.generate!(user_id: admin.id)
|
|
.tap do |token|
|
|
token.update!(impersonated_user_id: user.id, impersonation_expires_at: 1.hour.from_now)
|
|
end
|
|
end
|
|
|
|
before do
|
|
helper.stubs(:current_user).returns(user)
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::USER_TOKEN_KEY] = auth_token
|
|
end
|
|
|
|
it "binds the stored value to the impersonating token" do
|
|
key = helper.shared_session_key
|
|
expect(
|
|
Discourse.redis.get(Auth::DefaultCurrentUserProvider.shared_session_redis_key(key)),
|
|
).to eq(auth_token.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "when the request carries no auth token" do
|
|
before do
|
|
helper.stubs(:current_user).returns(user)
|
|
helper.request.env[Auth::DefaultCurrentUserProvider::USER_TOKEN_KEY] = nil
|
|
end
|
|
|
|
it "returns no shared session key" do
|
|
expect(helper.shared_session_key).to eq(nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#can_sign_up?" do
|
|
it "returns false when the site is in read only mode" do
|
|
expect(helper.can_sign_up?).to eq(true)
|
|
|
|
helper.instance_variable_set(:@readonly_mode, true)
|
|
|
|
expect(helper.can_sign_up?).to eq(false)
|
|
end
|
|
end
|
|
end
|